diff --git a/include/nlohmann/detail/input/contiguous_parser.hpp b/include/nlohmann/detail/input/contiguous_parser.hpp index 4093cbdb5..04c972bb8 100644 --- a/include/nlohmann/detail/input/contiguous_parser.hpp +++ b/include/nlohmann/detail/input/contiguous_parser.hpp @@ -646,7 +646,9 @@ done: } else if (i < avail && data[i] >= '1' && data[i] <= '9') { - i = accumulate_digits(data, i, avail, w); + // accept() needs the value only for a float with a large exponent, + // which computes it below; its handler ignores the values + i = keep_values() ? accumulate_digits(data, i, avail, w) : skip_digits(data, i, avail); } else { @@ -664,7 +666,7 @@ done: return false; } const std::size_t fraction_begin = i; - i = accumulate_digits(data, i, avail, w); + i = keep_values() ? accumulate_digits(data, i, avail, w) : skip_digits(data, i, avail); fraction_digits = i - fraction_begin; digit_count += fraction_digits; } @@ -753,6 +755,16 @@ done: { return report(sax->number_float(value, number_text(data, data + i))); } + if (!keep_values()) + { + // the digits were skipped: their value now + s.w = 0; + const std::size_t integer_end = accumulate_digits(data, integer_begin, avail, s.w); + if (dot_index != std::string::npos) + { + static_cast(accumulate_digits(data, integer_end + 1, avail, s.w)); + } + } value = decimal_to_float(s); } else diff --git a/include/nlohmann/detail/input/number_parse.hpp b/include/nlohmann/detail/input/number_parse.hpp index 852e1a6c8..71215d3df 100644 --- a/include/nlohmann/detail/input/number_parse.hpp +++ b/include/nlohmann/detail/input/number_parse.hpp @@ -316,6 +316,30 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t accumulate_digits(const char* data, std::s return i; } +/// the index of the first byte at or after @a i that is not a digit: the +/// digits of accumulate_digits() without their value +JSON_HEDLEY_ALWAYS_INLINE std::size_t skip_digits(const char* data, std::size_t i, std::size_t avail) noexcept +{ + while (avail - i >= 8) + { + if (!is_eight_digits(read_eight_bytes(data + i))) + { + // a byte of the word is not a digit: no bounds checks before it + while (static_cast(data[i] - '0') <= 9) + { + ++i; + } + return i; + } + i += 8; + } + while (i < avail && data[i] >= '0' && data[i] <= '9') + { + ++i; + } + return i; +} + /// whether [first, last) contains a digit other than '0'; a plain loop, as /// GCC calls std::any_of out of line even for the usual empty range JSON_HEDLEY_ALWAYS_INLINE bool has_nonzero_digit(const char* first, const char* last) noexcept diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index aebd9b25a..b5801e12a 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -11257,6 +11257,30 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t accumulate_digits(const char* data, std::s return i; } +/// the index of the first byte at or after @a i that is not a digit: the +/// digits of accumulate_digits() without their value +JSON_HEDLEY_ALWAYS_INLINE std::size_t skip_digits(const char* data, std::size_t i, std::size_t avail) noexcept +{ + while (avail - i >= 8) + { + if (!is_eight_digits(read_eight_bytes(data + i))) + { + // a byte of the word is not a digit: no bounds checks before it + while (static_cast(data[i] - '0') <= 9) + { + ++i; + } + return i; + } + i += 8; + } + while (i < avail && data[i] >= '0' && data[i] <= '9') + { + ++i; + } + return i; +} + /// whether [first, last) contains a digit other than '0'; a plain loop, as /// GCC calls std::any_of out of line even for the usual empty range JSON_HEDLEY_ALWAYS_INLINE bool has_nonzero_digit(const char* first, const char* last) noexcept @@ -24488,7 +24512,9 @@ done: } else if (i < avail && data[i] >= '1' && data[i] <= '9') { - i = accumulate_digits(data, i, avail, w); + // accept() needs the value only for a float with a large exponent, + // which computes it below; its handler ignores the values + i = keep_values() ? accumulate_digits(data, i, avail, w) : skip_digits(data, i, avail); } else { @@ -24506,7 +24532,7 @@ done: return false; } const std::size_t fraction_begin = i; - i = accumulate_digits(data, i, avail, w); + i = keep_values() ? accumulate_digits(data, i, avail, w) : skip_digits(data, i, avail); fraction_digits = i - fraction_begin; digit_count += fraction_digits; } @@ -24595,6 +24621,16 @@ done: { return report(sax->number_float(value, number_text(data, data + i))); } + if (!keep_values()) + { + // the digits were skipped: their value now + s.w = 0; + const std::size_t integer_end = accumulate_digits(data, integer_begin, avail, s.w); + if (dot_index != std::string::npos) + { + static_cast(accumulate_digits(data, integer_end + 1, avail, s.w)); + } + } value = decimal_to_float(s); } else diff --git a/tests/src/unit-contiguous_parser.cpp b/tests/src/unit-contiguous_parser.cpp index a12094e59..5579c0d1d 100644 --- a/tests/src/unit-contiguous_parser.cpp +++ b/tests/src/unit-contiguous_parser.cpp @@ -518,6 +518,33 @@ TEST_CASE("contiguous parser") } } + SECTION("accept() of floats near the largest value") + { + // accept() skips digits without their value and computes it only for + // a float whose exponent may reach the largest value + for (const char* number : + {"17976931348623158e292", "17976931348623159e292", "1797693134862315.8e293", "1797693134862315.9e293", + "0.17976931348623158e309", "0.17976931348623159e309", "-17976931348623159e292", "9999999999999999999e289" + }) + { + check_both(number); + check_both(std::string("[") + number + "]"); + } + CHECK(json::accept("17976931348623158e292")); + CHECK_FALSE(json::accept("17976931348623159e292")); + CHECK(json::accept("[1797693134862315.8e293]")); + CHECK_FALSE(json::accept("[1797693134862315.9e293]")); + CHECK(json::accept("0.17976931348623158e309")); + CHECK_FALSE(json::accept("0.17976931348623159e309")); + CHECK_FALSE(json::accept("-17976931348623159e292")); + + using narrow_json = nlohmann::basic_json; + check_same("34028235e31"); + check_same("34028236e31"); + CHECK(narrow_json::accept("34028235e31")); + CHECK_FALSE(narrow_json::accept("34028236e31")); + } + SECTION("callbacks, comments and trailing commas take the library parser") { // (the results are those of the options; the contiguous parser does not support them)