From 510ea1fa8a7108b1f7072628895f24fbc86589b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Mon, 21 Sep 2026 22:08:58 +0000 Subject: [PATCH] Parse a long input once instead of twice A mantissa of more than 19 significant digits needs more than 19 characters, so only a longer input can come back too_many_digits. Today that is discovered after a full parse without spans, and the input is then parsed a second time with them. Testing the length up front sends it straight to the parse that materializes the spans. benchmarks/benchmark.cpp, Intel Xeon 6975P-C, clang 22.1.8, cycles per float, best of 5: canada mesh long fractions 20-38 digit ints >38 digit ints before 47.30 26.20 233.94 259.73 447.73 after 46.90 25.68 195.68 181.65 273.11 Results are bit-identical over 400k adversarial inputs. Caveat: the test looks at the range, not at the number. A caller that passes the whole remaining buffer for a short value takes the slow path on every value, measured at 20% for 7-character numbers in a large buffer. Deciding on the token instead costs more than it saves: a 20-character probe loop is slower than the second parse it avoids. --- include/fast_float/parse_number.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index 58cdafd5..524530f7 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -336,6 +336,14 @@ from_chars_float_advanced(UC const *first, UC const *last, T &value, } bool const bjf = uint64_t(fmt & detail::basic_json_fmt) != 0; + // A mantissa of more than 19 significant digits needs more than 19 + // characters, so only a longer input can be too_many_digits. Predicting it + // from the length sends it straight to the span-materializing parse instead + // of parsing once without spans and then re-parsing with them. + if fastfloat_unlikely ((last - first) > 19) { + return parse_number_slow_path(first, last, value, options, bjf); + } + // Fast path: parse WITHOUT materializing the integer/fraction spans (read // only by the rare slow paths). Skipping their stores keeps the fat // parsed_number_string_t off the hot path. store_spans is a runtime argument,