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')
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -664,7 +666,7 @@ done:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const std::size_t fraction_begin = i;
|
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;
|
fraction_digits = i - fraction_begin;
|
||||||
digit_count += fraction_digits;
|
digit_count += fraction_digits;
|
||||||
}
|
}
|
||||||
@@ -753,6 +755,16 @@ done:
|
|||||||
{
|
{
|
||||||
return report(sax->number_float(value, number_text(data, data + i)));
|
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);
|
value = decimal_to_float<number_float_t>(s);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -316,6 +316,30 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t accumulate_digits(const char* data, std::s
|
|||||||
return i;
|
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
|
/// 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
|
/// 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
|
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;
|
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
|
/// 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
|
/// 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
|
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')
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -24506,7 +24532,7 @@ done:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const std::size_t fraction_begin = i;
|
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;
|
fraction_digits = i - fraction_begin;
|
||||||
digit_count += fraction_digits;
|
digit_count += fraction_digits;
|
||||||
}
|
}
|
||||||
@@ -24595,6 +24621,16 @@ done:
|
|||||||
{
|
{
|
||||||
return report(sax->number_float(value, number_text(data, data + i)));
|
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);
|
value = decimal_to_float<number_float_t>(s);
|
||||||
}
|
}
|
||||||
else
|
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")
|
SECTION("callbacks, comments and trailing commas take the library parser")
|
||||||
{
|
{
|
||||||
// (the results are those of the options; the contiguous parser does not support them)
|
// (the results are those of the options; the contiguous parser does not support them)
|
||||||
|
|||||||
Reference in new issue
Block a user