diff --git a/BUILD.bazel b/BUILD.bazel index de0ff7145..020816044 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -21,6 +21,7 @@ cc_library( "include/nlohmann/adl_serializer.hpp", "include/nlohmann/byte_container_with_subtype.hpp", "include/nlohmann/detail/abi_macros.hpp", + "include/nlohmann/detail/conversions/from_chars.hpp", "include/nlohmann/detail/conversions/from_json.hpp", "include/nlohmann/detail/conversions/to_chars.hpp", "include/nlohmann/detail/conversions/to_json.hpp", diff --git a/include/nlohmann/detail/conversions/from_chars.hpp b/include/nlohmann/detail/conversions/from_chars.hpp new file mode 100644 index 000000000..dd490f049 --- /dev/null +++ b/include/nlohmann/detail/conversions/from_chars.hpp @@ -0,0 +1,369 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#if JSON_HAS_FROM_CHARS + #include // find, find_if + #include // from_chars, from_chars_result + #include // isnan +#else + #include // isspace + #include // ERANGE + #include // localeconv + #include // strto* + #include // enable_if, is_unsigned +#endif +#include // numeric_limits::[has_]infinity, quiet_NaN +#include // errc + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +constexpr char get_locale_independent_decimal_point() noexcept +{ + return '.'; +} + +#if JSON_HAS_FROM_CHARS + +////////////////////////////////////////////// +// Delegate to std::from_chars if supported // +////////////////////////////////////////////// + +/*! +@brief Number parsing implementation details + +A traits class template that allows users of nlohmann::detail::from_chars to +query details of the used implementation. + +@tparam T numeric type to parse, matching the `value` argument of from_chars +*/ +template +struct from_chars_traits +{ + /// whether the from_chars in unaffected by the current global C locale + static constexpr bool is_locale_independent = true; + + /// getter for the character used as decimal point by from_chars + static constexpr char(*get_decimal_point)() = &get_locale_independent_decimal_point; +}; + +using std::from_chars_result; + +/*! +@brief Parse integer/floating-point numbers + +Parses the string [first, last) into the numeric type T. + +This implementation merely delegates to `std::from_chars` with one noteworthy +difference: Different C++ standard library implementations do not agree on +whether `value` should be set in the out-of-range case for floating-point +numbers. Presently, libstdc++ leaves `value` unchanged whereas libc++ and MS STL +set `value` to +/-0 or +/-inf which allows its users to distinguish between the +various cases of over- and underflow. The C++ proposal [P4168][1] details this +discrepancy and advocates to standardize the latter behavior. + +Since we need to be able to distinguish absolute underflow (+/-0) from absolute +overflow (+/-inf), this implementation "corrects" the out-of-range behavior +accordingly by estimating the effective exponent through manual string parsing. + +[1]: https://isocpp.org/files/papers/P4168R0.html + +@tparam T numeric type to parse +@param[in] first points to the beginning of the parsed string (inclusive) +@param[in] end points to the end of the parsed string (exclusive) +@param[out] value references the variable to set the parsed number +@return structure consisting of `ptr` and `ec` such that: + - If the string starting at `first` matches a number, `ptr` points to + the address immediately after the last character that matches. + * If the matched number can be represented by `T`, `value` is set and + `ec` is value-initialized. + * If the matched number cannot be represented by `T`, `ec` is set to + `std::errc::result_out_of_range` and depending on the type of `T`: + + `value` is left unchanged for integral `T`, + + `value` is set to +/-0 or +/-inf for floating-point `T`. + - If the string starting at `first` doesn't match a number, `ptr` + points to `first`, `ec` is `std::errc::invalid_argument`, and `value` + is left unchanged. +*/ +template +JSON_HEDLEY_NON_NULL(1, 2) +inline from_chars_result from_chars(const char* first, const char* last, T& value) +{ + // No implementation of std::from_chars will set its value argument to NaN, + // so by using NaN as the initial value, we can tell if it changed. + T inner_value = std::numeric_limits::quiet_NaN(); + auto res = std::from_chars(first, last, inner_value); + + if constexpr (std::numeric_limits::has_infinity) + { + if (res.ec == std::errc::result_out_of_range) + { + if (std::isnan(inner_value)) // inner_value was not set + { + // [first, res.ptr) matches a valid floating-point number that's + // out-of-range and our std::from_chars did not set `value`, so we + // need to distinguish the type of under-/overflow ourselves. + const char* mantissa_begin = *first == '-' ? first + 1 : first; + const char* mantissa_end = std::find_if(mantissa_begin, res.ptr, [](char c) + { + return c == 'e' || c == 'E'; + }); + const char* decimal_point = std::find(mantissa_begin, mantissa_end, '.'); + const char* significant_digit = std::find_if(mantissa_begin, mantissa_end, [](char d) + { + return d >= '1' && d <= '9'; + }); + + // Position of the significant digit relative to the decimal gives + // the order of magnitude of the mantissa. + auto effective_exponent = static_cast(decimal_point - significant_digit); + + // If the number includes an explicit exponent, add that to the the + // total exponent. + if (mantissa_end != res.ptr) + { + const char* exponent_begin = *(mantissa_end + 1) == '+' + ? mantissa_end + 2 // skip the exponent's + sign + : mantissa_end + 1; + int exponent = 0; + auto exp_res = std::from_chars(exponent_begin, res.ptr, exponent); + JSON_ASSERT(exp_res.ptr == res.ptr); + + if (exp_res.ec == std::errc::result_out_of_range) + { + // NOTE: Parentheses around function names mitigate min/max macro collision on Windows + effective_exponent = *exponent_begin == '-' + ? (std::numeric_limits::min)() + : (std::numeric_limits::max)(); + } + else + { + JSON_ASSERT(exp_res.ec == std::errc{}); + effective_exponent += exponent; + } + } + + // Set `value` to the sign-correct non-finite value based on the + // effective exponent. + value = (*first == '-' ? -1 : 1) * (effective_exponent < 0 + ? T{} + : std::numeric_limits::infinity()); + } + else + { + value = inner_value; + } + } + } + + if (res.ec == std::errc{}) + { + value = inner_value; + } + + return res; +} + +#else + +////////////////////////////////////////// +// Fallback implementation using strto* // +////////////////////////////////////////// + +/*! +@brief Query the decimal point used by the current C locale + +Note that calling this function while switching the global C locale from +another thread is undefined behavior. +*/ +JSON_HEDLEY_PURE +inline char get_locale_decimal_point() noexcept +{ + const auto* loc = localeconv(); + JSON_ASSERT(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); +} + +/*! +@brief Number parsing implementation details + +A traits class template that allows users of nlohmann::detail::from_chars to +query details of the used implementation. + +Each specialization provides the following static members: +- is_locale_independent: whether the from_chars in unaffected by the current + global C locale +- get_decimal_point: getter for the character used as decimal point by from_chars +- strto: dispatches to the C standard library strto* function for type T + +@tparam T numeric type to parse, matching the `value` argument of from_chars +*/ +template +struct from_chars_traits; + +template <> +struct from_chars_traits // NOLINT(runtime/int) +{ + static constexpr bool is_locale_independent = false; + static constexpr char(*get_decimal_point)() = &get_locale_decimal_point; + static long long strto(const char* str, char** endptr) noexcept + { + return std::strtoll(str, endptr, 10); + } +}; + +template <> +struct from_chars_traits // NOLINT(runtime/int) +{ + static constexpr bool is_locale_independent = false; + static constexpr char(*get_decimal_point)() = &get_locale_decimal_point; + static unsigned long long strto(const char* str, char** endptr) noexcept + { + return std::strtoull(str, endptr, 10); + } +}; + +template <> +struct from_chars_traits +{ + static constexpr bool is_locale_independent = false; + static constexpr char(*get_decimal_point)() = &get_locale_decimal_point; + static constexpr float(*strto)(const char*, char**) = &std::strtof; +}; + +template <> +struct from_chars_traits +{ + static constexpr bool is_locale_independent = false; + static constexpr char(*get_decimal_point)() = &get_locale_decimal_point; + static constexpr double(*strto)(const char*, char**) = &std::strtod; +}; + +template <> +struct from_chars_traits +{ + static constexpr bool is_locale_independent = false; + static constexpr char(*get_decimal_point)() = &get_locale_decimal_point; + static constexpr long double(*strto)(const char*, char**) = &std::strtold; +}; + +template +constexpr typename std::enable_if::has_infinity, bool>::type is_out_of_range_value(T value) noexcept +{ +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + return value == T {} || value == std::numeric_limits::infinity() || value == -std::numeric_limits::infinity(); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +} + +template +constexpr typename std::enable_if < !std::numeric_limits::has_infinity, bool >::type is_out_of_range_value(T value) noexcept +{ + // NOTE: Parentheses around function names mitigate min/max macro collision on Windows + return value == (std::numeric_limits::max)() || value == (std::numeric_limits::min)(); +} + +struct from_chars_result +{ + const char* ptr; + std::errc ec; +}; + +/*! +@brief Parse integer/floating-point numbers + +Parses the string [first, last) into the numeric type T. + +This implementation uses the C standard library functions strto* to emulate the +behavior of `std::from_chars` on platforms where it is not (fully) supported. + +Regarding the under-/overflow behavior, this implementation adopts the behavior +proposed by [P4168][1] and implemented by libc++ and MS STL of setting `value` +to the corresponding non-finite floating-point value in case of an out-of-range +result. + +[1]: https://isocpp.org/files/papers/P4168R0.html + +@pre Unlike std::from_chars, this implementation requires the string to be + null-terminated, such that `last` dereferences into a NUL byte. + +@tparam T numeric type to parse +@param[in] first points to the beginning of the parsed string (inclusive) +@param[in] end points to the end of the parsed string (exclusive) +@param[out] value references the variable to set the parsed number +@return structure consisting of `ptr` and `ec` such that: + - If the string starting at `first` matches a number, `ptr` points to + the address immediately after the last character that matches. + * If the matched number can be represented by `T`, `value` is set and + `ec` is value-initialized. + * If the matched number cannot be represented by `T`, `ec` is set to + `std::errc::result_out_of_range` and depending on the type of `T`: + + `value` is left unchanged for integral `T`, + + `value` is set to +/-0 or +/-inf for floating-point `T`. + - If the string starting at `first` doesn't match a number, `ptr` + points to `first`, `ec` is `std::errc::invalid_argument`, and `value` + is left unchanged. +*/ +template +JSON_HEDLEY_NON_NULL(1, 2) +inline from_chars_result from_chars(const char* first, const char* last, T& value) +{ + JSON_ASSERT(*last == '\0'); + + // Unlike strto*, from_chars does not accept leading whitespace or + signs + if (first == last || *first == '+' + || (std::is_unsigned::value && *first == '-') + || std::isspace(*first) != 0) + { + return {first, std::errc::invalid_argument}; + } + + errno = 0; + char* ptr = nullptr; // NOLINT(misc-const-correctness) + T result = from_chars_traits::strto(first, &ptr); + + if (ptr == first) + { + return {ptr, std::errc::invalid_argument}; + } + + // Upon under-/overflow, strto* returns a marginal value and sets errno. + // Note that it is NOT sufficient to just check errno: strto* only clears + // errno if the parsed string actually parses into 0/[U]LLONG_MIN/-_MAX + // and this result was returned without indicating an under-/overflow. + // Otherwise, errno may not be relied upon to indicate the *absence* of + // an out-of-range error. + if (is_out_of_range_value(result) && errno == ERANGE) + { + if (std::numeric_limits::has_infinity) + { + // ONLY for floating-point types, set `value` to the same non-finite + // result returned by strto* to allow users to distinguish between + // different types of under-/overflow. + value = result; + } + return {ptr, std::errc::result_out_of_range}; + } + + value = result; + return {ptr, std::errc{}}; +} + +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/input/lexer.hpp b/include/nlohmann/detail/input/lexer.hpp index 6318994e6..1cf269192 100644 --- a/include/nlohmann/detail/input/lexer.hpp +++ b/include/nlohmann/detail/input/lexer.hpp @@ -9,15 +9,15 @@ #pragma once #include // array -#include // localeconv #include // size_t #include // snprintf -#include // strtof, strtod, strtold, strtoll, strtoull #include // initializer_list #include // char_traits, string +#include // errc #include // move #include // vector +#include #include #include #include @@ -152,7 +152,7 @@ class lexer : public lexer_base explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept : ia(std::move(adapter)) , ignore_comments(ignore_comments_) - , decimal_point_char(static_cast(get_decimal_point())) + , decimal_point_char(static_cast(from_chars_traits::get_decimal_point())) {} // deleted because of pointer members @@ -163,19 +163,6 @@ class lexer : public lexer_base ~lexer() = default; private: - ///////////////////// - // locales - ///////////////////// - - /// return the locale-dependent decimal point - JSON_HEDLEY_PURE - static char get_decimal_point() noexcept - { - const auto* loc = localeconv(); - JSON_ASSERT(loc != nullptr); - return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); - } - ///////////////////// // scan functions ///////////////////// @@ -941,24 +928,6 @@ class lexer : public lexer_base } } - JSON_HEDLEY_NON_NULL(2) - static void strtof(float& f, const char* str, char** endptr) noexcept - { - f = std::strtof(str, endptr); - } - - JSON_HEDLEY_NON_NULL(2) - static void strtof(double& f, const char* str, char** endptr) noexcept - { - f = std::strtod(str, endptr); - } - - JSON_HEDLEY_NON_NULL(2) - static void strtof(long double& f, const char* str, char** endptr) noexcept - { - f = std::strtold(str, endptr); - } - /*! @brief scan a number literal @@ -1279,19 +1248,18 @@ scan_number_done: // we are done scanning a number) unget(); - char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg) - errno = 0; - // try to parse integers first and fall back to floats if (number_type == token_type::value_unsigned) { - const auto x = std::strtoull(token_buffer.data(), &endptr, 10); + unsigned long long x{}; // NOLINT(runtime/int) + const auto res = ::nlohmann::detail::from_chars(token_buffer.data(), token_buffer.data() + token_buffer.size(), x); // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + JSON_ASSERT(res.ptr == token_buffer.data() + token_buffer.size()); - if (errno != ERANGE) + if (res.ec != std::errc::result_out_of_range) { + JSON_ASSERT(res.ec == std::errc{}); value_unsigned = static_cast(x); if (value_unsigned == x) { @@ -1301,13 +1269,15 @@ scan_number_done: } else if (number_type == token_type::value_integer) { - const auto x = std::strtoll(token_buffer.data(), &endptr, 10); + long long x{}; // NOLINT(runtime/int) + const auto res = ::nlohmann::detail::from_chars(token_buffer.data(), token_buffer.data() + token_buffer.size(), x); // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + JSON_ASSERT(res.ptr == token_buffer.data() + token_buffer.size()); - if (errno != ERANGE) + if (res.ec != std::errc::result_out_of_range) { + JSON_ASSERT(res.ec == std::errc{}); value_integer = static_cast(x); if (value_integer == x) { @@ -1318,10 +1288,11 @@ scan_number_done: // this code is reached if we parse a floating-point number or if an // integer conversion above failed - strtof(value_float, token_buffer.data(), &endptr); + const auto res = ::nlohmann::detail::from_chars(token_buffer.data(), token_buffer.data() + token_buffer.size(), value_float); // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + JSON_ASSERT(res.ptr == token_buffer.data() + token_buffer.size()); + JSON_ASSERT(res.ec != std::errc::invalid_argument); return token_type::value_float; } diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 4682fd361..822463fe8 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -124,6 +124,20 @@ #define JSON_HAS_FILESYSTEM 0 #endif +#ifndef JSON_HAS_FROM_CHARS + #if defined(JSON_HAS_CPP_17) && defined(__cpp_lib_to_chars) + // The std::from_chars implementation in libstdc++ 11 gets some of the corner cases wrong; + // starting with libstdc++ 12, these are fixed by switching the implementation to fast_float. + #if defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 12 + #define JSON_HAS_FROM_CHARS 0 + #else + #define JSON_HAS_FROM_CHARS 1 + #endif + #else + #define JSON_HAS_FROM_CHARS 0 + #endif +#endif + #ifndef JSON_HAS_THREE_WAY_COMPARISON #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L diff --git a/include/nlohmann/detail/macro_unscope.hpp b/include/nlohmann/detail/macro_unscope.hpp index 2ac25a46d..3a55f87a3 100644 --- a/include/nlohmann/detail/macro_unscope.hpp +++ b/include/nlohmann/detail/macro_unscope.hpp @@ -39,6 +39,7 @@ #undef JSON_HAS_CPP_26 #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #undef JSON_HAS_FROM_CHARS #undef JSON_HAS_THREE_WAY_COMPARISON #undef JSON_HAS_RANGES #undef JSON_HAS_STD_FORMAT diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 03d235173..5c6e6ebcf 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2499,6 +2499,20 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define JSON_HAS_FILESYSTEM 0 #endif +#ifndef JSON_HAS_FROM_CHARS + #if defined(JSON_HAS_CPP_17) && defined(__cpp_lib_to_chars) + // The std::from_chars implementation in libstdc++ 11 gets some of the corner cases wrong; + // starting with libstdc++ 12, these are fixed by switching the implementation to fast_float. + #if defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 12 + #define JSON_HAS_FROM_CHARS 0 + #else + #define JSON_HAS_FROM_CHARS 1 + #endif + #else + #define JSON_HAS_FROM_CHARS 0 + #endif +#endif + #ifndef JSON_HAS_THREE_WAY_COMPARISON #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L @@ -7690,15 +7704,386 @@ NLOHMANN_JSON_NAMESPACE_END #include // array -#include // localeconv #include // size_t #include // snprintf -#include // strtof, strtod, strtold, strtoll, strtoull #include // initializer_list #include // char_traits, string +#include // errc #include // move #include // vector +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +#if JSON_HAS_FROM_CHARS + #include // find, find_if + #include // from_chars, from_chars_result + #include // isnan +#else + #include // isspace + #include // ERANGE + #include // localeconv + #include // strto* + #include // enable_if, is_unsigned +#endif +#include // numeric_limits::[has_]infinity, quiet_NaN +#include // errc + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +constexpr char get_locale_independent_decimal_point() noexcept +{ + return '.'; +} + +#if JSON_HAS_FROM_CHARS + +////////////////////////////////////////////// +// Delegate to std::from_chars if supported // +////////////////////////////////////////////// + +/*! +@brief Number parsing implementation details + +A traits class template that allows users of nlohmann::detail::from_chars to +query details of the used implementation. + +@tparam T numeric type to parse, matching the `value` argument of from_chars +*/ +template +struct from_chars_traits +{ + /// whether the from_chars in unaffected by the current global C locale + static constexpr bool is_locale_independent = true; + + /// getter for the character used as decimal point by from_chars + static constexpr char(*get_decimal_point)() = &get_locale_independent_decimal_point; +}; + +using std::from_chars_result; + +/*! +@brief Parse integer/floating-point numbers + +Parses the string [first, last) into the numeric type T. + +This implementation merely delegates to `std::from_chars` with one noteworthy +difference: Different C++ standard library implementations do not agree on +whether `value` should be set in the out-of-range case for floating-point +numbers. Presently, libstdc++ leaves `value` unchanged whereas libc++ and MS STL +set `value` to +/-0 or +/-inf which allows its users to distinguish between the +various cases of over- and underflow. The C++ proposal [P4168][1] details this +discrepancy and advocates to standardize the latter behavior. + +Since we need to be able to distinguish absolute underflow (+/-0) from absolute +overflow (+/-inf), this implementation "corrects" the out-of-range behavior +accordingly by estimating the effective exponent through manual string parsing. + +[1]: https://isocpp.org/files/papers/P4168R0.html + +@tparam T numeric type to parse +@param[in] first points to the beginning of the parsed string (inclusive) +@param[in] end points to the end of the parsed string (exclusive) +@param[out] value references the variable to set the parsed number +@return structure consisting of `ptr` and `ec` such that: + - If the string starting at `first` matches a number, `ptr` points to + the address immediately after the last character that matches. + * If the matched number can be represented by `T`, `value` is set and + `ec` is value-initialized. + * If the matched number cannot be represented by `T`, `ec` is set to + `std::errc::result_out_of_range` and depending on the type of `T`: + + `value` is left unchanged for integral `T`, + + `value` is set to +/-0 or +/-inf for floating-point `T`. + - If the string starting at `first` doesn't match a number, `ptr` + points to `first`, `ec` is `std::errc::invalid_argument`, and `value` + is left unchanged. +*/ +template +JSON_HEDLEY_NON_NULL(1, 2) +inline from_chars_result from_chars(const char* first, const char* last, T& value) +{ + // No implementation of std::from_chars will set its value argument to NaN, + // so by using NaN as the initial value, we can tell if it changed. + T inner_value = std::numeric_limits::quiet_NaN(); + auto res = std::from_chars(first, last, inner_value); + + if constexpr (std::numeric_limits::has_infinity) + { + if (res.ec == std::errc::result_out_of_range) + { + if (std::isnan(inner_value)) // inner_value was not set + { + // [first, res.ptr) matches a valid floating-point number that's + // out-of-range and our std::from_chars did not set `value`, so we + // need to distinguish the type of under-/overflow ourselves. + const char* mantissa_begin = *first == '-' ? first + 1 : first; + const char* mantissa_end = std::find_if(mantissa_begin, res.ptr, [](char c) + { + return c == 'e' || c == 'E'; + }); + const char* decimal_point = std::find(mantissa_begin, mantissa_end, '.'); + const char* significant_digit = std::find_if(mantissa_begin, mantissa_end, [](char d) + { + return d >= '1' && d <= '9'; + }); + + // Position of the significant digit relative to the decimal gives + // the order of magnitude of the mantissa. + auto effective_exponent = static_cast(decimal_point - significant_digit); + + // If the number includes an explicit exponent, add that to the the + // total exponent. + if (mantissa_end != res.ptr) + { + const char* exponent_begin = *(mantissa_end + 1) == '+' + ? mantissa_end + 2 // skip the exponent's + sign + : mantissa_end + 1; + int exponent = 0; + auto exp_res = std::from_chars(exponent_begin, res.ptr, exponent); + JSON_ASSERT(exp_res.ptr == res.ptr); + + if (exp_res.ec == std::errc::result_out_of_range) + { + // NOTE: Parentheses around function names mitigate min/max macro collision on Windows + effective_exponent = *exponent_begin == '-' + ? (std::numeric_limits::min)() + : (std::numeric_limits::max)(); + } + else + { + JSON_ASSERT(exp_res.ec == std::errc{}); + effective_exponent += exponent; + } + } + + // Set `value` to the sign-correct non-finite value based on the + // effective exponent. + value = (*first == '-' ? -1 : 1) * (effective_exponent < 0 + ? T{} + : std::numeric_limits::infinity()); + } + else + { + value = inner_value; + } + } + } + + if (res.ec == std::errc{}) + { + value = inner_value; + } + + return res; +} + +#else + +////////////////////////////////////////// +// Fallback implementation using strto* // +////////////////////////////////////////// + +/*! +@brief Query the decimal point used by the current C locale + +Note that calling this function while switching the global C locale from +another thread is undefined behavior. +*/ +JSON_HEDLEY_PURE +inline char get_locale_decimal_point() noexcept +{ + const auto* loc = localeconv(); + JSON_ASSERT(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); +} + +/*! +@brief Number parsing implementation details + +A traits class template that allows users of nlohmann::detail::from_chars to +query details of the used implementation. + +Each specialization provides the following static members: +- is_locale_independent: whether the from_chars in unaffected by the current + global C locale +- get_decimal_point: getter for the character used as decimal point by from_chars +- strto: dispatches to the C standard library strto* function for type T + +@tparam T numeric type to parse, matching the `value` argument of from_chars +*/ +template +struct from_chars_traits; + +template <> +struct from_chars_traits // NOLINT(runtime/int) +{ + static constexpr bool is_locale_independent = false; + static constexpr char(*get_decimal_point)() = &get_locale_decimal_point; + static long long strto(const char* str, char** endptr) noexcept + { + return std::strtoll(str, endptr, 10); + } +}; + +template <> +struct from_chars_traits // NOLINT(runtime/int) +{ + static constexpr bool is_locale_independent = false; + static constexpr char(*get_decimal_point)() = &get_locale_decimal_point; + static unsigned long long strto(const char* str, char** endptr) noexcept + { + return std::strtoull(str, endptr, 10); + } +}; + +template <> +struct from_chars_traits +{ + static constexpr bool is_locale_independent = false; + static constexpr char(*get_decimal_point)() = &get_locale_decimal_point; + static constexpr float(*strto)(const char*, char**) = &std::strtof; +}; + +template <> +struct from_chars_traits +{ + static constexpr bool is_locale_independent = false; + static constexpr char(*get_decimal_point)() = &get_locale_decimal_point; + static constexpr double(*strto)(const char*, char**) = &std::strtod; +}; + +template <> +struct from_chars_traits +{ + static constexpr bool is_locale_independent = false; + static constexpr char(*get_decimal_point)() = &get_locale_decimal_point; + static constexpr long double(*strto)(const char*, char**) = &std::strtold; +}; + +template +constexpr typename std::enable_if::has_infinity, bool>::type is_out_of_range_value(T value) noexcept +{ +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + return value == T {} || value == std::numeric_limits::infinity() || value == -std::numeric_limits::infinity(); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +} + +template +constexpr typename std::enable_if < !std::numeric_limits::has_infinity, bool >::type is_out_of_range_value(T value) noexcept +{ + // NOTE: Parentheses around function names mitigate min/max macro collision on Windows + return value == (std::numeric_limits::max)() || value == (std::numeric_limits::min)(); +} + +struct from_chars_result +{ + const char* ptr; + std::errc ec; +}; + +/*! +@brief Parse integer/floating-point numbers + +Parses the string [first, last) into the numeric type T. + +This implementation uses the C standard library functions strto* to emulate the +behavior of `std::from_chars` on platforms where it is not (fully) supported. + +Regarding the under-/overflow behavior, this implementation adopts the behavior +proposed by [P4168][1] and implemented by libc++ and MS STL of setting `value` +to the corresponding non-finite floating-point value in case of an out-of-range +result. + +[1]: https://isocpp.org/files/papers/P4168R0.html + +@pre Unlike std::from_chars, this implementation requires the string to be + null-terminated, such that `last` dereferences into a NUL byte. + +@tparam T numeric type to parse +@param[in] first points to the beginning of the parsed string (inclusive) +@param[in] end points to the end of the parsed string (exclusive) +@param[out] value references the variable to set the parsed number +@return structure consisting of `ptr` and `ec` such that: + - If the string starting at `first` matches a number, `ptr` points to + the address immediately after the last character that matches. + * If the matched number can be represented by `T`, `value` is set and + `ec` is value-initialized. + * If the matched number cannot be represented by `T`, `ec` is set to + `std::errc::result_out_of_range` and depending on the type of `T`: + + `value` is left unchanged for integral `T`, + + `value` is set to +/-0 or +/-inf for floating-point `T`. + - If the string starting at `first` doesn't match a number, `ptr` + points to `first`, `ec` is `std::errc::invalid_argument`, and `value` + is left unchanged. +*/ +template +JSON_HEDLEY_NON_NULL(1, 2) +inline from_chars_result from_chars(const char* first, const char* last, T& value) +{ + JSON_ASSERT(*last == '\0'); + + // Unlike strto*, from_chars does not accept leading whitespace or + signs + if (first == last || *first == '+' + || (std::is_unsigned::value && *first == '-') + || std::isspace(*first) != 0) + { + return {first, std::errc::invalid_argument}; + } + + errno = 0; + char* ptr = nullptr; // NOLINT(misc-const-correctness) + T result = from_chars_traits::strto(first, &ptr); + + if (ptr == first) + { + return {ptr, std::errc::invalid_argument}; + } + + // Upon under-/overflow, strto* returns a marginal value and sets errno. + // Note that it is NOT sufficient to just check errno: strto* only clears + // errno if the parsed string actually parses into 0/[U]LLONG_MIN/-_MAX + // and this result was returned without indicating an under-/overflow. + // Otherwise, errno may not be relied upon to indicate the *absence* of + // an out-of-range error. + if (is_out_of_range_value(result) && errno == ERANGE) + { + if (std::numeric_limits::has_infinity) + { + // ONLY for floating-point types, set `value` to the same non-finite + // result returned by strto* to allow users to distinguish between + // different types of under-/overflow. + value = result; + } + return {ptr, std::errc::result_out_of_range}; + } + + value = result; + return {ptr, std::errc{}}; +} + +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + // #include // #include @@ -7837,7 +8222,7 @@ class lexer : public lexer_base explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept : ia(std::move(adapter)) , ignore_comments(ignore_comments_) - , decimal_point_char(static_cast(get_decimal_point())) + , decimal_point_char(static_cast(from_chars_traits::get_decimal_point())) {} // deleted because of pointer members @@ -7848,19 +8233,6 @@ class lexer : public lexer_base ~lexer() = default; private: - ///////////////////// - // locales - ///////////////////// - - /// return the locale-dependent decimal point - JSON_HEDLEY_PURE - static char get_decimal_point() noexcept - { - const auto* loc = localeconv(); - JSON_ASSERT(loc != nullptr); - return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); - } - ///////////////////// // scan functions ///////////////////// @@ -8626,24 +8998,6 @@ class lexer : public lexer_base } } - JSON_HEDLEY_NON_NULL(2) - static void strtof(float& f, const char* str, char** endptr) noexcept - { - f = std::strtof(str, endptr); - } - - JSON_HEDLEY_NON_NULL(2) - static void strtof(double& f, const char* str, char** endptr) noexcept - { - f = std::strtod(str, endptr); - } - - JSON_HEDLEY_NON_NULL(2) - static void strtof(long double& f, const char* str, char** endptr) noexcept - { - f = std::strtold(str, endptr); - } - /*! @brief scan a number literal @@ -8964,19 +9318,18 @@ scan_number_done: // we are done scanning a number) unget(); - char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg) - errno = 0; - // try to parse integers first and fall back to floats if (number_type == token_type::value_unsigned) { - const auto x = std::strtoull(token_buffer.data(), &endptr, 10); + unsigned long long x{}; // NOLINT(runtime/int) + const auto res = ::nlohmann::detail::from_chars(token_buffer.data(), token_buffer.data() + token_buffer.size(), x); // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + JSON_ASSERT(res.ptr == token_buffer.data() + token_buffer.size()); - if (errno != ERANGE) + if (res.ec != std::errc::result_out_of_range) { + JSON_ASSERT(res.ec == std::errc{}); value_unsigned = static_cast(x); if (value_unsigned == x) { @@ -8986,13 +9339,15 @@ scan_number_done: } else if (number_type == token_type::value_integer) { - const auto x = std::strtoll(token_buffer.data(), &endptr, 10); + long long x{}; // NOLINT(runtime/int) + const auto res = ::nlohmann::detail::from_chars(token_buffer.data(), token_buffer.data() + token_buffer.size(), x); // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + JSON_ASSERT(res.ptr == token_buffer.data() + token_buffer.size()); - if (errno != ERANGE) + if (res.ec != std::errc::result_out_of_range) { + JSON_ASSERT(res.ec == std::errc{}); value_integer = static_cast(x); if (value_integer == x) { @@ -9003,10 +9358,11 @@ scan_number_done: // this code is reached if we parse a floating-point number or if an // integer conversion above failed - strtof(value_float, token_buffer.data(), &endptr); + const auto res = ::nlohmann::detail::from_chars(token_buffer.data(), token_buffer.data() + token_buffer.size(), value_float); // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + JSON_ASSERT(res.ptr == token_buffer.data() + token_buffer.size()); + JSON_ASSERT(res.ec != std::errc::invalid_argument); return token_type::value_float; } @@ -26555,6 +26911,7 @@ struct formatter // NOLINT(cert-dcl58-c #undef JSON_HAS_CPP_26 #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #undef JSON_HAS_FROM_CHARS #undef JSON_HAS_THREE_WAY_COMPARISON #undef JSON_HAS_RANGES #undef JSON_HAS_STD_FORMAT diff --git a/tests/src/unit-class_lexer.cpp b/tests/src/unit-class_lexer.cpp index 92eadfbc1..2ffc2d049 100644 --- a/tests/src/unit-class_lexer.cpp +++ b/tests/src/unit-class_lexer.cpp @@ -55,6 +55,8 @@ TEST_CASE("lexer class") SECTION("numbers") { + // Number parsing implementation uses std::from_chars where available, + // so run this test suite with JSON_HAS_CPP_17 as well. CHECK((scan_string("0") == json::lexer::token_type::value_unsigned)); CHECK((scan_string("1") == json::lexer::token_type::value_unsigned)); CHECK((scan_string("2") == json::lexer::token_type::value_unsigned)); diff --git a/tests/src/unit-from_chars.cpp b/tests/src/unit-from_chars.cpp new file mode 100644 index 000000000..824dd9d4f --- /dev/null +++ b/tests/src/unit-from_chars.cpp @@ -0,0 +1,278 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include + +#include +#include +#include +#include +#include +#include + +// nlohmann::details::from_chars is a wrapper around std::from_chars when it is +// available (__cpp_lib_to_chars is defined), otherwise our fallback +// implementation kicks in. +// +// Due to the incomplete implementation of this C++17 standard library feature +// (for example, lack of support for long double even in current libc++), +// JSON_HAS_CPP_17 does not guarantee that std::from_chars is available. +// However, the reverse holds: If the test is compiled in C++11 mode, the +// fallback implementation will be used for sure. +// By mentioning the JSON_HAS_CPP_17 macro in this here comment, the test will +// be compiled both using our fallback and, at least on some platforms, +// the C++17 standard library version, as a way of ensuring that the +// expectations formulated in this test align with `std::from_chars`. + +namespace +{ +struct init_val_t {}; +constexpr init_val_t init_val{}; + +template +void check_result(std::string& str, T& value, std::ptrdiff_t expected_ptr_offset, std::errc expected_ec) +{ + // On platforms where `std::from_chars` is not available, our `strtof`-based implementation is + // in fact locale-dependent and might use a different decimal separator, so we need to adapt + // the test expectations accordingly. + std::replace(str.begin(), str.end(), '.', nlohmann::detail::from_chars_traits::get_decimal_point()); + + auto res = nlohmann::detail::from_chars(str.data(), str.data() + str.size(), value); + CHECK_MESSAGE(res.ec == expected_ec, "Error code mismatch while parsing: \"", str, + "\": Actual: ", make_error_code(res.ec).message(), + "; expected: ", make_error_code(expected_ec).message()); + CHECK_MESSAGE(res.ptr - str.data() == expected_ptr_offset, "Ptr offset mismatch while parsing: \"", str, "\""); +} + +template +typename std::enable_if::value, void>::type +check(std::string str, T expected_value, std::ptrdiff_t expected_ptr_offset, std::errc expected_ec) +{ + T value = 42; + check_result(str, value, expected_ptr_offset, expected_ec); + CHECK_MESSAGE(value == expected_value, "while parsing: \"", str, "\""); +} + +template +typename std::enable_if::value, void>::type +check(std::string str, T expected_value, std::ptrdiff_t expected_ptr_offset, std::errc expected_ec) +{ + { + T value = 42; + check_result(str, value, expected_ptr_offset, expected_ec); + CHECK_MESSAGE(value == expected_value, "while parsing: \"", str, "\""); + CHECK_MESSAGE(std::signbit(value) == std::signbit(expected_value), "while parsing: \"", str, "\""); + } + { + T value = std::numeric_limits::quiet_NaN(); + check_result(str, value, expected_ptr_offset, expected_ec); + CHECK_MESSAGE(value == expected_value, "while parsing: \"", str, "\""); + } +} + +template +typename std::enable_if::value, void>::type +check(std::string str, init_val_t, std::ptrdiff_t expected_ptr_offset, std::errc expected_ec) +{ + T const init_value = 42; + T value = init_value; + check_result(str, value, expected_ptr_offset, expected_ec); + CHECK_MESSAGE(value == init_value, "while parsing: \"", str, "\""); +} + +template +typename std::enable_if::value, void>::type +check(std::string str, init_val_t, std::ptrdiff_t expected_ptr_offset, std::errc expected_ec) +{ + { + T const init_value = 42; + T value = init_value; + check_result(str, value, expected_ptr_offset, expected_ec); + CHECK_MESSAGE(value == init_value, "while parsing: \"", str, "\""); + } + { + T value = std::numeric_limits::quiet_NaN(); + check_result(str, value, expected_ptr_offset, expected_ec); + CHECK_MESSAGE(doctest::IsNaN(value), "while parsing: \"", str, "\""); + } +} + +} // namespace + +TEST_CASE("integral results consistent with std::from_chars") +{ + SECTION("unsigned long long") + { + check("", init_val, 0, std::errc::invalid_argument); + check("0", 0ULL, 1, std::errc{}); + check(" 123", init_val, 0, std::errc::invalid_argument); + check("123 ", 123ULL, 3, std::errc{}); + check("+123", init_val, 0, std::errc::invalid_argument); + check("-123", init_val, 0, std::errc::invalid_argument); + check("123", 123ULL, 3, std::errc{}); + check("123e10", 123ULL, 3, std::errc{}); + check("18446744073709551615", 18446744073709551615ULL, 20, std::errc{}); + check("18446744073709551616", init_val, 20, std::errc::result_out_of_range); + } + + SECTION("long long") + { + check("", init_val, 0, std::errc::invalid_argument); + check("0", 0LL, 1, std::errc{}); + check(" 123", init_val, 0, std::errc::invalid_argument); + check("123 ", 123LL, 3, std::errc{}); + check("+123", init_val, 0, std::errc::invalid_argument); + check("-123", -123LL, 4, std::errc{}); + check("123", 123LL, 3, std::errc{}); + check("123e10", 123LL, 3, std::errc{}); + check("9223372036854775807", 9223372036854775807LL, 19, std::errc{}); + check("9223372036854775808", init_val, 19, std::errc::result_out_of_range); + check("-9223372036854775808", -9223372036854775807LL - 1, 20, std::errc{}); + check("-9223372036854775809", init_val, 20, std::errc::result_out_of_range); + } +} + +TEST_CASE("floating point results consistent with std::from_chars") +{ + INFO("Using decimal point '", + std::string{1, nlohmann::detail::from_chars_traits::get_decimal_point()}, + "' to match our possibly-locale-dependent from_chars fallback implementation"); + + SECTION("single precision") + { + check("", init_val, 0, std::errc::invalid_argument); + check(" 123", init_val, 0, std::errc::invalid_argument); + check("123 ", 123.0f, 3, std::errc{}); + check("+123", init_val, 0, std::errc::invalid_argument); + check("-123", -123.0f, 4, std::errc{}); + check("123", 123.0f, 3, std::errc{}); + check("123e10", 123e10f, 6, std::errc{}); + check("123e+10", 123e10f, 7, std::errc{}); + check("123e-10", 123e-10f, 7, std::errc{}); + check("123.456", 123.456f, 7, std::errc{}); + check("123;456", 123.0f, 3, std::errc{}); + check("123.456 ", 123.456f, 7, std::errc{}); + check("123;456 ", 123.0f, 3, std::errc{}); + check("123456789.123456789", 123456789.123456789f, 19, std::errc{}); + check("1e40", std::numeric_limits::infinity(), 4, std::errc::result_out_of_range); + check("-1e40", -std::numeric_limits::infinity(), 5, std::errc::result_out_of_range); + check("2e308", std::numeric_limits::infinity(), 5, std::errc::result_out_of_range); + check("-2e308", -std::numeric_limits::infinity(), 6, std::errc::result_out_of_range); + check("123.456e-789", 0.0f, 12, std::errc::result_out_of_range); + check("-123.456e-789", -0.0f, 13, std::errc::result_out_of_range); + check("1e-45", 1e-45f, 5, std::errc{}); + check("1e-46", 0.0f, 5, std::errc::result_out_of_range); + check("1E-45", 1e-45f, 5, std::errc{}); + check("1E-46", 0.0f, 5, std::errc::result_out_of_range); + check("10e-46", 1e-45f, 6, std::errc{}); + check("0.1e-45", 0.0f, 7, std::errc::result_out_of_range); + check("100000000000000000000000000000000000000", 1e38f, 39, std::errc{}); + check("100000000000000000000000000000000000000.0", 1e38f, 41, std::errc{}); + check("1000000000000000000000000000000000000000e-1", 1e38f, 43, std::errc{}); + check("0.0000000000000000000000000000000000000000000001e84", 1e38f, 51, std::errc{}); + check("0.000000000000000000000000000000000000000000001", 1e-45f, 47, std::errc{}); + check("00.000000000000000000000000000000000000000000001", 1e-45f, 48, std::errc{}); + check("0.0000000000000000000000000000000000000000000001e1", 1e-45f, 50, std::errc{}); + check("1000000000000000000000000000000000000000e-84", 1e-45f, 44, std::errc{}); + check("1000000000000000000000000000000000000000", std::numeric_limits::infinity(), 40, std::errc::result_out_of_range); + check("1000000000000000000000000000000000000000.0", std::numeric_limits::infinity(), 42, std::errc::result_out_of_range); + check("100000000000000000000000000000000000000e1", std::numeric_limits::infinity(), 41, std::errc::result_out_of_range); + check("0.0000000000000000000000000000000000000000000001e85", std::numeric_limits::infinity(), 51, std::errc::result_out_of_range); + check("1000000000000000000000000000000000000000e-85", 0.0f, 44, std::errc::result_out_of_range); + check("0.0000000000000000000000000000000000000000000001", 0.0f, 48, std::errc::result_out_of_range); + check("0.000000000000000000000000000000000000000000001e-1", 0.0f, 50, std::errc::result_out_of_range); + check("1e-99999999999999999999", 0.0f, 23, std::errc::result_out_of_range); + check("1e+99999999999999999999", std::numeric_limits::infinity(), 23, std::errc::result_out_of_range); + check("-1e-99999999999999999999", -0.0f, 24, std::errc::result_out_of_range); + check("-1e+99999999999999999999", -std::numeric_limits::infinity(), 24, std::errc::result_out_of_range); + } + + SECTION("double precision") + { + check("", init_val, 0, std::errc::invalid_argument); + check(" 123", init_val, 0, std::errc::invalid_argument); + check("123 ", 123.0, 3, std::errc{}); + check("+123", init_val, 0, std::errc::invalid_argument); + check("-123", -123.0, 4, std::errc{}); + check("123", 123.0, 3, std::errc{}); + check("123e10", 123e10, 6, std::errc{}); + check("123e+10", 123e10, 7, std::errc{}); + check("123e-10", 123e-10, 7, std::errc{}); + check("123.456", 123.456, 7, std::errc{}); + check("123;456", 123.0, 3, std::errc{}); + check("123.456 ", 123.456, 7, std::errc{}); + check("123;456 ", 123.0, 3, std::errc{}); + check("123456789.123456789", 123456789.123456789, 19, std::errc{}); + check("1e40", 1e40, 4, std::errc{}); + check("-1e40", -1e40, 5, std::errc{}); + check("2e308", std::numeric_limits::infinity(), 5, std::errc::result_out_of_range); + check("-2e308", -std::numeric_limits::infinity(), 6, std::errc::result_out_of_range); + check("2e-324", 0.0, 6, std::errc::result_out_of_range); + check("-2e-324", -0.0, 7, std::errc::result_out_of_range); + check("123.456e-789", 0.0, 12, std::errc::result_out_of_range); + check("-123.456e-789", -0.0, 13, std::errc::result_out_of_range); + check("1e-99999999999999999999", 0.0, 23, std::errc::result_out_of_range); + check("1e+99999999999999999999", std::numeric_limits::infinity(), 23, std::errc::result_out_of_range); + check("-1e-99999999999999999999", -0.0, 24, std::errc::result_out_of_range); + check("-1e+99999999999999999999", -std::numeric_limits::infinity(), 24, std::errc::result_out_of_range); + } + + SECTION("long double precision") + { + check("", init_val, 0, std::errc::invalid_argument); + check(" 123", init_val, 0, std::errc::invalid_argument); + check("123 ", 123.0L, 3, std::errc{}); + check("+123", init_val, 0, std::errc::invalid_argument); + check("-123", -123.0L, 4, std::errc{}); + check("123", 123.0L, 3, std::errc{}); + check("123e10", 123e10L, 6, std::errc{}); + check("123e+10", 123e10L, 7, std::errc{}); + check("123e-10", 123e-10L, 7, std::errc{}); + check("123.456", 123.456L, 7, std::errc{}); + check("123;456", 123.0L, 3, std::errc{}); + check("123.456 ", 123.456L, 7, std::errc{}); + check("123;456 ", 123.0L, 3, std::errc{}); + check("123456789.123456789", 123456789.123456789L, 19, std::errc{}); + check("1e40", 1e40L, 4, std::errc{}); + check("-1e40", -1e40L, 5, std::errc{}); +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4127) +#endif + if (sizeof(long double) > 8) +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + { + // Right-hand-side is calculated to avoid warning about literal + // exceeding range on platforms where this branch is NOT taken. + check("2e308", 1e308L * 2, 5, std::errc{}); + check("-2e308", -1e308L * 2, 6, std::errc{}); + check("2e-324", 8e-324L / 4, 6, std::errc{}); + check("-2e-324", -8e-324L / 4, 7, std::errc{}); + } + else + { + check("2e308", std::numeric_limits::infinity(), 5, std::errc::result_out_of_range); + check("-2e308", -std::numeric_limits::infinity(), 6, std::errc::result_out_of_range); + check("2e-324", 0.0L, 6, std::errc::result_out_of_range); + check("-2e-324", -0.0L, 7, std::errc::result_out_of_range); + } + check("1e5000", std::numeric_limits::infinity(), 6, std::errc::result_out_of_range); + check("-1e5000", -std::numeric_limits::infinity(), 7, std::errc::result_out_of_range); + check("123.456e-7890", 0.0L, 13, std::errc::result_out_of_range); + check("-123.456e-7890", -0.0L, 14, std::errc::result_out_of_range); + check("1e-99999999999999999999", 0.0L, 23, std::errc::result_out_of_range); + check("1e+99999999999999999999", std::numeric_limits::infinity(), 23, std::errc::result_out_of_range); + check("-1e-99999999999999999999", -0.0L, 24, std::errc::result_out_of_range); + check("-1e+99999999999999999999", -std::numeric_limits::infinity(), 24, std::errc::result_out_of_range); + } +} +