diff --git a/include/boost/charconv/detail/from_chars_float_impl.hpp b/include/boost/charconv/detail/from_chars_float_impl.hpp index 7277fb51..620f326f 100644 --- a/include/boost/charconv/detail/from_chars_float_impl.hpp +++ b/include/boost/charconv/detail/from_chars_float_impl.hpp @@ -129,6 +129,26 @@ from_chars_result from_chars_float_impl(const char* first, const char* last, T& std::int64_t exponent {}; auto r = boost::charconv::detail::parser(first, last, sign, significand, exponent, fmt); + if (r.ec == std::errc::value_too_large) + { + r.ec = std::errc(); + value = sign ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); + return r; + } + else if (r.ec == std::errc::not_supported) + { + r.ec = std::errc(); + if (significand == 0) + { + value = sign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); + } + else + { + value = sign ? -std::numeric_limits::signaling_NaN() : std::numeric_limits::signaling_NaN(); + } + + return r; + } if (r.ec != std::errc()) { return r; diff --git a/include/boost/charconv/detail/generate_nan.hpp b/include/boost/charconv/detail/generate_nan.hpp new file mode 100644 index 00000000..e527adec --- /dev/null +++ b/include/boost/charconv/detail/generate_nan.hpp @@ -0,0 +1,56 @@ +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_GENERATE_NAN_HPP +#define BOOST_GENERATE_NAN_HPP + +#include +#include + +#ifdef BOOST_CHARCONV_HAS_FLOAT128 + +namespace boost { +namespace charconv { +namespace detail { + +struct words +{ +#if BOOST_CHARCONV_ENDIAN_LITTLE_BYTE + std::uint64_t lo; + std::uint64_t hi; +#else + std::uint64_t hi; + std::uint64_t lo; +#endif +}; + +inline __float128 nans BOOST_PREVENT_MACRO_SUBSTITUTION () noexcept +{ + words bits; + bits.hi = UINT64_C(0x7FFF400000000000); + bits.lo = UINT64_C(0); + + __float128 return_val; + std::memcpy(&return_val, &bits, sizeof(__float128)); + return return_val; +} + +inline __float128 nanq BOOST_PREVENT_MACRO_SUBSTITUTION () noexcept +{ + words bits; + bits.hi = UINT64_C(0x7FFF800000000000); + bits.lo = UINT64_C(0); + + __float128 return_val; + std::memcpy(&return_val, &bits, sizeof(__float128)); + return return_val; +} + +} //namespace detail +} //namespace charconv +} //namespace boost + +#endif + +#endif //BOOST_GENERATE_NAN_HPP diff --git a/include/boost/charconv/detail/parser.hpp b/include/boost/charconv/detail/parser.hpp index 3ddea1b1..acb3a878 100644 --- a/include/boost/charconv/detail/parser.hpp +++ b/include/boost/charconv/detail/parser.hpp @@ -88,6 +88,60 @@ inline from_chars_result parser(const char* first, const char* last, bool& sign, sign = false; } + // 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 + if (*next == 'i' || *next == 'I') + { + ++next; + if (*next == 'n' || *next == 'N') + { + ++next; + if (*next == 'f' || *next == 'F') + { + significand = 0; + return {next, std::errc::value_too_large}; + } + } + + return {next, std::errc::invalid_argument}; + } + else if (*next == 'n' || *next == 'N') + { + ++next; + if (*next == 'a' || *next == 'A') + { + ++next; + if (*next == 'n' || *next == 'N') + { + ++next; + if (*next == '(') + { + ++next; + if (*next == 's' || *next == 'S') + { + significand = 1; + return {next, std::errc::not_supported}; + } + else if (*next == 'i' || *next == 'I') + { + significand = 0; + return {next, std::errc::not_supported}; + } + } + else + { + significand = 0; + return {next, std::errc::not_supported}; + } + } + } + + return {next, std::errc::invalid_argument}; + } + // Ignore leading zeros (e.g. 00005 or -002.3e+5) while (*next == '0' && next != last) { diff --git a/src/from_chars.cpp b/src/from_chars.cpp index b99ac674..23460f28 100644 --- a/src/from_chars.cpp +++ b/src/from_chars.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -97,7 +98,47 @@ boost::charconv::from_chars_result boost::charconv::from_chars(const char* first #endif auto r = boost::charconv::detail::parser(first, last, sign, significand, exponent, fmt); - if (r.ec != std::errc()) + if (r.ec == std::errc::value_too_large) + { + r.ec = std::errc(); + + #if BOOST_CHARCONV_HAS_BUILTIN(__builtin_inf) + value = sign ? -static_cast<__float128>(__builtin_inf()) : static_cast<__float128>(__builtin_inf()); + #else // Conversion from HUGE_VALL should work + value = sign ? -static_cast<__float128>(HUGE_VALL) : static_cast<__float128>(HUGE_VALL); + #endif + + return r; + } + else if (r.ec == std::errc::not_supported) + { + r.ec = std::errc(); + if (significand == 0) + { + #if BOOST_CHARCONV_HAS_BUILTIN(__builtin_nanq) + value = sign ? -static_cast<__float128>(__builtin_nanq("")) : static_cast<__float128>(__builtin_nanq("")); + #elif BOOST_CHARCONV_HAS_BUILTIN(__nanq) + value = sign ? -static_cast<__float128>(__nanq("")) : static_cast<__float128>(__nanq("")); + #else + value = boost::charconv::detail::nanq(); + value = sign ? -value : value; + #endif + } + else + { + #if BOOST_CHARCONV_HAS_BUILTIN(__builtin_nansq) + value = sign ? -static_cast<__float128>(__builtin_nansq("")) : static_cast<__float128>(__builtin_nansq("")); + #elif BOOST_CHARCONV_HAS_BUILTIN(__nansq) + value = sign ? -static_cast<__float128>(__nansq("")) : static_cast<__float128>(__nansq("")); + #else + value = boost::charconv::detail::nans(); + value = sign ? -value : value; + #endif + } + + return r; + } + else if (r.ec != std::errc()) { return r; } @@ -224,7 +265,27 @@ boost::charconv::from_chars_result boost::charconv::from_chars(const char* first #endif auto r = boost::charconv::detail::parser(first, last, sign, significand, exponent, fmt); - if (r.ec != std::errc()) + if (r.ec == std::errc::value_too_large) + { + r.ec = std::errc(); + value = sign ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); + return r; + } + else if (r.ec == std::errc::not_supported) + { + r.ec = std::errc(); + if (significand == 0) + { + value = sign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); + } + else + { + value = sign ? -std::numeric_limits::signaling_NaN() : std::numeric_limits::signaling_NaN(); + } + + return r; + } + else if (r.ec != std::errc()) { return r; } diff --git a/test/from_chars_float.cpp b/test/from_chars_float.cpp index c7e0dc96..fcae0af4 100644 --- a/test/from_chars_float.cpp +++ b/test/from_chars_float.cpp @@ -51,6 +51,36 @@ inline void spot_check(T expected_value, const std::string& buffer, boost::charc spot_value(buffer, expected_value, fmt); } +template +void spot_check_nan(const std::string& buffer, boost::charconv::chars_format fmt = boost::charconv::chars_format::general) +{ + T v {}; + auto r = boost::charconv::from_chars(buffer.c_str(), buffer.c_str() + buffer.size(), v, fmt); + if (!(BOOST_TEST(std::isnan(v)) && BOOST_TEST(r))) + { + std::cerr << "Test failure for: " << buffer << " got: " << v << std::endl; // LCOV_EXCL_LINE + } +} + +template +void spot_check_inf(const std::string& buffer, boost::charconv::chars_format fmt = boost::charconv::chars_format::general) +{ + T v {}; + auto r = boost::charconv::from_chars(buffer.c_str(), buffer.c_str() + buffer.size(), v, fmt); + if (!(BOOST_TEST(std::isinf(v)) && BOOST_TEST(r))) + { + std::cerr << "Test failure for: " << buffer << " got: " << v << std::endl; // LCOV_EXCL_LINE + } +} + +template +void spot_check_bad_non_finite(const std::string& buffer, boost::charconv::chars_format fmt) +{ + T v = static_cast(5.0L); + auto r = boost::charconv::from_chars(buffer.c_str(), buffer.c_str() + buffer.size(), v, fmt); + BOOST_TEST(r.ec == std::errc::invalid_argument); +} + void fc (const std::string& s) { char* str_end; @@ -1817,5 +1847,54 @@ int main() spot_check(170.0e-00, "170.0e+00", boost::charconv::chars_format::general); spot_check(170.0000e-00, "170.0000e+00", boost::charconv::chars_format::general); + // https://github.com/cppalliance/charconv/issues/114 + auto fmts = {boost::charconv::chars_format::general, boost::charconv::chars_format::scientific, + boost::charconv::chars_format::fixed ,boost::charconv::chars_format::hex}; + for (const auto fmt : fmts) + { + spot_check_nan("nan", fmt); + spot_check_nan("-nan", fmt); + spot_check_nan("nan", fmt); + spot_check_nan("-nan", fmt); + spot_check_nan("nan", fmt); + spot_check_nan("-nan", fmt); + + spot_check_inf("inf", fmt); + spot_check_inf("-inf", fmt); + spot_check_inf("inf", fmt); + spot_check_inf("-inf", fmt); + spot_check_inf("inf", fmt); + spot_check_inf("-inf", fmt); + + spot_check_nan("NAN", fmt); + spot_check_nan("-NAN", fmt); + spot_check_nan("NAN", fmt); + spot_check_nan("-NAN", fmt); + spot_check_nan("NAN", fmt); + spot_check_nan("-NAN", fmt); + + spot_check_inf("INF", fmt); + spot_check_inf("-INF", fmt); + spot_check_inf("INF", fmt); + spot_check_inf("-INF", fmt); + spot_check_inf("INF", fmt); + spot_check_inf("-INF", fmt); + + spot_check_nan("nan(snan)", fmt); + spot_check_nan("-nan(snan)", fmt); + spot_check_nan("nan(snan)", fmt); + spot_check_nan("-nan(snan)", fmt); + spot_check_nan("nan(snan)", fmt); + spot_check_nan("-nan(snan)", fmt); + + spot_check_nan("-nan(ind)", fmt); + spot_check_nan("-nan(ind)", fmt); + spot_check_nan("-nan(ind)", fmt); + + spot_check_bad_non_finite("na7", fmt); + spot_check_bad_non_finite("na", fmt); + spot_check_bad_non_finite("in", fmt); + } + return boost::report_errors(); } diff --git a/test/test_float128.cpp b/test/test_float128.cpp index 5407c38c..a1312778 100644 --- a/test/test_float128.cpp +++ b/test/test_float128.cpp @@ -85,6 +85,7 @@ std::ostream& operator<<( std::ostream& os, boost::int128_type v ) #include #include #include +#include #include #include #include @@ -541,8 +542,48 @@ void random_roundtrip(boost::charconv::chars_format fmt = boost::charconv::chars #endif // BOOST_CHARCONV_HAS_STDFLOAT128 +void spot_check_nan(const std::string& buffer, boost::charconv::chars_format fmt) +{ + __float128 v {}; + auto r = boost::charconv::from_chars(buffer.c_str(), buffer.c_str() + buffer.size(), v, fmt); + if (!(BOOST_TEST(isnanq(v)) && BOOST_TEST(r))) + { + std::cerr << "Test failure for: " << buffer << " got: " << v << std::endl; // LCOV_EXCL_LINE + } +} + +void spot_check_inf(const std::string& buffer, boost::charconv::chars_format fmt) +{ + __float128 v {}; + auto r = boost::charconv::from_chars(buffer.c_str(), buffer.c_str() + buffer.size(), v, fmt); + if (!(BOOST_TEST(isinfq(v)) && BOOST_TEST(r))) + { + std::cerr << "Test failure for: " << buffer << " got: " << v << std::endl; // LCOV_EXCL_LINE + } +} + +#if defined(__GNUC__) && __GNUC__ < 9 && __GNUC__ >= 5 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-conversion" +#endif + +void test_nanq() +{ + BOOST_TEST(isnanq(boost::charconv::detail::nanq())) && BOOST_TEST(!issignaling(boost::charconv::detail::nanq())); +} + +void test_nans() +{ + BOOST_TEST(isnanq(boost::charconv::detail::nans())) && BOOST_TEST(issignaling(boost::charconv::detail::nans())); +} + +#if defined(__GNUC__) && __GNUC__ < 9 && __GNUC__ >= 5 +#pragma GCC diagnostic pop +#endif + int main() { + /* #if BOOST_CHARCONV_LDBL_BITS == 128 test_signaling_nan(); @@ -761,6 +802,22 @@ int main() } #endif +*/ + spot_check_nan("nan", boost::charconv::chars_format::general); + spot_check_nan("-nan", boost::charconv::chars_format::general); + spot_check_inf("inf", boost::charconv::chars_format::general); + spot_check_inf("-inf", boost::charconv::chars_format::general); + spot_check_nan("NAN", boost::charconv::chars_format::general); + spot_check_nan("-NAN", boost::charconv::chars_format::general); + spot_check_inf("INF", boost::charconv::chars_format::general); + spot_check_inf("-INF", boost::charconv::chars_format::general); + spot_check_nan("nan(snan)", boost::charconv::chars_format::general); + spot_check_nan("-nan(snan)", boost::charconv::chars_format::general); + + test_nanq(); + #if defined(__GNUC__) && __GNUC__ >= 6 + test_nans(); + #endif return boost::report_errors(); }