Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,6 @@ jobs:
- libc++-14-dev
- libc++abi-14-dev

- toolset: clang
cxxstd: "03,11,14,17,20,2b"
os: macos-13
- toolset: clang
cxxstd: "03,11,14,17,20,2b"
os: macos-14
Expand Down Expand Up @@ -568,7 +565,6 @@ jobs:
matrix:
include:
- os: ubuntu-24.04
- os: macos-13
- os: macos-14
- os: macos-15

Expand Down Expand Up @@ -616,7 +612,6 @@ jobs:
matrix:
include:
- os: ubuntu-24.04
- os: macos-13
- os: macos-14
- os: macos-15

Expand Down Expand Up @@ -674,7 +669,6 @@ jobs:
matrix:
include:
- os: ubuntu-24.04
- os: macos-13
- os: macos-14
- os: macos-15

Expand Down
17 changes: 2 additions & 15 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,9 @@ target_include_directories(boost_charconv PUBLIC include)

# find_library for quadmath does not always work so attempt
# to compile the trivial test case we use with B2
include(CheckCXXSourceRuns)
set(BOOST_CHARCONV_QUADMATH_TEST_SOURCE
"
#include <quadmath.h>
int main()
{
__float128 f = -2.0Q;
f = fabsq(f);
__float128 big = HUGE_VALQ;

return f == big;
}
")

include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_LIBRARIES "quadmath")
check_cxx_source_runs("${BOOST_CHARCONV_QUADMATH_TEST_SOURCE}" BOOST_CHARCONV_QUADMATH_FOUND)
check_cxx_source_compiles("#include <${CMAKE_CURRENT_SOURCE_DIR}/config/has_float128.cpp>" BOOST_CHARCONV_QUADMATH_FOUND)
set(CMAKE_REQUIRED_LIBRARIES "")

target_link_libraries(boost_charconv
Expand Down
10 changes: 5 additions & 5 deletions include/boost/charconv/detail/dragonbox/floff.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1526,13 +1526,13 @@ BOOST_CHARCONV_SAFEBUFFERS to_chars_result floff(const double x, int precision,
if (fmt == boost::charconv::chars_format::scientific)
{
remaining_digits = precision + 1;
int exponent_print_length =
decimal_exponent_normalized >= 100 ? 5 :
decimal_exponent_normalized <= -100 ? 6 :
decimal_exponent_normalized >= 0 ? 4 : 5;

// e+XX or e+XXX since we always print at least two characters e.g. e+02
const int exponent_print_length =
decimal_exponent_normalized > -100 && decimal_exponent_normalized < 100 ? 4 : 5;

// No trailing decimal dot.
auto minimum_required_buffer_size =
const auto minimum_required_buffer_size =
static_cast<std::size_t>(remaining_digits + exponent_print_length + (precision != 0 ? 1 : 0));
if (buffer_size < minimum_required_buffer_size)
{
Expand Down
12 changes: 6 additions & 6 deletions src/to_chars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,12 +313,6 @@ namespace boost { namespace charconv { namespace detail { namespace to_chars_det
{
auto buffer = first;

const std::ptrdiff_t total_length = total_buffer_length(17, exponent, false);
if (total_length > (last - first))
{
return {last, std::errc::value_too_large};
}

// Print significand by decomposing it into a 9-digit block and a 8-digit block.
std::uint32_t first_block;
std::uint32_t second_block {};
Expand All @@ -337,6 +331,12 @@ namespace boost { namespace charconv { namespace detail { namespace to_chars_det
no_second_block = true;
}

const std::ptrdiff_t total_length = total_buffer_length(no_second_block ? 9 : 17, exponent, false);;
if (total_length > (last - first))
{
return {last, std::errc::value_too_large};
}

if (no_second_block)
{
print_9_digits(first_block, exponent, buffer);
Expand Down
3 changes: 2 additions & 1 deletion src/to_chars_float_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -680,14 +680,15 @@ to_chars_result to_chars_float_impl(char* first, char* last, Real value, chars_f

auto abs_value = std::abs(value);
constexpr auto max_fractional_value = std::is_same<Real, double>::value ? static_cast<Real>(1e16) : static_cast<Real>(1e7);
constexpr auto min_fractional_value = static_cast<Real>(1) / static_cast<Real>(100000); // 1e-1 takes more characters than 0.1
constexpr auto max_value = static_cast<Real>((std::numeric_limits<Unsigned_Integer>::max)());

// Unspecified precision so we always go with the shortest representation
if (precision == -1)
{
if (fmt == boost::charconv::chars_format::general)
{
if (abs_value >= 1 && abs_value < max_fractional_value)
if (abs_value > min_fractional_value && abs_value < max_fractional_value)
{
return to_chars_fixed_impl(first, last, value, fmt, precision);
}
Expand Down
2 changes: 2 additions & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,5 @@ run github_issue_186.cpp ;
run github_issue_212.cpp ;
run github_issue_266.cpp ;
run github_issue_267.cpp ;
run github_issue_280.cpp ;
run github_issue_282.cpp ;
61 changes: 61 additions & 0 deletions test/github_issue_280.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// See: https://github.com/boostorg/charconv/issues/280

#include <boost/charconv.hpp>
#include <boost/core/lightweight_test.hpp>
#include <string>

template <std::size_t precision>
void test(const char* result_string)
{
constexpr double d = DBL_MIN;
// for scientific, precision + 7 should suffice:
// <digit> <.> <#precision digits> <e> <sign> <up to 3 digits>
// 1 2 3 4 7
constexpr size_t buf_size = precision + 7;
char buf[buf_size];

auto result = boost::charconv::to_chars(buf, buf + buf_size, d, boost::charconv::chars_format::scientific, precision);
BOOST_TEST(result);

// We have to use memcmp here since there is no space to null terminate
BOOST_TEST(std::memcmp(buf, result_string, precision) == 0);
}

template <std::size_t precision>
void test_negative(const char* result_string)
{
constexpr double d = -DBL_MIN;
// for scientific, precision + 7 should suffice:
// <digit> <.> <#precision digits> <e> <sign> <up to 3 digits>
// 1 2 3 4 7
constexpr size_t buf_size = 1 + precision + 7;
char buf[buf_size];

auto result = boost::charconv::to_chars(buf, buf + buf_size, d, boost::charconv::chars_format::scientific, precision);
BOOST_TEST(result);

// We have to use memcmp here since there is no space to null terminate
BOOST_TEST(std::memcmp(buf, result_string, precision) == 0);
}

int main()
{
// DBL_MIN = 2.2250738585072013830902327173324040642192159804623318306e-308
test<1>("2.2e-308");
test<2>("2.23e-308");
test<3>("2.225e-308");
test<4>("2.2251e-308");
test<5>("2.22507e-308");

test_negative<1>("-2.2e-308");
test_negative<2>("-2.23e-308");
test_negative<3>("-2.225e-308");
test_negative<4>("-2.2251e-308");
test_negative<5>("-2.22507e-308");

return boost::report_errors();
}
79 changes: 79 additions & 0 deletions test/github_issue_282.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// See: https://github.com/boostorg/charconv/issues/282

#include <boost/charconv.hpp>
#include <boost/core/lightweight_test.hpp>

template <std::size_t size>
void test_no_format()
{
char buffer[size];
const auto r = boost::charconv::to_chars(buffer, buffer + size, 0.1);
if (BOOST_TEST(r))
{
*r.ptr = '\0';
BOOST_TEST_CSTR_EQ(buffer, "0.1");
}
}

template <std::size_t size>
void test_with_format()
{
char buffer[size];
const auto r = boost::charconv::to_chars(buffer, buffer + size, 0.1, boost::charconv::chars_format::general);
if (BOOST_TEST(r))
{
*r.ptr = '\0';
BOOST_TEST_CSTR_EQ(buffer, "0.1");
}
}

template <std::size_t size>
void test_smaller(const double value, const char* res)
{
char buffer[size];
const auto r = boost::charconv::to_chars(buffer, buffer + size, value, boost::charconv::chars_format::general);
if (BOOST_TEST(r))
{
*r.ptr = '\0';
BOOST_TEST_CSTR_EQ(buffer, res);
}
}

int main()
{
test_no_format<20>();
test_no_format<100>();
test_no_format<boost::charconv::limits<double>::max_chars10>();

test_with_format<20>();
test_with_format<100>();
test_with_format<boost::charconv::limits<double>::max_chars10>();

// The following match the behavior of GCC 15.2

// 0.01 vs 1e-02
test_smaller<20>(0.01, "0.01");
test_smaller<100>(0.01, "0.01");
test_smaller<boost::charconv::limits<double>::max_chars10>(0.01, "0.01");

// 0.001 vs 1e-03
test_smaller<20>(0.001, "0.001");
test_smaller<100>(0.001, "0.001");
test_smaller<boost::charconv::limits<double>::max_chars10>(0.001, "0.001");

// 0.0001 vs 1e-04
test_smaller<20>(0.0001, "0.0001");
test_smaller<100>(0.0001, "0.0001");
test_smaller<boost::charconv::limits<double>::max_chars10>(0.0001, "0.0001");

// 0.00001 vs 1e-05
test_smaller<20>(0.00001, "1e-05");
test_smaller<100>(0.00001, "1e-05");
test_smaller<boost::charconv::limits<double>::max_chars10>(0.00001, "1e-05");

return boost::report_errors();
}