diff --git a/include/nlohmann/detail/input/lexer.hpp b/include/nlohmann/detail/input/lexer.hpp index 8c13cd6d8..aec10c5d2 100644 --- a/include/nlohmann/detail/input/lexer.hpp +++ b/include/nlohmann/detail/input/lexer.hpp @@ -14,6 +14,7 @@ #include // snprintf #include // memcmp #include // initializer_list +#include // numeric_limits #include // char_traits, string #include // move #include // vector @@ -1816,6 +1817,15 @@ scan_number_done: /// significand (see scan_number_bulk_contiguous()) bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept { + // accept() only needs to know that the value is finite (see the parser): + // w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a + // value that underflows is a finite number as well. Only a token that + // may overflow has to be converted to find out. + if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits::max_exponent10)) + { + value_float = 0; + return true; + } value_float = decimal_to_float(s); return true; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 4dbec78fb..f36721a6a 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -9723,6 +9723,7 @@ NLOHMANN_JSON_NAMESPACE_END #include // snprintf #include // memcmp #include // initializer_list +#include // numeric_limits #include // char_traits, string #include // move #include // vector @@ -14071,6 +14072,15 @@ scan_number_done: /// significand (see scan_number_bulk_contiguous()) bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept { + // accept() only needs to know that the value is finite (see the parser): + // w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a + // value that underflows is a finite number as well. Only a token that + // may overflow has to be converted to find out. + if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits::max_exponent10)) + { + value_float = 0; + return true; + } value_float = decimal_to_float(s); return true; } diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 1d1b5f40b..a97652243 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -2952,3 +2952,32 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX") } } #endif + +TEST_CASE("accept() agrees with parse() on floats around the overflow limit") +{ + // accept() does not convert a float that cannot overflow; the tokens + // around the limit (and beyond it) must still be rejected exactly as + // parse() rejects them + const char* const numbers[] = + { + "1e0", "123.456e-5", "1e-400", "-1e-400", "1e307", "1e308", "1e309", "-1e309", + "10e307", "100e307", "0.1e309", "0.1e310", "1E+308", "1E+309", + "1.7976931348623157e308", "1.7976931348623159e308", "1.8e308", "-1.7976931348623157e308", + "12345678901234567e291", "12345678901234567e292", "123456789012345678e290", "123456789012345678e291", + "1234567890123456789e289", "1234567890123456789e290", "9999999999999999999e289", "9999999999999999999e290", + "0.00000000000000000001e328", "0.00000000000000000001e329" + }; + + for (const char* number : numbers) + { + for (const std::string text : + { + std::string(number), "[" + std::string(number) + "]", "{\"a\":" + std::string(number) + "}" + }) + { + INFO(text); + const bool parses = !json::parse(text, nullptr, false).is_discarded(); + CHECK(json::accept(text) == parses); + } + } +}