diff --git a/include/boost/decimal/charconv.hpp b/include/boost/decimal/charconv.hpp index e857f72b1..1ea4e99f7 100644 --- a/include/boost/decimal/charconv.hpp +++ b/include/boost/decimal/charconv.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #ifndef BOOST_DECIMAL_BUILD_MODULE #include @@ -81,13 +82,26 @@ constexpr auto from_chars_general_impl(const char* first, const char* last, Targ { if (r.ec == std::errc::not_supported) { - if (significand) + using resultant_sig_type = typename TargetDecimalType::significand_type; + + resultant_sig_type payload_value {}; + if (significand < std::numeric_limits::max()) + { + payload_value = static_cast(significand); + } + + if (expval > 0) { - value = std::numeric_limits::signaling_NaN(); + value = write_payload(payload_value); } else { - value = std::numeric_limits::quiet_NaN(); + value = write_payload(payload_value); + } + + if (sign) + { + value = -value; } r.ec = std::errc(); diff --git a/include/boost/decimal/decimal_fast128_t.hpp b/include/boost/decimal/decimal_fast128_t.hpp index ecd94ea54..bb7e227a8 100644 --- a/include/boost/decimal/decimal_fast128_t.hpp +++ b/include/boost/decimal/decimal_fast128_t.hpp @@ -74,7 +74,7 @@ template constexpr auto to_chars_cohort_preserving_scientific(char* first, char* last, const TargetDecimalType& value) noexcept -> to_chars_result; template -constexpr auto nan_impl(const char* arg) noexcept +constexpr auto write_payload(typename TargetDecimalType::significand_type payload_value) BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType); } // namespace detail @@ -196,7 +196,7 @@ BOOST_DECIMAL_EXPORT class decimal_fast128_t final friend constexpr auto detail::to_chars_cohort_preserving_scientific(char* first, char* last, const TargetDecimalType& value) noexcept -> to_chars_result; template - friend constexpr auto detail::nan_impl(const char* arg) noexcept + friend constexpr auto detail::write_payload(typename TargetDecimalType::significand_type payload_value) BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType); template diff --git a/include/boost/decimal/decimal_fast32_t.hpp b/include/boost/decimal/decimal_fast32_t.hpp index 11de1002d..3ab0e4773 100644 --- a/include/boost/decimal/decimal_fast32_t.hpp +++ b/include/boost/decimal/decimal_fast32_t.hpp @@ -72,7 +72,7 @@ template constexpr auto d32_fma_impl(T x, T y, T z) noexcept -> T; template -constexpr auto nan_impl(const char* arg) noexcept +constexpr auto write_payload(typename TargetDecimalType::significand_type payload_value) BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType); } // namespace detail @@ -196,7 +196,7 @@ BOOST_DECIMAL_EXPORT class decimal_fast32_t final friend constexpr auto detail::generic_div_impl(const T& lhs, const T& rhs) noexcept -> DecimalType; template - friend constexpr auto detail::nan_impl(const char* arg) noexcept + friend constexpr auto detail::write_payload(typename TargetDecimalType::significand_type payload_value) BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType); template diff --git a/include/boost/decimal/decimal_fast64_t.hpp b/include/boost/decimal/decimal_fast64_t.hpp index a7ba82332..4547c7b65 100644 --- a/include/boost/decimal/decimal_fast64_t.hpp +++ b/include/boost/decimal/decimal_fast64_t.hpp @@ -74,7 +74,7 @@ template constexpr auto d64_fma_impl(T x, T y, T z) noexcept -> T; template -constexpr auto nan_impl(const char* arg) noexcept +constexpr auto write_payload(typename TargetDecimalType::significand_type payload_value) BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType); } // namespace detail @@ -203,7 +203,7 @@ BOOST_DECIMAL_EXPORT class decimal_fast64_t final friend constexpr auto detail::d64_fma_impl(T x, T y, T z) noexcept -> T; template - friend constexpr auto detail::nan_impl(const char* arg) noexcept + friend constexpr auto detail::write_payload(typename TargetDecimalType::significand_type payload_value) BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType); template diff --git a/include/boost/decimal/detail/cmath/nan.hpp b/include/boost/decimal/detail/cmath/nan.hpp index 7eed1fbe8..5379ed27b 100644 --- a/include/boost/decimal/detail/cmath/nan.hpp +++ b/include/boost/decimal/detail/cmath/nan.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #ifndef BOOST_DECIMAL_BUILD_MODULE @@ -33,23 +34,16 @@ constexpr auto nan_impl(const char* arg) noexcept constexpr TargetDecimalType nan_type {is_snan ? std::numeric_limits::signaling_NaN() : std::numeric_limits::quiet_NaN()}; - constexpr std::uint32_t significand_field_bits {decimal_val_v < 64 ? 23U : - decimal_val_v < 128 ? 53U : 110U}; - - constexpr sig_type max_payload_value {(static_cast(1) << significand_field_bits) - 1U}; - sig_type payload_value {}; const auto r {from_chars_integer_impl(arg, arg + detail::strlen(arg), payload_value, 10)}; - TargetDecimalType return_value {nan_type}; - if (!r || payload_value > max_payload_value) + if (!r) { - return return_value; + return nan_type; } else { - return_value.significand_ |= payload_value; - return return_value; + return write_payload(payload_value); } } @@ -62,23 +56,16 @@ constexpr auto nan_impl(const char* arg) noexcept constexpr TargetDecimalType nan_type {is_snan ? std::numeric_limits::signaling_NaN() : std::numeric_limits::quiet_NaN()}; - constexpr std::uint32_t significand_field_bits {sizeof(TargetDecimalType) == sizeof(std::uint32_t) ? 23U : - sizeof(TargetDecimalType) == sizeof(std::uint64_t) ? 53U : 110U}; - - constexpr sig_type max_payload_value {(static_cast(1) << significand_field_bits) - 1U}; - constexpr TargetDecimalType zero {}; - constexpr TargetDecimalType zero_bits {zero ^ zero}; - sig_type payload_value {}; const auto r {from_chars_integer_impl(arg, arg + detail::strlen(arg), payload_value, 10)}; - if (!r || payload_value > max_payload_value) + if (!r) { return nan_type; } else { - return (zero_bits | payload_value) | nan_type; + return write_payload(payload_value); } } diff --git a/include/boost/decimal/detail/parser.hpp b/include/boost/decimal/detail/parser.hpp index a3affb537..e383044ad 100644 --- a/include/boost/decimal/detail/parser.hpp +++ b/include/boost/decimal/detail/parser.hpp @@ -98,11 +98,15 @@ constexpr auto parser(const char* first, const char* last, bool& sign, Unsigned_ sign = false; } + constexpr std::size_t significand_buffer_size = std::numeric_limits::digits10 ; + char significand_buffer[significand_buffer_size] {}; + // Handle non-finite values // Stl allows for string like "iNf" to return inf // // This is nested ifs rather than a big one-liner to ensure that once we hit an invalid character // or an end of buffer we return the correct value of next + bool signaling {}; if (next != last && (*next == 'i' || *next == 'I')) { ++next; @@ -112,14 +116,21 @@ constexpr auto parser(const char* first, const char* last, bool& sign, Unsigned_ if (next != last && (*next == 'f' || *next == 'F')) { ++next; - significand = 0; + exponent = 0; return {next, std::errc::value_too_large}; } } return {first, std::errc::invalid_argument}; } - else if (next != last && (*next == 'n' || *next == 'N')) + + if (next != last && (*next == 's' || *next == 'S')) + { + ++next; + signaling = true; + } + + if (next != last && (*next == 'n' || *next == 'N')) { ++next; if (next != last && (*next == 'a' || *next == 'A')) @@ -128,56 +139,97 @@ constexpr auto parser(const char* first, const char* last, bool& sign, Unsigned_ if (next != last && (*next == 'n' || *next == 'N')) { ++next; - if (next != last && (*next == '(')) + if (next != last) { const auto current_pos {next}; - ++next; + + bool any_valid_char {false}; + bool has_opening_brace {false}; + if (*next == '(') + { + ++next; + has_opening_brace = true; + } // Handle nan(SNAN) if ((last - next) >= 4 && (*next == 's' || *next == 'S') && (*(next + 1) == 'n' || *(next + 1) == 'N') && (*(next + 2) == 'a' || *(next + 2) == 'A') && (*(next + 3) == 'n' || *(next + 3) == 'N')) { - next += 3; - significand = 1; + next += 4; + signaling = true; + any_valid_char = true; } // Handle Nan(IND) else if ((last - next) >= 3 && (*next == 'i' || *next == 'I') && (*(next + 1) == 'n' || *(next + 1) == 'N') && (*(next + 2) == 'd' || *(next + 2) == 'D')) { - next += 2; - significand = 0; + next += 3; + sign = true; + any_valid_char = true; } - // Arbitrary payload - bool valid_payload {false}; + // Arbitrary numerical payload + bool has_numerical_payload {false}; + auto significand_buffer_first {significand_buffer}; + std::size_t significand_characters {}; while (next != last && (*next != ')')) { - if (is_payload_char(*next)) + if (significand_characters < significand_buffer_size && is_integer_char(*next)) { - ++next; - valid_payload = true; + ++significand_characters; + *significand_buffer_first++ = *next++; + any_valid_char = true; + has_numerical_payload = true; } else { - valid_payload = false; + // End of valid payload even if there are more characters + // e.g. SNAN42JUNK stops at J break; } } - if (valid_payload) + // Non-numerical payload still needs to be parsed + // e.g. nan(PAYLOAD) + if (!has_numerical_payload && has_opening_brace) + { + while (next != last && (*next != ')')) + { + if (is_payload_char(*next)) + { + any_valid_char = true; + ++next; + } + else + { + break; + } + } + } + + if (next != last && any_valid_char) { + // One past the end if we need to ++next; } - else + + if (significand_characters != 0) + { + from_chars_dispatch(significand_buffer, significand_buffer + significand_characters, significand, 10); + } + + if (!any_valid_char) { + // If we have nan(..BAD..) we should point to ( next = current_pos; } + exponent = static_cast(signaling); return {next, std::errc::not_supported}; } else { - significand = 0; + exponent = static_cast(signaling); return {next, std::errc::not_supported}; } } @@ -204,8 +256,6 @@ constexpr auto parser(const char* first, const char* last, bool& sign, Unsigned_ } // Next we get the significand - constexpr std::size_t significand_buffer_size = std::numeric_limits::digits10 ; - char significand_buffer[significand_buffer_size] {}; std::size_t i = 0; std::size_t dot_position = 0; Integer extra_zeros = 0; diff --git a/include/boost/decimal/detail/write_payload.hpp b/include/boost/decimal/detail/write_payload.hpp new file mode 100644 index 000000000..b289bb6a0 --- /dev/null +++ b/include/boost/decimal/detail/write_payload.hpp @@ -0,0 +1,69 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_DECIMAL_DETAIL_WRITE_PAYLOAD_HPP +#define BOOST_DECIMAL_DETAIL_WRITE_PAYLOAD_HPP + +#include +#include +#include + +namespace boost { +namespace decimal { +namespace detail { + +template +constexpr auto write_payload(typename TargetDecimalType::significand_type payload_value) + BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType) +{ + using sig_type = typename TargetDecimalType::significand_type; + + constexpr TargetDecimalType nan_type {is_snan ? std::numeric_limits::signaling_NaN() : + std::numeric_limits::quiet_NaN()}; + + constexpr std::uint32_t significand_field_bits {decimal_val_v < 64 ? 23U : + decimal_val_v < 128 ? 53U : 110U}; + + constexpr sig_type max_payload_value {(static_cast(1) << significand_field_bits) - 1U}; + + TargetDecimalType return_value {nan_type}; + if (payload_value < max_payload_value) + { + return_value.significand_ |= payload_value; + } + + return return_value; +} + +template +constexpr auto write_payload(typename TargetDecimalType::significand_type payload_value) + BOOST_DECIMAL_REQUIRES(detail::is_ieee_type_v, TargetDecimalType) +{ + using sig_type = typename TargetDecimalType::significand_type; + + constexpr TargetDecimalType nan_type {is_snan ? std::numeric_limits::signaling_NaN() : + std::numeric_limits::quiet_NaN()}; + + constexpr std::uint32_t significand_field_bits {decimal_val_v < 64 ? 23U : + decimal_val_v < 128 ? 53U : 110U}; + + constexpr sig_type max_payload_value {(static_cast(1) << significand_field_bits) - 1U}; + + constexpr TargetDecimalType zero {}; + constexpr TargetDecimalType zero_bits {zero ^ zero}; + + TargetDecimalType return_value {nan_type}; + if (payload_value < max_payload_value) + { + return_value = (zero_bits | payload_value) | nan_type; + } + + return return_value; +} + +} // namespace detail +} // namespace decimal +} // namespace boost + +#endif // BOOST_DECIMAL_DETAIL_WRITE_PAYLOAD_HPP diff --git a/test/Jamfile b/test/Jamfile index 591c0cc7b..f3f84151a 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -147,6 +147,7 @@ run test_format_fmtlib.cpp ; run-fail test_fprintf.cpp ; run test_frexp_ldexp.cpp ; run test_from_chars.cpp /boost/charconv//boost_charconv ; +run test_from_chars_nan_payloads.cpp ; run test_git_issue_266.cpp ; run test_git_issue_271.cpp ; run test_hash.cpp ; diff --git a/test/github_issue_1054.cpp b/test/github_issue_1054.cpp index 43714d68f..b16222a88 100644 --- a/test/github_issue_1054.cpp +++ b/test/github_issue_1054.cpp @@ -77,7 +77,7 @@ void endptr_using_strtod(const std::string& str) template void check_endptr() { - // endptr should points to the character after "inf" + // endptr should point to the character after "inf" endptr_using_from_chars("info"); endptr_using_strtod("info"); @@ -85,22 +85,26 @@ void check_endptr() endptr_using_from_chars("inch"); endptr_using_strtod("inch"); - // endptr should points to the character after "nan" + // endptr should point to the character after "nan" endptr_using_from_chars("nano"); endptr_using_strtod("nano"); - // endptr should points to the beginning of the string + // endptr should point to the beginning of the string endptr_using_from_chars("name"); endptr_using_strtod("name"); #ifdef __APPLE__ // Darwin's strtod works incorrectly for nan with payload, so skip the test. #else - // endptr should points to the character after "nan(PAYLOAD)" + // endptr should point to the character after "nan(PAYLOAD)" endptr_using_from_chars("nan(PAYLOAD)"); endptr_using_strtod("nan(PAYLOAD)"); - // endptr should points to the character after "nan" + // endptr should point to the character after "nan(123)" + endptr_using_from_chars("nan(123)"); + endptr_using_strtod("nan(123)"); + + // endptr should point to the character after "nan" endptr_using_from_chars("nan(..BAD..)"); endptr_using_strtod("nan(..BAD..)"); #endif diff --git a/test/test_from_chars_nan_payloads.cpp b/test/test_from_chars_nan_payloads.cpp new file mode 100644 index 000000000..a18fde675 --- /dev/null +++ b/test/test_from_chars_nan_payloads.cpp @@ -0,0 +1,81 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include + +using namespace boost::decimal; + +template +void test_signaling() +{ + const std::array lhs_values { "NAN(SNAN)", "SNAN", "SNAN(42)", "SNAN42", "+sNaN400JUNK", "NAN(SNAN42)" }; + const std::array rhs_values { "nan(snan)", "snan", "snan(43)", "snan43", "+SnAn410JUNK", "nan(snan4000)" }; + + for (std::size_t i {}; i < lhs_values.size(); ++i) + { + const T lhs {lhs_values[i]}; + const T rhs {rhs_values[i]}; + + BOOST_TEST(isnan(lhs)); + BOOST_TEST(isnan(rhs)); + + BOOST_TEST(issignaling(lhs)); + BOOST_TEST(issignaling(rhs)); + + if (i >= 2) + { + // A nan with a higher payload compares higher + BOOST_TEST(comparetotal(lhs, rhs)); + } + } +} + +template +void test_quiet() +{ + const std::array lhs_values { "nan(IND)", "NAN", "NAN(42)", "NAN42", "NaN400JUNK", "-nan(IND4200)" }; + const std::array rhs_values { "nan(ind)", "nan", "nan(43)", "nan43", "nAn410junk", "-nan(ind4000)" }; + + for (std::size_t i {}; i < lhs_values.size(); ++i) + { + const T lhs {lhs_values[i]}; + const T rhs {rhs_values[i]}; + + BOOST_TEST(isnan(lhs)); + BOOST_TEST(isnan(rhs)); + + BOOST_TEST(!issignaling(lhs)); + BOOST_TEST(!issignaling(rhs)); + + if (i >= 2) + { + // A nan with a higher payload compares higher + BOOST_TEST(comparetotal(lhs, rhs)); + } + } +} + +int main() +{ + test_signaling(); + test_signaling(); + test_signaling(); + + test_signaling(); + test_signaling(); + test_signaling(); + + test_quiet(); + test_quiet(); + test_quiet(); + + test_quiet(); + test_quiet(); + test_quiet(); + + return boost::report_errors(); +}