mirror of
https://github.com/nlohmann/json.git
synced 2026-09-03 15:05:01 +00:00
Skip the float fast path when it cannot succeed
parse_float_fast() (Clinger) needs a significand below 2^53, so it always declines once the mantissa has 17 or more significant digits. convert_number() called it unconditionally, so those numbers were walked an extra time before strtod had to run anyway. On streaming input, where scanning is byte-at-a-time and there is no compensating win, that made canada.json about 6% slower than develop. Derive the significant-digit count from token_buffer indices - the digits are not scanned again - and skip the call when it is guaranteed to decline. Both scanners pass the offset where the mantissa ends; the count only has to be corrected for a leading "0", which the JSON grammar admits nowhere else. The integer path returns before the check, so integer-heavy input is unaffected. Values are unchanged: this only avoids an attempt that would have failed. Verified bit-exact against develop over every number in canada.json, floats.json, signed_ints.json, unsigned_ints.json, small_signed_ints.json, citm_catalog.json and twitter.json, for both the contiguous and the streaming scanner. parse, streaming develop before after canada.json 19.4ms 20.5ms 19.3ms floats.json 135.9ms 131.8ms 128.0ms parse, contiguous develop before after canada.json 15.5ms 12.9ms 11.7ms floats.json 98.6ms 69.8ms 66.7ms Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <cstdlib> // strtod
|
||||
#include <sstream> // stringstream
|
||||
#include <string> // string
|
||||
#include <vector> // vector
|
||||
@@ -273,6 +274,56 @@ TEST_CASE("lexer number fast path")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("significant-digit gate for the Clinger fast path")
|
||||
{
|
||||
// Clinger's fast path needs a significand below 2^53, so it cannot
|
||||
// succeed once the mantissa has 17 or more significant digits (the
|
||||
// significand would be at least 10^16). The lexer skips the attempt
|
||||
// there. That is only allowed to save work: every value must still come
|
||||
// out bit-exactly, and both scanners must agree. In particular the gate
|
||||
// must not fire for tokens whose leading zeros merely look like extra
|
||||
// digits - "0.1234567890123456" has 16 significant digits, not 17.
|
||||
const std::vector<std::string> numbers =
|
||||
{
|
||||
"1234567890123456", // 16 significant digits
|
||||
"12345678901234567", // 17 -> attempt skipped
|
||||
"123456789012345678", // 18 -> attempt skipped
|
||||
"0.1234567890123456", // 16: the leading "0" is not significant
|
||||
"0.12345678901234567", // 17
|
||||
"0.00000000000000001", // 1, in a long token
|
||||
"0.000000000000000012345678901234", // 14, in a long token
|
||||
"-0.0000000000000000000001", // 1, negative
|
||||
"1.0000000000000000", // 17: trailing zeros are significant here
|
||||
"10000000000000000", // 17
|
||||
"9007199254740992", // 2^53
|
||||
"9007199254740993", // 2^53 + 1
|
||||
"-65.613616999999977", // canada.json shape
|
||||
"1.2345678901234567e-250", // 17 with an exponent
|
||||
"1.234567890123456e-250", // 16 with an exponent
|
||||
"1e10", "0.0", "-0.0", "0e0", "0.000123"
|
||||
};
|
||||
|
||||
for (const auto& n : numbers)
|
||||
{
|
||||
CAPTURE(n);
|
||||
const std::string doc = "[" + n + "]";
|
||||
|
||||
const json a = json::parse(doc); // contiguous fast path
|
||||
std::stringstream ss(doc);
|
||||
const json b = json::parse(ss); // streaming byte path
|
||||
|
||||
CHECK(a[0].type() == b[0].type());
|
||||
CHECK(a == b);
|
||||
|
||||
if (a[0].is_number_float())
|
||||
{
|
||||
const double expected = std::strtod(n.c_str(), nullptr);
|
||||
CHECK(a[0].get<double>() == expected);
|
||||
CHECK(b[0].get<double>() == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("token type classification")
|
||||
{
|
||||
CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
|
||||
|
||||
Reference in New Issue
Block a user