From 5ac5cedbd455710102bab5d164130481a0fcb48f Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 23 Oct 2025 11:06:17 +0200 Subject: [PATCH 1/6] Add expanded reproducer test set --- test/Jamfile | 1 + test/github_issue_282.cpp | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 test/github_issue_282.cpp diff --git a/test/Jamfile b/test/Jamfile index 246b2088..57f19c57 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -73,3 +73,4 @@ 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 ; diff --git a/test/github_issue_282.cpp b/test/github_issue_282.cpp new file mode 100644 index 00000000..40dfa9d3 --- /dev/null +++ b/test/github_issue_282.cpp @@ -0,0 +1,45 @@ +// 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 +#include + +template +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 +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"); + } +} + +int main() +{ + test_no_format<20>(); + test_no_format<100>(); + test_no_format::max_chars10>(); + + test_with_format<20>(); + test_with_format<100>(); + test_with_format::max_chars10>(); + + return boost::report_errors(); +} From d08f7974ceb53be3940d53524835a666be7b8989 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 23 Oct 2025 11:06:42 +0200 Subject: [PATCH 2/6] Change crossover point between fixed and scientific format for min chars --- src/to_chars_float_impl.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/to_chars_float_impl.hpp b/src/to_chars_float_impl.hpp index d1be9723..df20fd95 100644 --- a/src/to_chars_float_impl.hpp +++ b/src/to_chars_float_impl.hpp @@ -680,6 +680,7 @@ 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::value ? static_cast(1e16) : static_cast(1e7); + constexpr auto min_fractional_value = static_cast(1) / static_cast(1000); // 1e-1 takes more characters than 0.1 constexpr auto max_value = static_cast((std::numeric_limits::max)()); // Unspecified precision so we always go with the shortest representation @@ -687,7 +688,7 @@ to_chars_result to_chars_float_impl(char* first, char* last, Real value, chars_f { 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); } From 26392cdbb39da2c274b710beca7c554cea4fc4c1 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 23 Oct 2025 11:13:55 +0200 Subject: [PATCH 3/6] Test further powers of 10 --- test/github_issue_282.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/github_issue_282.cpp b/test/github_issue_282.cpp index 40dfa9d3..d0bbd6db 100644 --- a/test/github_issue_282.cpp +++ b/test/github_issue_282.cpp @@ -31,6 +31,18 @@ void test_with_format() } } +template +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>(); @@ -41,5 +53,20 @@ int main() test_with_format<100>(); test_with_format::max_chars10>(); + // 0.01 vs 1e-02 + test_smaller<20>(0.01, "0.01"); + test_smaller<100>(0.01, "0.01"); + test_smaller::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::max_chars10>(0.001, "0.001"); + + // 0.0001 vs 1e-04 + test_smaller<20>(0.0001, "1e-04"); + test_smaller<100>(0.0001, "1e-04"); + test_smaller::max_chars10>(0.0001, "1e-04"); + return boost::report_errors(); } From bea21229c677183801aa875a80a2bbf1b348dfce Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 23 Oct 2025 11:14:10 +0200 Subject: [PATCH 4/6] Change comp and further reduce crossover --- src/to_chars_float_impl.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/to_chars_float_impl.hpp b/src/to_chars_float_impl.hpp index df20fd95..7abf5bee 100644 --- a/src/to_chars_float_impl.hpp +++ b/src/to_chars_float_impl.hpp @@ -680,7 +680,7 @@ 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::value ? static_cast(1e16) : static_cast(1e7); - constexpr auto min_fractional_value = static_cast(1) / static_cast(1000); // 1e-1 takes more characters than 0.1 + constexpr auto min_fractional_value = static_cast(1) / static_cast(10000); // 1e-1 takes more characters than 0.1 constexpr auto max_value = static_cast((std::numeric_limits::max)()); // Unspecified precision so we always go with the shortest representation @@ -688,7 +688,7 @@ to_chars_result to_chars_float_impl(char* first, char* last, Real value, chars_f { if (fmt == boost::charconv::chars_format::general) { - if (abs_value >= min_fractional_value && 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); } From 426ecd9816945ce42815f4d7f4567c6c307420d4 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 23 Oct 2025 11:23:56 +0200 Subject: [PATCH 5/6] Change buffer check depending on if a second block exists --- src/to_chars.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/to_chars.cpp b/src/to_chars.cpp index 24ddf0c6..0bdde32e 100644 --- a/src/to_chars.cpp +++ b/src/to_chars.cpp @@ -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 {}; @@ -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); From fb9008f483c61ed8900d0d1b5fa210fd1282e53c Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Thu, 23 Oct 2025 11:32:26 +0200 Subject: [PATCH 6/6] Match GCC 15 behavior --- src/to_chars_float_impl.hpp | 2 +- test/github_issue_282.cpp | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/to_chars_float_impl.hpp b/src/to_chars_float_impl.hpp index 7abf5bee..916d0c4e 100644 --- a/src/to_chars_float_impl.hpp +++ b/src/to_chars_float_impl.hpp @@ -680,7 +680,7 @@ 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::value ? static_cast(1e16) : static_cast(1e7); - constexpr auto min_fractional_value = static_cast(1) / static_cast(10000); // 1e-1 takes more characters than 0.1 + constexpr auto min_fractional_value = static_cast(1) / static_cast(100000); // 1e-1 takes more characters than 0.1 constexpr auto max_value = static_cast((std::numeric_limits::max)()); // Unspecified precision so we always go with the shortest representation diff --git a/test/github_issue_282.cpp b/test/github_issue_282.cpp index d0bbd6db..17383e6c 100644 --- a/test/github_issue_282.cpp +++ b/test/github_issue_282.cpp @@ -53,6 +53,8 @@ int main() test_with_format<100>(); test_with_format::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"); @@ -64,9 +66,14 @@ int main() test_smaller::max_chars10>(0.001, "0.001"); // 0.0001 vs 1e-04 - test_smaller<20>(0.0001, "1e-04"); - test_smaller<100>(0.0001, "1e-04"); - test_smaller::max_chars10>(0.0001, "1e-04"); + test_smaller<20>(0.0001, "0.0001"); + test_smaller<100>(0.0001, "0.0001"); + test_smaller::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::max_chars10>(0.00001, "1e-05"); return boost::report_errors(); }