accept(): skip digits without computing their value
accept() needs the value of a number only for a float whose exponent may reach the largest value. skip_digits() finds the end of the digits without the multiply-adds of accumulate_digits(), and once an 8-byte word fails the digit test, the remaining loop needs no bounds check: a non-digit is in that word. The value is computed afterwards for the rare floats that need it. accept() of canada.json: -16 % on Apple M1, -22 % with GCC 15 and -21 % with Clang 21 on x86-64; files with many numbers -9 to -15 %. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
4 files changed
+103
-4
No files matched your search
@@ -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<void>(accumulate_digits(data, integer_end + 1, avail, s.w));
|
||||
}
|
||||
}
|
||||
value = decimal_to_float<number_float_t>(s);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -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<unsigned char>(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
|
||||
|
||||
Generated
+38
-2
@@ -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<unsigned char>(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<void>(accumulate_digits(data, integer_end + 1, avail, s.w));
|
||||
}
|
||||
}
|
||||
value = decimal_to_float<number_float_t>(s);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -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<std::map, std::vector, std::string, bool, std::int32_t, std::uint32_t, float>;
|
||||
check_same<narrow_json>("34028235e31");
|
||||
check_same<narrow_json>("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)
|
||||
|
||||
Reference in new issue
Block a user