From a98dccdb113e1ec336c0b8dd9b9e86ce6a99c553 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 2 Feb 2024 11:30:34 +0100 Subject: [PATCH 01/27] Add non-finite values test --- test/Jamfile | 1 + test/github_issue_152.cpp | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 test/github_issue_152.cpp diff --git a/test/Jamfile b/test/Jamfile index 607a1c14..343619ee 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -57,4 +57,5 @@ run P2497.cpp ; run github_issue_110.cpp ; run github_issue_122.cpp ; run from_chars_string_view.cpp ; +run github_issue_152.cpp ; run github_issue_156.cpp ; diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp new file mode 100644 index 00000000..f3f4f418 --- /dev/null +++ b/test/github_issue_152.cpp @@ -0,0 +1,36 @@ +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include + +template +void test_non_finite() +{ + auto value = {std::numeric_limits::infinity(), -std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN(), -std::numeric_limits::quiet_NaN(), + std::numeric_limits::signaling_NaN(), -std::numeric_limits::signaling_NaN()}; + + for (const auto val : value) + { + char buffer[2]; + auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), val); + BOOST_TEST(r.ec == std::errc::result_out_of_range); + } +}; + +int main() +{ + test_non_finite(); + test_non_finite(); + test_non_finite(); + + #ifdef BOOST_CHARCONV_HAS_FLOAT128 + test_non_finite<__float128>(); + #endif + + return boost::report_errors(); +} From 420646e17e5f82e9d8f28de1fa22527d030a26c3 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 2 Feb 2024 11:40:34 +0100 Subject: [PATCH 02/27] Add first approximation of required buffer length --- .../boost/charconv/detail/buffer_sizing.hpp | 59 +++++++++++++++++++ src/to_chars.cpp | 14 +++-- src/to_chars_float_impl.hpp | 39 ++++-------- 3 files changed, 80 insertions(+), 32 deletions(-) create mode 100644 include/boost/charconv/detail/buffer_sizing.hpp diff --git a/include/boost/charconv/detail/buffer_sizing.hpp b/include/boost/charconv/detail/buffer_sizing.hpp new file mode 100644 index 00000000..a007e3c8 --- /dev/null +++ b/include/boost/charconv/detail/buffer_sizing.hpp @@ -0,0 +1,59 @@ +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_CHARCONV_DETAIL_BUFFER_SIZING_HPP +#define BOOST_CHARCONV_DETAIL_BUFFER_SIZING_HPP + +#include +#include +#include + +namespace boost { +namespace charconv { +namespace detail { + +template +inline int get_real_precision(int precision) noexcept +{ + // If the user did not specify a precision than we use the maximum representable amount + // and remove trailing zeros at the end + + int real_precision; + BOOST_IF_CONSTEXPR (std::is_same::value || std::is_same::value) + { + real_precision = precision == -1 ? std::numeric_limits::max_digits10 : precision; + } + else + { + #ifdef BOOST_CHARCONV_HAS_FLOAT128 + BOOST_CHARCONV_IF_CONSTEXPR (std::is_same::value) + { + real_precision = 33; + } + else + #endif + { + #if BOOST_CHARCONV_LDBL_BITS == 128 + real_precision = 33; + #else + real_precision = 18; + #endif + } + } + + return real_precision; +} + +template +inline int total_buffer_length(int real_precision, Int exp, bool signed_value) +{ + // Sign + integer part + '.' + precision of fraction part + e+/e- or p+/p- + exponent digits + return static_cast(signed_value) + 2 + real_precision + 2 + num_digits(exp); +} + +} //namespace detail +} //namespace charconv +} //namespace boost + +#endif //BOOST_CHARCONV_DETAIL_BUFFER_SIZING_HPP diff --git a/src/to_chars.cpp b/src/to_chars.cpp index 36709e34..405d8542 100644 --- a/src/to_chars.cpp +++ b/src/to_chars.cpp @@ -558,9 +558,12 @@ boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* la { static_assert(std::numeric_limits::is_iec559, "Long double must be IEEE 754 compliant"); - if (first > last) + // Sanity check our bounds + const std::ptrdiff_t buffer_size = last - first; + auto real_precision = get_real_precision(precision); + if (buffer_size < real_precision || first > last) { - return {last, std::errc::invalid_argument}; + return {last, std::errc::result_out_of_range}; } const auto classification = std::fpclassify(value); @@ -661,9 +664,12 @@ boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* la { char* const original_first = first; - if (first > last) + // Sanity check our bounds + const std::ptrdiff_t buffer_size = last - first; + auto real_precision = get_real_precision(precision); + if (buffer_size < real_precision || first > last) { - return {last, std::errc::invalid_argument}; + return {last, std::errc::result_out_of_range}; } if (isnanq(value)) diff --git a/src/to_chars_float_impl.hpp b/src/to_chars_float_impl.hpp index a4a232d4..de67a123 100644 --- a/src/to_chars_float_impl.hpp +++ b/src/to_chars_float_impl.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -224,34 +225,9 @@ Unsigned_Integer convert_value(Real value) noexcept template to_chars_result to_chars_hex(char* first, char* last, Real value, int precision) noexcept { - // If the user did not specify a precision than we use the maximum representable amount - // and remove trailing zeros at the end - - int real_precision; - BOOST_IF_CONSTEXPR (std::is_same::value || std::is_same::value) - { - real_precision = precision == -1 ? std::numeric_limits::max_digits10 : precision; - } - else - { - #ifdef BOOST_CHARCONV_HAS_FLOAT128 - BOOST_CHARCONV_IF_CONSTEXPR (std::is_same::value) - { - real_precision = 33; - } - else - #endif - { - #if BOOST_CHARCONV_LDBL_BITS == 128 - real_precision = 33; - #else - real_precision = 18; - #endif - } - } - // Sanity check our bounds const std::ptrdiff_t buffer_size = last - first; + auto real_precision = get_real_precision(precision); if (buffer_size < real_precision || first > last) { return {last, std::errc::result_out_of_range}; @@ -352,8 +328,7 @@ to_chars_result to_chars_hex(char* first, char* last, Real value, int precision) static_cast(unbiased_exponent); // Bounds check - // Sign + integer part + '.' + precision of fraction part + p+/p- + exponent digits - const std::ptrdiff_t total_length = (value < 0) + 2 + real_precision + 2 + num_digits(abs_unbiased_exponent); + const std::ptrdiff_t total_length = total_buffer_length(real_precision, abs_unbiased_exponent, (value < 0)); if (total_length > buffer_size) { return {last, std::errc::result_out_of_range}; @@ -550,6 +525,14 @@ to_chars_result to_chars_float_impl(char* first, char* last, Real value, chars_f { using Unsigned_Integer = typename std::conditional::value, std::uint64_t, std::uint32_t>::type; + // Sanity check our bounds + const std::ptrdiff_t buffer_size = last - first; + auto real_precision = get_real_precision(precision); + if (buffer_size < real_precision || first > last) + { + return {last, std::errc::result_out_of_range}; + } + 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 = 1 / max_fractional_value; From ffbd977615afd1c635d09838241049b744836687 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 2 Feb 2024 11:46:02 +0100 Subject: [PATCH 03/27] Add total length buffer check for fixed formatting --- src/to_chars_float_impl.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/to_chars_float_impl.hpp b/src/to_chars_float_impl.hpp index de67a123..324ca4dc 100644 --- a/src/to_chars_float_impl.hpp +++ b/src/to_chars_float_impl.hpp @@ -467,6 +467,13 @@ to_chars_result to_chars_fixed_impl(char* first, char* last, Real value, chars_f } } + // Make sure the result will fit in the buffer + const std::ptrdiff_t total_length = total_buffer_length(num_dig, value_struct.exponent, (value < 0)); + if (total_length > buffer_size) + { + return {last, std::errc::result_out_of_range}; + } + auto r = to_chars_integer_impl(first, last, value_struct.significand); if (r.ec != std::errc()) { From 02c6070500247fb835621198f6cc864706262233 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 2 Feb 2024 12:07:06 +0100 Subject: [PATCH 04/27] Add bounds checking to dragonbox --- .../boost/charconv/detail/buffer_sizing.hpp | 2 +- .../charconv/detail/dragonbox/dragonbox.hpp | 33 ++++++++++--------- src/to_chars.cpp | 29 ++++++++++++---- src/to_chars_float_impl.hpp | 13 +++----- 4 files changed, 45 insertions(+), 32 deletions(-) diff --git a/include/boost/charconv/detail/buffer_sizing.hpp b/include/boost/charconv/detail/buffer_sizing.hpp index a007e3c8..49bf8aa8 100644 --- a/include/boost/charconv/detail/buffer_sizing.hpp +++ b/include/boost/charconv/detail/buffer_sizing.hpp @@ -14,7 +14,7 @@ namespace charconv { namespace detail { template -inline int get_real_precision(int precision) noexcept +inline int get_real_precision(int precision = -1) noexcept { // If the user did not specify a precision than we use the maximum representable amount // and remove trailing zeros at the end diff --git a/include/boost/charconv/detail/dragonbox/dragonbox.hpp b/include/boost/charconv/detail/dragonbox/dragonbox.hpp index 59a34f18..717fbb65 100644 --- a/include/boost/charconv/detail/dragonbox/dragonbox.hpp +++ b/include/boost/charconv/detail/dragonbox/dragonbox.hpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include @@ -2560,15 +2562,17 @@ BOOST_FORCEINLINE BOOST_CHARCONV_SAFEBUFFERS auto to_decimal(Float x, Policies.. namespace to_chars_detail { template - extern char* to_chars(typename FloatTraits::carrier_uint significand, int exponent, char* buffer, chars_format fmt) noexcept; + extern to_chars_result dragon_box_print_chars(typename FloatTraits::carrier_uint significand, int exponent, char* first, char* last, chars_format fmt) noexcept; // Avoid needless ABI overhead incurred by tag dispatch. template - char* to_chars_n_impl(dragonbox_float_bits br, char* buffer, chars_format fmt) noexcept + to_chars_result to_chars_n_impl(dragonbox_float_bits br, char* first, char* last, chars_format fmt) noexcept { const auto exponent_bits = br.extract_exponent_bits(); const auto s = br.remove_exponent_bits(exponent_bits); + auto buffer = first; + if (br.is_finite(exponent_bits)) { if (s.is_negative()) @@ -2583,19 +2587,18 @@ namespace to_chars_detail { typename PolicyHolder::decimal_to_binary_rounding_policy{}, typename PolicyHolder::binary_to_decimal_rounding_policy{}, typename PolicyHolder::cache_policy{}); - return to_chars_detail::to_chars(result.significand, - result.exponent, buffer, fmt); + return to_chars_detail::dragon_box_print_chars(result.significand, result.exponent, buffer, last, fmt); } else { if (fmt != chars_format::scientific) { std::memcpy(buffer, "0", 1); // NOLINT: Specifically not null-terminated - return buffer + 1; + return {buffer + 1, std::errc()}; } std::memcpy(buffer, "0e+00", 5); // NOLINT: Specifically not null-terminated - return buffer + 5; + return {buffer + 5, std::errc()}; } } else @@ -2609,7 +2612,7 @@ namespace to_chars_detail { if (s.has_all_zero_significand_bits()) { std::memcpy(buffer, "inf", 3); // NOLINT: Specifically not null-terminated - return buffer + 3; + return {buffer + 3, std::errc()}; } else { @@ -2651,18 +2654,18 @@ namespace to_chars_detail { if (!s.is_negative()) { std::memcpy(buffer, "nan", 3); // NOLINT: Specifically not null-terminated - return buffer + 3; + return {buffer + 3, std::errc()}; } else { std::memcpy(buffer, "nan(ind)", 8); // NOLINT: Specifically not null-terminated - return buffer + 8; + return {buffer + 8, std::errc()}; } } else { std::memcpy(buffer, "nan(snan)", 9); // NOLINT: Specifically not null-terminated - return buffer + 9; + return {buffer + 9, std::errc()}; } } } @@ -2671,7 +2674,7 @@ namespace to_chars_detail { // Returns the next-to-end position template , typename... Policies> -char* to_chars_n(Float x, char* buffer, chars_format fmt, BOOST_ATTRIBUTE_UNUSED Policies... policies) noexcept +to_chars_result to_chars_n(Float x, char* first, char* last, chars_format fmt, BOOST_ATTRIBUTE_UNUSED Policies... policies) noexcept { using namespace policy_impl; @@ -2693,16 +2696,14 @@ char* to_chars_n(Float x, char* buffer, chars_format fmt, BOOST_ATTRIBUTE_UNUSED #endif - return to_chars_detail::to_chars_n_impl(dragonbox_float_bits(x), buffer, fmt); + return to_chars_detail::to_chars_n_impl(dragonbox_float_bits(x), first, last, fmt); } // Null-terminate and bypass the return value of fp_to_chars_n template , typename... Policies> -char* to_chars(Float x, char* buffer, chars_format fmt, Policies... policies) noexcept +to_chars_result dragonbox_to_chars(Float x, char* first, char* last, chars_format fmt, Policies... policies) noexcept { - auto ptr = to_chars_n(x, buffer, fmt, policies...); - *ptr = '\0'; - return ptr; + return to_chars_n(x, first, last, fmt, policies...); } }}} // Namespaces diff --git a/src/to_chars.cpp b/src/to_chars.cpp index 405d8542..99b4693e 100644 --- a/src/to_chars.cpp +++ b/src/to_chars.cpp @@ -265,8 +265,16 @@ namespace boost { namespace charconv { namespace detail { namespace to_chars_det } template <> - char* to_chars>(std::uint32_t s32, int exponent, char* buffer, chars_format fmt) noexcept + to_chars_result dragon_box_print_chars>(std::uint32_t s32, int exponent, char* first, char* last, chars_format fmt) noexcept { + auto buffer = first; + + const std::ptrdiff_t total_length = total_buffer_length(9, exponent, false); + if (total_length > (last - first)) + { + return {last, std::errc::result_out_of_range}; + } + // Print significand. print_9_digits(s32, exponent, buffer); @@ -285,7 +293,7 @@ namespace boost { namespace charconv { namespace detail { namespace to_chars_det buffer += 4; } - return buffer; + return {buffer, std::errc()}; } else { @@ -296,11 +304,20 @@ namespace boost { namespace charconv { namespace detail { namespace to_chars_det print_2_digits(std::uint32_t(exponent), buffer); buffer += 2; - return buffer; + return {buffer, std::errc()}; } template <> - char* to_chars>(const std::uint64_t significand, int exponent, char* buffer, chars_format fmt) noexcept { + to_chars_result dragon_box_print_chars>(const std::uint64_t significand, int exponent, char* first, char* last, chars_format fmt) noexcept + { + auto buffer = first; + + const std::ptrdiff_t total_length = total_buffer_length(17, exponent, false); + if (total_length > (last - first)) + { + return {last, std::errc::result_out_of_range}; + } + // 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 {}; @@ -496,7 +513,7 @@ namespace boost { namespace charconv { namespace detail { namespace to_chars_det buffer += 4; } - return buffer; + return {buffer, std::errc()}; } else { @@ -522,7 +539,7 @@ namespace boost { namespace charconv { namespace detail { namespace to_chars_det buffer += 2; } - return buffer; + return {buffer, std::errc()}; } #ifdef BOOST_MSVC diff --git a/src/to_chars_float_impl.hpp b/src/to_chars_float_impl.hpp index 324ca4dc..b35a25f5 100644 --- a/src/to_chars_float_impl.hpp +++ b/src/to_chars_float_impl.hpp @@ -564,14 +564,12 @@ to_chars_result to_chars_float_impl(char* first, char* last, Real value, chars_f } else { - auto* ptr = boost::charconv::detail::to_chars(value, first, fmt); - return { ptr, std::errc() }; + return boost::charconv::detail::dragonbox_to_chars(value, first, last, fmt); } } else if (fmt == boost::charconv::chars_format::scientific) { - auto* ptr = boost::charconv::detail::to_chars(value, first, fmt); - return { ptr, std::errc() }; + return boost::charconv::detail::dragonbox_to_chars(value, first, last, fmt); } } else @@ -588,16 +586,13 @@ to_chars_result to_chars_float_impl(char* first, char* last, Real value, chars_f } } - // Before passing to hex check for edge cases - BOOST_ATTRIBUTE_UNUSED char* ptr; const int classification = std::fpclassify(value); switch (classification) { case FP_INFINITE: case FP_NAN: // The dragonbox impl will return the correct type of NaN - ptr = boost::charconv::detail::to_chars(value, first, chars_format::general); - return { ptr, std::errc() }; + return boost::charconv::detail::dragonbox_to_chars(value, first, last, chars_format::general); case FP_ZERO: if (std::signbit(value)) { @@ -607,7 +602,7 @@ to_chars_result to_chars_float_impl(char* first, char* last, Real value, chars_f return {first + 4, std::errc()}; default: // Do nothing - (void)ptr; + (void)precision; } // Hex handles both cases already From f4171a60eb60d18ec1d0ca97d98ef097b58b28b8 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 2 Feb 2024 12:11:53 +0100 Subject: [PATCH 05/27] Fix long double function call --- src/to_chars.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/to_chars.cpp b/src/to_chars.cpp index 99b4693e..b150d253 100644 --- a/src/to_chars.cpp +++ b/src/to_chars.cpp @@ -577,7 +577,7 @@ boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* la // Sanity check our bounds const std::ptrdiff_t buffer_size = last - first; - auto real_precision = get_real_precision(precision); + auto real_precision = boost::charconv::detail::get_real_precision(precision); if (buffer_size < real_precision || first > last) { return {last, std::errc::result_out_of_range}; From d6b37f70129a6e0cc9ab026217065db0947e1e63 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 5 Feb 2024 09:16:19 +0100 Subject: [PATCH 06/27] Reorder checks to allow non-finite numbers in small buffers --- .../charconv/detail/dragonbox/dragonbox.hpp | 60 +++++++++++++++---- src/to_chars.cpp | 32 +++++----- src/to_chars_float_impl.hpp | 9 ++- test/github_issue_152.cpp | 20 +++++++ 4 files changed, 91 insertions(+), 30 deletions(-) diff --git a/include/boost/charconv/detail/dragonbox/dragonbox.hpp b/include/boost/charconv/detail/dragonbox/dragonbox.hpp index 717fbb65..caf9db89 100644 --- a/include/boost/charconv/detail/dragonbox/dragonbox.hpp +++ b/include/boost/charconv/detail/dragonbox/dragonbox.hpp @@ -2572,6 +2572,7 @@ namespace to_chars_detail { const auto s = br.remove_exponent_bits(exponent_bits); auto buffer = first; + const auto buffer_size = last - first; if (br.is_finite(exponent_bits)) { @@ -2597,22 +2598,38 @@ namespace to_chars_detail { return {buffer + 1, std::errc()}; } - std::memcpy(buffer, "0e+00", 5); // NOLINT: Specifically not null-terminated - return {buffer + 5, std::errc()}; + if (buffer_size >= 5) + { + std::memcpy(buffer, "0e+00", 5); // NOLINT: Specifically not null-terminated + return {buffer + 5, std::errc()}; + } + else + { + return {last, std::errc::result_out_of_range}; + } } } else { - if (s.is_negative()) + bool is_negative = false; + if (s.is_negative()) { *buffer = '-'; ++buffer; + is_negative = true; } if (s.has_all_zero_significand_bits()) { - std::memcpy(buffer, "inf", 3); // NOLINT: Specifically not null-terminated - return {buffer + 3, std::errc()}; + if (buffer_size >= 3 + static_cast(is_negative)) + { + std::memcpy(buffer, "inf", 3); // NOLINT: Specifically not null-terminated + return {buffer + 3, std::errc()}; + } + else + { + return {last, std::errc::result_out_of_range}; + } } else { @@ -2653,19 +2670,40 @@ namespace to_chars_detail { { if (!s.is_negative()) { - std::memcpy(buffer, "nan", 3); // NOLINT: Specifically not null-terminated - return {buffer + 3, std::errc()}; + if (buffer_size >= 3 + static_cast(is_negative)) + { + std::memcpy(buffer, "nan", 3); // NOLINT: Specifically not null-terminated + return {buffer + 3, std::errc()}; + } + else + { + return {last, std::errc::result_out_of_range}; + } } else { - std::memcpy(buffer, "nan(ind)", 8); // NOLINT: Specifically not null-terminated - return {buffer + 8, std::errc()}; + if (buffer_size >= 8 + static_cast(is_negative)) + { + std::memcpy(buffer, "nan(ind)", 8); // NOLINT: Specifically not null-terminated + return {buffer + 8, std::errc()}; + } + else + { + return {last, std::errc::result_out_of_range}; + } } } else { - std::memcpy(buffer, "nan(snan)", 9); // NOLINT: Specifically not null-terminated - return {buffer + 9, std::errc()}; + if (buffer_size >= 9 + static_cast(is_negative)) + { + std::memcpy(buffer, "nan(snan)", 9); // NOLINT: Specifically not null-terminated + return {buffer + 9, std::errc()}; + } + else + { + return {last, std::errc::result_out_of_range}; + } } } } diff --git a/src/to_chars.cpp b/src/to_chars.cpp index b150d253..3f99655e 100644 --- a/src/to_chars.cpp +++ b/src/to_chars.cpp @@ -575,14 +575,6 @@ boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* la { static_assert(std::numeric_limits::is_iec559, "Long double must be IEEE 754 compliant"); - // Sanity check our bounds - const std::ptrdiff_t buffer_size = last - first; - auto real_precision = boost::charconv::detail::get_real_precision(precision); - if (buffer_size < real_precision || first > last) - { - return {last, std::errc::result_out_of_range}; - } - const auto classification = std::fpclassify(value); #if BOOST_CHARCONV_LDBL_BITS == 128 if (classification == FP_NAN || classification == FP_INFINITE) @@ -602,6 +594,14 @@ boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* la } #endif + // Sanity check our bounds + const std::ptrdiff_t buffer_size = last - first; + auto real_precision = boost::charconv::detail::get_real_precision(precision); + if (buffer_size < real_precision || first > last) + { + return {last, std::errc::result_out_of_range}; + } + if (fmt == boost::charconv::chars_format::general || fmt == boost::charconv::chars_format::scientific) { const auto fd128 = boost::charconv::detail::ryu::long_double_to_fd128(value); @@ -681,14 +681,6 @@ boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* la { char* const original_first = first; - // Sanity check our bounds - const std::ptrdiff_t buffer_size = last - first; - auto real_precision = get_real_precision(precision); - if (buffer_size < real_precision || first > last) - { - return {last, std::errc::result_out_of_range}; - } - if (isnanq(value)) { return boost::charconv::detail::to_chars_nonfinite(first, last, value, FP_NAN); @@ -698,6 +690,14 @@ boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* la return boost::charconv::detail::to_chars_nonfinite(first, last, value, FP_INFINITE); } + // Sanity check our bounds + const std::ptrdiff_t buffer_size = last - first; + auto real_precision = get_real_precision<__float128>(precision); + if (buffer_size < real_precision || first > last) + { + return {last, std::errc::result_out_of_range}; + } + if ((fmt == boost::charconv::chars_format::general || fmt == boost::charconv::chars_format::scientific)) { const auto fd128 = boost::charconv::detail::ryu::float128_to_fd128(value); diff --git a/src/to_chars_float_impl.hpp b/src/to_chars_float_impl.hpp index b35a25f5..8487f312 100644 --- a/src/to_chars_float_impl.hpp +++ b/src/to_chars_float_impl.hpp @@ -422,6 +422,11 @@ template to_chars_result to_chars_fixed_impl(char* first, char* last, Real value, chars_format fmt = chars_format::general, int precision = -1) noexcept { const std::ptrdiff_t buffer_size = last - first; + auto real_precision = get_real_precision(precision); + if (buffer_size < real_precision || first > last) + { + return {last, std::errc::result_out_of_range}; + } auto abs_value = std::abs(value); @@ -533,9 +538,7 @@ to_chars_result to_chars_float_impl(char* first, char* last, Real value, chars_f using Unsigned_Integer = typename std::conditional::value, std::uint64_t, std::uint32_t>::type; // Sanity check our bounds - const std::ptrdiff_t buffer_size = last - first; - auto real_precision = get_real_precision(precision); - if (buffer_size < real_precision || first > last) + if (first >= last) { return {last, std::errc::result_out_of_range}; } diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index f3f4f418..bf3e0d8b 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -20,6 +20,26 @@ void test_non_finite() auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), val); BOOST_TEST(r.ec == std::errc::result_out_of_range); } + + char inf_buffer[3]; + auto r_inf = boost::charconv::to_chars(inf_buffer, inf_buffer + 3, std::numeric_limits::infinity()); + BOOST_TEST(r_inf); + BOOST_TEST(!std::memcmp(inf_buffer, "inf", 3)); + + char nan_buffer[3]; + auto r_nan = boost::charconv::to_chars(nan_buffer, nan_buffer + 3, std::numeric_limits::quiet_NaN()); + BOOST_TEST(r_nan); + BOOST_TEST(!std::memcmp(nan_buffer, "nan", 3)); + + char neg_nan_buffer[9]; + auto r_neg_nan = boost::charconv::to_chars(neg_nan_buffer, neg_nan_buffer + 9, -std::numeric_limits::quiet_NaN()); + BOOST_TEST(r_neg_nan); + BOOST_TEST(!std::memcmp(neg_nan_buffer, "-nan(ind)", 9)); + + char snan_buffer[9]; + auto r_snan = boost::charconv::to_chars(snan_buffer, snan_buffer + 9, std::numeric_limits::signaling_NaN()); + BOOST_TEST(r_snan); + BOOST_TEST(!std::memcmp(snan_buffer, "nan(snan)", 9)); }; int main() From bd5c741f598b2b90c343c8c29b16579e8e3c1974 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 5 Feb 2024 09:22:47 +0100 Subject: [PATCH 07/27] Add test for random number to fit in minimum size buffer --- test/github_issue_152.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index bf3e0d8b..ae1b54c2 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -6,6 +6,11 @@ #include #include #include +#include +#include + +constexpr std::size_t N = 1024; +static std::mt19937_64 rng(42); template void test_non_finite() @@ -42,6 +47,27 @@ void test_non_finite() BOOST_TEST(!std::memcmp(snan_buffer, "nan(snan)", 9)); }; +template +void test_min_buffer_size() +{ + std::uniform_real_distribution dist((std::numeric_limits::lowest)(), (std::numeric_limits::max)()); + + // No guarantees are made for fixed, especially in this domain + auto formats = {boost::charconv::chars_format::hex, + boost::charconv::chars_format::scientific, + boost::charconv::chars_format::general}; + + for (const auto format : formats) + { + for (std::size_t i = 0; i < N; ++i) + { + char buffer[boost::charconv::limits::max_chars10]; + auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), dist(rng), format); + BOOST_TEST(r); + } + } +} + int main() { test_non_finite(); @@ -52,5 +78,9 @@ int main() test_non_finite<__float128>(); #endif + test_min_buffer_size(); + test_min_buffer_size(); + test_min_buffer_size(); + return boost::report_errors(); } From ff22c4bf82052dea706153bc433d5811a0edfbff Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 5 Feb 2024 09:58:25 +0100 Subject: [PATCH 08/27] Add bounds checking to non-finite RYU handling --- .../charconv/detail/ryu/ryu_generic_128.hpp | 59 +++++++++++++++---- src/to_chars.cpp | 4 ++ 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/include/boost/charconv/detail/ryu/ryu_generic_128.hpp b/include/boost/charconv/detail/ryu/ryu_generic_128.hpp index 150b6072..f24ab98c 100644 --- a/include/boost/charconv/detail/ryu/ryu_generic_128.hpp +++ b/include/boost/charconv/detail/ryu/ryu_generic_128.hpp @@ -282,7 +282,7 @@ static inline struct floating_decimal_128 generic_binary_to_decimal( return {output, exp, ieeeSign}; } -static inline int copy_special_str(char* result, const struct floating_decimal_128 fd) noexcept +static inline int copy_special_str(char* result, const std::ptrdiff_t result_size, const struct floating_decimal_128 fd) noexcept { if (fd.sign) { @@ -298,13 +298,27 @@ static inline int copy_special_str(char* result, const struct floating_decimal_1 fd.mantissa == static_cast(6917529027641081856) || fd.mantissa == static_cast(1) << 110) // 2^110 { - std::memcpy(result, "nan(snan)", 9); - return 10; + if (result_size >= 10) + { + std::memcpy(result, "nan(snan)", 9); + return 10; + } + else + { + return -1; + } } else { - std::memcpy(result, "nan(ind)", 8); - return 9; + if (result_size >= 9) + { + std::memcpy(result, "nan(ind)", 8); + return 9; + } + else + { + return -1; + } } } else @@ -313,26 +327,45 @@ static inline int copy_special_str(char* result, const struct floating_decimal_1 fd.mantissa == static_cast(6917529027641081856) || fd.mantissa == static_cast(1) << 110) // 2^110 { - std::memcpy(result, "nan(snan)", 9); - return 9; + if (result_size >= 9) + { + std::memcpy(result, "nan(snan)", 9); + return 9; + } + else + { + return -1; + } } else { - std::memcpy(result, "nan", 3); - return 3; + if (result_size >= 3) + { + std::memcpy(result, "nan", 3); + return 3; + } + else + { + return -1; + } } } } - memcpy(result, "inf", 3); - return static_cast(fd.sign) + 3; + if (result_size >= 3 + static_cast(fd.sign)) + { + memcpy(result, "inf", 3); + return static_cast(fd.sign) + 3; + } + + return -1; } static inline int generic_to_chars_fixed(const struct floating_decimal_128 v, char* result, const ptrdiff_t result_size, int precision) noexcept { if (v.exponent == fd128_exceptional_exponent) { - return copy_special_str(result, v); + return copy_special_str(result, result_size, v); } // Step 5: Print the decimal representation. @@ -416,7 +449,7 @@ static inline int generic_to_chars(const struct floating_decimal_128 v, char* re { if (v.exponent == fd128_exceptional_exponent) { - return copy_special_str(result, v); + return copy_special_str(result, result_size, v); } unsigned_128_type output = v.mantissa; diff --git a/src/to_chars.cpp b/src/to_chars.cpp index 3f99655e..9a310897 100644 --- a/src/to_chars.cpp +++ b/src/to_chars.cpp @@ -591,6 +591,10 @@ boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* la { return { first + num_chars, std::errc() }; } + else + { + return {last, std::errc::result_out_of_range}; + } } #endif From 514e61cee5595c8c1d3e17d9f7e51056374085b0 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 5 Feb 2024 10:20:27 +0100 Subject: [PATCH 09/27] Add debug statement --- test/github_issue_152.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index ae1b54c2..6d61a26f 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include constexpr std::size_t N = 1024; @@ -15,11 +16,11 @@ static std::mt19937_64 rng(42); template void test_non_finite() { - auto value = {std::numeric_limits::infinity(), -std::numeric_limits::infinity(), - std::numeric_limits::quiet_NaN(), -std::numeric_limits::quiet_NaN(), - std::numeric_limits::signaling_NaN(), -std::numeric_limits::signaling_NaN()}; + constexpr std::array values = {std::numeric_limits::infinity(), -std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN(), -std::numeric_limits::quiet_NaN(), + std::numeric_limits::signaling_NaN(), -std::numeric_limits::signaling_NaN()}; - for (const auto val : value) + for (const auto val : values) { char buffer[2]; auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), val); @@ -62,8 +63,12 @@ void test_min_buffer_size() for (std::size_t i = 0; i < N; ++i) { char buffer[boost::charconv::limits::max_chars10]; - auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), dist(rng), format); - BOOST_TEST(r); + const T value = dist(rng); + auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), value, format); + if (!BOOST_TEST(r)) + { + std::cerr << "Overflow for: " << value << std::endl; // LCOV_EXCL_LINE + } } } } From 92415b8657d57f546b8dce7a8076b04320786ab5 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 5 Feb 2024 11:42:31 +0100 Subject: [PATCH 10/27] Fix over counting in buffer sizing --- include/boost/charconv/detail/buffer_sizing.hpp | 2 +- test/github_issue_152.cpp | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/include/boost/charconv/detail/buffer_sizing.hpp b/include/boost/charconv/detail/buffer_sizing.hpp index 49bf8aa8..5b48cda2 100644 --- a/include/boost/charconv/detail/buffer_sizing.hpp +++ b/include/boost/charconv/detail/buffer_sizing.hpp @@ -49,7 +49,7 @@ template inline int total_buffer_length(int real_precision, Int exp, bool signed_value) { // Sign + integer part + '.' + precision of fraction part + e+/e- or p+/p- + exponent digits - return static_cast(signed_value) + 2 + real_precision + 2 + num_digits(exp); + return static_cast(signed_value) + 1 + real_precision + 2 + num_digits(exp); } } //namespace detail diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index 6d61a26f..43312c89 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -16,9 +16,9 @@ static std::mt19937_64 rng(42); template void test_non_finite() { - constexpr std::array values = {std::numeric_limits::infinity(), -std::numeric_limits::infinity(), + constexpr std::array values = {{std::numeric_limits::infinity(), -std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN(), -std::numeric_limits::quiet_NaN(), - std::numeric_limits::signaling_NaN(), -std::numeric_limits::signaling_NaN()}; + std::numeric_limits::signaling_NaN(), -std::numeric_limits::signaling_NaN()}}; for (const auto val : values) { @@ -64,6 +64,12 @@ void test_min_buffer_size() { char buffer[boost::charconv::limits::max_chars10]; const T value = dist(rng); + + if (!std::isnormal(value)) + { + continue; + } + auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), value, format); if (!BOOST_TEST(r)) { From fe19e776d2a4e3bacb73feb6f005cd449bf2886c Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 5 Feb 2024 12:05:57 +0100 Subject: [PATCH 11/27] Fix __float128 sanity check --- src/to_chars.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/to_chars.cpp b/src/to_chars.cpp index 9a310897..c1cc7dfe 100644 --- a/src/to_chars.cpp +++ b/src/to_chars.cpp @@ -683,6 +683,12 @@ boost::charconv::to_chars_result boost::charconv::to_chars( char* first, char* l boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* last, __float128 value, boost::charconv::chars_format fmt, int precision) noexcept { + // Sanity check our bounds + if (first <= last) + { + return {last, std::errc::result_out_of_range}; + } + char* const original_first = first; if (isnanq(value)) @@ -696,7 +702,7 @@ boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* la // Sanity check our bounds const std::ptrdiff_t buffer_size = last - first; - auto real_precision = get_real_precision<__float128>(precision); + auto real_precision = boost::charconv::detail::get_real_precision<__float128>(precision); if (buffer_size < real_precision || first > last) { return {last, std::errc::result_out_of_range}; @@ -711,6 +717,10 @@ boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* la { return { first + num_chars, std::errc() }; } + else + { + return {last, std::errc::result_out_of_range}; + } } else if (fmt == boost::charconv::chars_format::hex) { From 1fb4a31ac54c70bd97c85e43a6e353ef5d38471a Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 5 Feb 2024 12:10:38 +0100 Subject: [PATCH 12/27] Ignore MSVC C4127 --- include/boost/charconv/detail/buffer_sizing.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/boost/charconv/detail/buffer_sizing.hpp b/include/boost/charconv/detail/buffer_sizing.hpp index 5b48cda2..0a3b3dbf 100644 --- a/include/boost/charconv/detail/buffer_sizing.hpp +++ b/include/boost/charconv/detail/buffer_sizing.hpp @@ -13,6 +13,11 @@ namespace boost { namespace charconv { namespace detail { +#ifdef BOOST_MSVC +# pragma warning(push) +# pragma warning(disable: 4127) // Conditional expression for BOOST_IF_CONSTEXPR will be constant in not C++17 +#endif + template inline int get_real_precision(int precision = -1) noexcept { @@ -52,6 +57,10 @@ inline int total_buffer_length(int real_precision, Int exp, bool signed_value) return static_cast(signed_value) + 1 + real_precision + 2 + num_digits(exp); } +#ifdef BOOST_MSVC +# pragma warning(pop) +#endif + } //namespace detail } //namespace charconv } //namespace boost From eeddadf847e25794df6e4866d753db79f83f94d3 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 5 Feb 2024 12:23:11 +0100 Subject: [PATCH 13/27] Change range for MSVC --- test/github_issue_152.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index 43312c89..88521cbd 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -51,7 +51,11 @@ void test_non_finite() template void test_min_buffer_size() { + #ifdef BOOST_MSVC + std::uniform_real_distribution dist((std::numeric_limits::min)(), (std::numeric_limits::max)()); + #else std::uniform_real_distribution dist((std::numeric_limits::lowest)(), (std::numeric_limits::max)()); + #endif // No guarantees are made for fixed, especially in this domain auto formats = {boost::charconv::chars_format::hex, From ed59b7b9c21742a3320304147d33e785721c75b1 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 6 Feb 2024 08:36:55 +0100 Subject: [PATCH 14/27] Improve debug-ability --- test/github_issue_152.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index 88521cbd..ce1adec1 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -28,22 +28,25 @@ void test_non_finite() } char inf_buffer[3]; - auto r_inf = boost::charconv::to_chars(inf_buffer, inf_buffer + 3, std::numeric_limits::infinity()); + constexpr T inf_val = std::numeric_limits::infinity(); + auto r_inf = boost::charconv::to_chars(inf_buffer, inf_buffer + 3, inf_val); BOOST_TEST(r_inf); BOOST_TEST(!std::memcmp(inf_buffer, "inf", 3)); char nan_buffer[3]; - auto r_nan = boost::charconv::to_chars(nan_buffer, nan_buffer + 3, std::numeric_limits::quiet_NaN()); + constexpr T nan_val = std::numeric_limits::quiet_NaN(); + auto r_nan = boost::charconv::to_chars(nan_buffer, nan_buffer + 3, nan_val); BOOST_TEST(r_nan); BOOST_TEST(!std::memcmp(nan_buffer, "nan", 3)); char neg_nan_buffer[9]; - auto r_neg_nan = boost::charconv::to_chars(neg_nan_buffer, neg_nan_buffer + 9, -std::numeric_limits::quiet_NaN()); + auto r_neg_nan = boost::charconv::to_chars(neg_nan_buffer, neg_nan_buffer + 9, -nan_val); BOOST_TEST(r_neg_nan); BOOST_TEST(!std::memcmp(neg_nan_buffer, "-nan(ind)", 9)); char snan_buffer[9]; - auto r_snan = boost::charconv::to_chars(snan_buffer, snan_buffer + 9, std::numeric_limits::signaling_NaN()); + constexpr T snan_val = std::numeric_limits::signaling_NaN(); + auto r_snan = boost::charconv::to_chars(snan_buffer, snan_buffer + 9, snan_val); BOOST_TEST(r_snan); BOOST_TEST(!std::memcmp(snan_buffer, "nan(snan)", 9)); }; From 29616508aba20a97c70cdc3382e712dd6ed4d317 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 6 Feb 2024 08:37:08 +0100 Subject: [PATCH 15/27] Fix comparison --- src/to_chars.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/to_chars.cpp b/src/to_chars.cpp index c1cc7dfe..2a6764aa 100644 --- a/src/to_chars.cpp +++ b/src/to_chars.cpp @@ -684,7 +684,7 @@ boost::charconv::to_chars_result boost::charconv::to_chars( char* first, char* l boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* last, __float128 value, boost::charconv::chars_format fmt, int precision) noexcept { // Sanity check our bounds - if (first <= last) + if (first >= last) { return {last, std::errc::result_out_of_range}; } From eec4f2cae47889acd8cc041296f2ad360807655d Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 6 Feb 2024 08:59:32 +0100 Subject: [PATCH 16/27] Specialize __float128 test --- test/github_issue_152.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index ce1adec1..2b415555 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -51,6 +51,36 @@ void test_non_finite() BOOST_TEST(!std::memcmp(snan_buffer, "nan(snan)", 9)); }; +#ifdef BOOST_CHARCONV_HAS_FLOAT128 +template <> +void test_non_finite<__float128>() +{ + constexpr std::array<__float128, 4> values = {{INFINITY, -INFINITY, NAN, -NAN}}; + + for (const auto val : values) + { + char buffer[2]; + auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), val); + BOOST_TEST(r.ec == std::errc::result_out_of_range); + } + + char inf_buffer[3]; + auto r_inf = boost::charconv::to_chars(inf_buffer, inf_buffer + 3, values[0]); + BOOST_TEST(r_inf); + BOOST_TEST(!std::memcmp(inf_buffer, "inf", 3)); + + char nan_buffer[3]; + auto r_nan = boost::charconv::to_chars(nan_buffer, nan_buffer + 3, values[2]); + BOOST_TEST(r_nan); + BOOST_TEST(!std::memcmp(nan_buffer, "nan", 3)); + + char neg_nan_buffer[9]; + auto r_neg_nan = boost::charconv::to_chars(neg_nan_buffer, neg_nan_buffer + 9, values[3]); + BOOST_TEST(r_neg_nan); + BOOST_TEST(!std::memcmp(neg_nan_buffer, "-nan(ind)", 9)); +} +#endif + template void test_min_buffer_size() { From b717e88e84750815a81197b83ff6c231732d27ad Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 6 Feb 2024 09:13:25 +0100 Subject: [PATCH 17/27] Add min buffer size test set for __float128 --- test/github_issue_152.cpp | 58 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index 2b415555..c8475f3d 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -2,8 +2,24 @@ // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt +#include +#include + +#ifdef BOOST_CHARCONV_HAS_FLOAT128 + +std::ostream& operator<<( std::ostream& os, __float128 v ) +{ + char buffer[ 256 ] {}; + quadmath_snprintf(buffer, sizeof(buffer), "%Qg", v); + os << buffer; + return os; +} + +#endif + #include #include +#include #include #include #include @@ -116,19 +132,53 @@ void test_min_buffer_size() } } +#ifdef BOOST_CHARCONV_HAS_FLOAT128 +template <> +void test_min_buffer_size<__float128>() +{ + boost::random::uniform_real_distribution<__float128> dist(-1e4000Q, 1e4000Q); + + // No guarantees are made for fixed, especially in this domain + auto formats = {boost::charconv::chars_format::hex, + boost::charconv::chars_format::scientific, + boost::charconv::chars_format::general}; + + for (const auto format : formats) + { + for (std::size_t i = 0; i < N; ++i) + { + char buffer[boost::charconv::limits<__float128>::max_chars10]; + const auto value = dist(rng); + + if (isinfq(value) || isnanq(value)) + { + continue; + } + + auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), value, format); + if (!BOOST_TEST(r)) + { + std::cerr << "Overflow for: " << value << std::endl; // LCOV_EXCL_LINE + } + } + } +} +#endif + int main() { test_non_finite(); test_non_finite(); test_non_finite(); - #ifdef BOOST_CHARCONV_HAS_FLOAT128 - test_non_finite<__float128>(); - #endif - test_min_buffer_size(); test_min_buffer_size(); test_min_buffer_size(); + #ifdef BOOST_CHARCONV_HAS_FLOAT128 + test_non_finite<__float128>(); + test_min_buffer_size<__float128>(); + #endif + return boost::report_errors(); } From db41374c0aa23ee1f505bb1586eb2fe25f2cf593 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 6 Feb 2024 09:20:59 +0100 Subject: [PATCH 18/27] Add limits overload for __float128 --- include/boost/charconv/limits.hpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/include/boost/charconv/limits.hpp b/include/boost/charconv/limits.hpp index 21c78c2d..beac0578 100644 --- a/include/boost/charconv/limits.hpp +++ b/include/boost/charconv/limits.hpp @@ -5,7 +5,7 @@ #ifndef BOOST_CHARCONV_LIMITS_HPP #define BOOST_CHARCONV_LIMITS_HPP -#include +#include #include #include @@ -72,6 +72,16 @@ template struct limits std::numeric_limits::max_digits10 + 3 + 2 + detail::exp_digits( std::numeric_limits::max_exponent10 ); // as above }; +#ifdef BOOST_CHARCONV_HAS_FLOAT128 + +template <> struct limits<__float128> +{ + BOOST_ATTRIBUTE_UNUSED static constexpr int max_chars10 = 33 + 3 + 2 + 5; + BOOST_ATTRIBUTE_UNUSED static constexpr int max_chars = max_chars10; +}; + +#endif + #if defined(BOOST_NO_CXX17_INLINE_VARIABLES) // Definitions of in-class constexpr members are allowed but deprecated in C++17 From f76d388ecb880102b9e1970df22b3c49b980768e Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 6 Feb 2024 09:21:18 +0100 Subject: [PATCH 19/27] Add boost.random to test CML --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7bf0e6e0..e662b9bb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,6 +8,6 @@ if(HAVE_BOOST_TEST) # https://crascit.com/2015/03/28/enabling-cxx11-in-cmake/ set(CMAKE_CXX_EXTENSIONS OFF) -boost_test_jamfile(FILE Jamfile LINK_LIBRARIES Boost::charconv Boost::core Boost::assert) +boost_test_jamfile(FILE Jamfile LINK_LIBRARIES Boost::charconv Boost::core Boost::assert Boost::random) endif() From 04952ae6e5ebebd2ad836ea793d21b83d0b9176f Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 6 Feb 2024 09:26:39 +0100 Subject: [PATCH 20/27] Change return val for error in RYU --- include/boost/charconv/detail/ryu/ryu_generic_128.hpp | 2 +- src/to_chars.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/charconv/detail/ryu/ryu_generic_128.hpp b/include/boost/charconv/detail/ryu/ryu_generic_128.hpp index f24ab98c..6b30c85d 100644 --- a/include/boost/charconv/detail/ryu/ryu_generic_128.hpp +++ b/include/boost/charconv/detail/ryu/ryu_generic_128.hpp @@ -484,7 +484,7 @@ static inline int generic_to_chars(const struct floating_decimal_128 v, char* re } else if (olength == 0) { - return -1; // Something has gone horribly wrong + return -2; // Something has gone horribly wrong } for (uint32_t i = 0; i < olength - 1; ++i) diff --git a/src/to_chars.cpp b/src/to_chars.cpp index 2a6764aa..43ba8bed 100644 --- a/src/to_chars.cpp +++ b/src/to_chars.cpp @@ -717,7 +717,7 @@ boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* la { return { first + num_chars, std::errc() }; } - else + else if (num_chars == -1) { return {last, std::errc::result_out_of_range}; } From 094a8a7658ea9f27a274feee54ab2bfcdcff142c Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 6 Feb 2024 10:59:47 +0100 Subject: [PATCH 21/27] Move float128 testing into its own file --- test/Jamfile | 1 + test/github_issue_152.cpp | 85 +----------------------- test/github_issue_152_float128.cpp | 102 +++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 84 deletions(-) create mode 100644 test/github_issue_152_float128.cpp diff --git a/test/Jamfile b/test/Jamfile index 343619ee..d2f289cc 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -58,4 +58,5 @@ run github_issue_110.cpp ; run github_issue_122.cpp ; run from_chars_string_view.cpp ; run github_issue_152.cpp ; +run github_issue_152_float128.cpp : : : [ check-target-builds ../config//has_float128 "GCC libquadmath and __float128 support" : "quadmath" ] ; run github_issue_156.cpp ; diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index c8475f3d..4ccb4f0a 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -2,21 +2,6 @@ // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt -#include -#include - -#ifdef BOOST_CHARCONV_HAS_FLOAT128 - -std::ostream& operator<<( std::ostream& os, __float128 v ) -{ - char buffer[ 256 ] {}; - quadmath_snprintf(buffer, sizeof(buffer), "%Qg", v); - os << buffer; - return os; -} - -#endif - #include #include #include @@ -67,40 +52,10 @@ void test_non_finite() BOOST_TEST(!std::memcmp(snan_buffer, "nan(snan)", 9)); }; -#ifdef BOOST_CHARCONV_HAS_FLOAT128 -template <> -void test_non_finite<__float128>() -{ - constexpr std::array<__float128, 4> values = {{INFINITY, -INFINITY, NAN, -NAN}}; - - for (const auto val : values) - { - char buffer[2]; - auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), val); - BOOST_TEST(r.ec == std::errc::result_out_of_range); - } - - char inf_buffer[3]; - auto r_inf = boost::charconv::to_chars(inf_buffer, inf_buffer + 3, values[0]); - BOOST_TEST(r_inf); - BOOST_TEST(!std::memcmp(inf_buffer, "inf", 3)); - - char nan_buffer[3]; - auto r_nan = boost::charconv::to_chars(nan_buffer, nan_buffer + 3, values[2]); - BOOST_TEST(r_nan); - BOOST_TEST(!std::memcmp(nan_buffer, "nan", 3)); - - char neg_nan_buffer[9]; - auto r_neg_nan = boost::charconv::to_chars(neg_nan_buffer, neg_nan_buffer + 9, values[3]); - BOOST_TEST(r_neg_nan); - BOOST_TEST(!std::memcmp(neg_nan_buffer, "-nan(ind)", 9)); -} -#endif - template void test_min_buffer_size() { - #ifdef BOOST_MSVC + #if defined(_WIN32) std::uniform_real_distribution dist((std::numeric_limits::min)(), (std::numeric_limits::max)()); #else std::uniform_real_distribution dist((std::numeric_limits::lowest)(), (std::numeric_limits::max)()); @@ -132,39 +87,6 @@ void test_min_buffer_size() } } -#ifdef BOOST_CHARCONV_HAS_FLOAT128 -template <> -void test_min_buffer_size<__float128>() -{ - boost::random::uniform_real_distribution<__float128> dist(-1e4000Q, 1e4000Q); - - // No guarantees are made for fixed, especially in this domain - auto formats = {boost::charconv::chars_format::hex, - boost::charconv::chars_format::scientific, - boost::charconv::chars_format::general}; - - for (const auto format : formats) - { - for (std::size_t i = 0; i < N; ++i) - { - char buffer[boost::charconv::limits<__float128>::max_chars10]; - const auto value = dist(rng); - - if (isinfq(value) || isnanq(value)) - { - continue; - } - - auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), value, format); - if (!BOOST_TEST(r)) - { - std::cerr << "Overflow for: " << value << std::endl; // LCOV_EXCL_LINE - } - } - } -} -#endif - int main() { test_non_finite(); @@ -175,10 +97,5 @@ int main() test_min_buffer_size(); test_min_buffer_size(); - #ifdef BOOST_CHARCONV_HAS_FLOAT128 - test_non_finite<__float128>(); - test_min_buffer_size<__float128>(); - #endif - return boost::report_errors(); } diff --git a/test/github_issue_152_float128.cpp b/test/github_issue_152_float128.cpp new file mode 100644 index 00000000..4eb22eb2 --- /dev/null +++ b/test/github_issue_152_float128.cpp @@ -0,0 +1,102 @@ +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#ifdef BOOST_CHARCONV_HAS_FLOAT128 + +std::ostream& operator<<( std::ostream& os, __float128 v ) +{ + char buffer[ 256 ] {}; + quadmath_snprintf(buffer, sizeof(buffer), "%Qg", v); + os << buffer; + return os; +} + +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr std::size_t N = 1024; +static std::mt19937_64 rng(42); + +void test_non_finite() +{ + constexpr std::array<__float128, 4> values = {{INFINITY, -INFINITY, NAN, -NAN}}; + + for (const auto val : values) + { + char buffer[2]; + auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), val); + BOOST_TEST(r.ec == std::errc::result_out_of_range); + } + + char inf_buffer[3]; + auto r_inf = boost::charconv::to_chars(inf_buffer, inf_buffer + 3, values[0]); + BOOST_TEST(r_inf); + BOOST_TEST(!std::memcmp(inf_buffer, "inf", 3)); + + char nan_buffer[3]; + auto r_nan = boost::charconv::to_chars(nan_buffer, nan_buffer + 3, values[2]); + BOOST_TEST(r_nan); + BOOST_TEST(!std::memcmp(nan_buffer, "nan", 3)); + + char neg_nan_buffer[9]; + auto r_neg_nan = boost::charconv::to_chars(neg_nan_buffer, neg_nan_buffer + 9, values[3]); + BOOST_TEST(r_neg_nan); + BOOST_TEST(!std::memcmp(neg_nan_buffer, "-nan(ind)", 9)); +} + +void test_min_buffer_size() +{ + boost::random::uniform_real_distribution<__float128> dist(-1e4000Q, 1e4000Q); + + // No guarantees are made for fixed, especially in this domain + auto formats = {boost::charconv::chars_format::hex, + boost::charconv::chars_format::scientific, + boost::charconv::chars_format::general}; + + for (const auto format : formats) + { + for (std::size_t i = 0; i < N; ++i) + { + char buffer[boost::charconv::limits<__float128>::max_chars10]; + const auto value = dist(rng); + + if (isinfq(value) || isnanq(value)) + { + continue; + } + + auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), value, format); + if (!BOOST_TEST(r)) + { + std::cerr << "Overflow for: " << value << std::endl; // LCOV_EXCL_LINE + } + } + } +} + +int main() +{ + test_non_finite(); + test_min_buffer_size(); + + return boost::report_errors(); +} + +#else + +int main() +{ + return 0; +} + +#endif From 4451f25d878e5a884a3226e3a48ef74f222c415e Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 7 Feb 2024 09:05:16 +0100 Subject: [PATCH 22/27] Remove boost random since it fails ASAN --- test/github_issue_152_float128.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/github_issue_152_float128.cpp b/test/github_issue_152_float128.cpp index 4eb22eb2..81faefe3 100644 --- a/test/github_issue_152_float128.cpp +++ b/test/github_issue_152_float128.cpp @@ -17,7 +17,6 @@ std::ostream& operator<<( std::ostream& os, __float128 v ) #include #include -#include #include #include #include @@ -56,8 +55,6 @@ void test_non_finite() void test_min_buffer_size() { - boost::random::uniform_real_distribution<__float128> dist(-1e4000Q, 1e4000Q); - // No guarantees are made for fixed, especially in this domain auto formats = {boost::charconv::chars_format::hex, boost::charconv::chars_format::scientific, @@ -68,7 +65,7 @@ void test_min_buffer_size() for (std::size_t i = 0; i < N; ++i) { char buffer[boost::charconv::limits<__float128>::max_chars10]; - const auto value = dist(rng); + const auto value = static_cast<__float128>(rng()); if (isinfq(value) || isnanq(value)) { From 4c2c3cf5a6492e5d0a71524faba9d19c24f6a656 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 7 Feb 2024 09:10:26 +0100 Subject: [PATCH 23/27] Increase precision of error message --- test/github_issue_152.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index 4ccb4f0a..438c141c 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -10,6 +10,7 @@ #include #include #include +#include constexpr std::size_t N = 1024; static std::mt19937_64 rng(42); @@ -81,7 +82,7 @@ void test_min_buffer_size() auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), value, format); if (!BOOST_TEST(r)) { - std::cerr << "Overflow for: " << value << std::endl; // LCOV_EXCL_LINE + std::cerr << std::setprecision(std::numeric_limits::max_digits10) << "Overflow for: " << value << std::endl; // LCOV_EXCL_LINE } } } From 1501c5a2ae6cab5d3364c7ca5b11026dee31eae2 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 7 Feb 2024 09:58:44 +0100 Subject: [PATCH 24/27] Add additional testing for MinGW failures --- test/github_issue_152.cpp | 45 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index 438c141c..a97e4efc 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -69,6 +69,7 @@ void test_min_buffer_size() for (const auto format : formats) { + int format_int = 0; for (std::size_t i = 0; i < N; ++i) { char buffer[boost::charconv::limits::max_chars10]; @@ -82,8 +83,48 @@ void test_min_buffer_size() auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), value, format); if (!BOOST_TEST(r)) { - std::cerr << std::setprecision(std::numeric_limits::max_digits10) << "Overflow for: " << value << std::endl; // LCOV_EXCL_LINE + // LCOV_EXCL_START + std::cerr << std::setprecision(std::numeric_limits::max_digits10) << "Overflow for: " << value + << "\nFormat: " << format_int + << "\nBuffer size: " << sizeof(buffer) << std::endl; + // LCOV_EXCL_STOP } + ++format_int; + } + } +} + +void test_failed_values() +{ + // No guarantees are made for fixed, especially in this domain + auto formats = {boost::charconv::chars_format::hex, + boost::charconv::chars_format::scientific, + boost::charconv::chars_format::general}; + + std::array failed_values = {{6.93880126833169422964e+4931L, 9.14517491001980558957e+4931L}}; + + for (const auto format : formats) + { + int format_int = 0; + for (const auto value : failed_values) + { + char buffer[boost::charconv::limits::max_chars10]; + + if (!std::isnormal(value)) + { + continue; + } + + auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer), value, format); + if (!BOOST_TEST(r)) + { + // LCOV_EXCL_START + std::cerr << std::setprecision(std::numeric_limits::max_digits10) << "Overflow for: " << value + << "\nFormat: " << format_int + << "\nBuffer size: " << sizeof(buffer) << std::endl; + // LCOV_EXCL_STOP + } + ++format_int; } } } @@ -98,5 +139,7 @@ int main() test_min_buffer_size(); test_min_buffer_size(); + test_failed_values(); + return boost::report_errors(); } From 75a78f0395d46c04a464f3017697590cd612ea56 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 7 Feb 2024 10:25:03 +0100 Subject: [PATCH 25/27] Skip long double hex testing since there is related outstanding issue --- test/github_issue_152.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index a97e4efc..a2332e13 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -64,12 +64,18 @@ void test_min_buffer_size() // No guarantees are made for fixed, especially in this domain auto formats = {boost::charconv::chars_format::hex, - boost::charconv::chars_format::scientific, - boost::charconv::chars_format::general}; + boost::charconv::chars_format::scientific, + boost::charconv::chars_format::general}; + int format_int = 0; for (const auto format : formats) { - int format_int = 0; + // TODO(mborland): Remove this once https://github.com/boostorg/charconv/issues/154 is fixed + if (std::is_same::value && format_int == 0) + { + continue; + } + for (std::size_t i = 0; i < N; ++i) { char buffer[boost::charconv::limits::max_chars10]; @@ -89,23 +95,23 @@ void test_min_buffer_size() << "\nBuffer size: " << sizeof(buffer) << std::endl; // LCOV_EXCL_STOP } - ++format_int; } + ++format_int; } } void test_failed_values() { // No guarantees are made for fixed, especially in this domain - auto formats = {boost::charconv::chars_format::hex, - boost::charconv::chars_format::scientific, - boost::charconv::chars_format::general}; + // TODO(mborland): Add hex once https://github.com/boostorg/charconv/issues/154 is fixed + auto formats = {boost::charconv::chars_format::scientific, + boost::charconv::chars_format::general}; std::array failed_values = {{6.93880126833169422964e+4931L, 9.14517491001980558957e+4931L}}; + int format_int = 0; for (const auto format : formats) { - int format_int = 0; for (const auto value : failed_values) { char buffer[boost::charconv::limits::max_chars10]; @@ -124,8 +130,8 @@ void test_failed_values() << "\nBuffer size: " << sizeof(buffer) << std::endl; // LCOV_EXCL_STOP } - ++format_int; } + ++format_int; } } From 4d6661546078e6390c97dce62bb09bd9bcba1a0a Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 7 Feb 2024 11:01:57 +0100 Subject: [PATCH 26/27] Fix testing for platforms with 64-bit long double --- test/github_issue_152.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index a2332e13..a95c5a12 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -100,6 +100,7 @@ void test_min_buffer_size() } } +#if BOOST_CHARCONV_LDBL_BITS > 64 void test_failed_values() { // No guarantees are made for fixed, especially in this domain @@ -134,6 +135,7 @@ void test_failed_values() ++format_int; } } +#endif int main() { @@ -145,7 +147,9 @@ int main() test_min_buffer_size(); test_min_buffer_size(); + #if BOOST_CHARCONV_LDBL_BITS > 64 test_failed_values(); + #endif return boost::report_errors(); } From 29be488929ebefb2284d97f194ef60ec71be6852 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 7 Feb 2024 11:13:55 +0100 Subject: [PATCH 27/27] Ignore MSVC warning C4127 --- test/github_issue_152.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/github_issue_152.cpp b/test/github_issue_152.cpp index a95c5a12..d6a52505 100644 --- a/test/github_issue_152.cpp +++ b/test/github_issue_152.cpp @@ -53,6 +53,11 @@ void test_non_finite() BOOST_TEST(!std::memcmp(snan_buffer, "nan(snan)", 9)); }; +#ifdef BOOST_MSVC +# pragma warning(push) +# pragma warning(disable: 4127) +#endif + template void test_min_buffer_size() { @@ -100,6 +105,10 @@ void test_min_buffer_size() } } +#ifdef BOOST_MSVC +# pragma warning(pop) +#endif + #if BOOST_CHARCONV_LDBL_BITS > 64 void test_failed_values() {