412 Commits

Author SHA1 Message Date
Daniel Lemire
9cd6dfc8e3 Leave std::float32_t/std::float64_t unmodified on invalid input (fixes #424)
The fixed-width wrappers parsed into a zero-initialized temporary and
copied it back unconditionally, so invalid input overwrote the
destination with zero. Copy back only when the result is not
invalid_argument, as required by [charconv.from.chars]/1.
2026-10-12 10:10:54 -04:00
github-actions[bot]
42f254836e Version 8.3.2 2026-10-09 19:56:38 +00:00
Sahana Bogar
50f01dbb51 avoid signed overflow in is_integer for negative code units 2026-10-08 19:12:13 +05:30
Sahana Bogar
1271675129 avoid signed negation overflow in integer_times_pow10
Negating the most negative int64_t is undefined behaviour. GCC at -O2 uses that to turn the fast-path mantissa bound into a signed compare and returns the wrong sign, so take the magnitude in uint64_t instead.
2026-10-05 22:46:40 +05:30
github-actions[bot]
f3f02c8ad0 Version 8.3.1 2026-10-04 16:26:53 +00:00
Daniel Lemire
fe5e97eeac Reorder parsed_number_string_t fields to avoid padding (fixes #418)
Placing the parse_error field next to the booleans shrinks the struct
from 72 to 64 bytes on 64-bit systems, recovering a large performance
regression observed with MSVC. Add a static_assert to guard the size.
2026-10-04 09:34:39 -04:00
Daniel Lemire
1dc46a6b8f
Merge pull request #409 from caolanm/main
drop the unreachable tail of parse_mantissa
2026-09-25 18:21:25 -04:00
Daniel Lemire
cdc385d058
Merge pull request #408 from jadidbourbaki/float16-exactness
Fix std::float16_t rounding of subnormal ties and fast-path overflow
2026-09-22 14:11:44 -04:00
Daniel Lemire
82623d83c5 Avoid right-shifting a negative value in subnormal_ties_possible() 2026-09-22 13:57:31 -04:00
Daniel Lemire
b57a12270e Fold the format at compile time under clang, mark cold checks unlikely
Clang generated ~35% more instructions per float than GCC on identical
code (canada.txt, 64-bit: 276 vs 200 i/f on a Xeon Gold 6548N). Two
causes, confirmed with exact instruction traces:

1. Clang declines to inline the parser (from_chars_caller<T>::call) into
   its callers, so parse_options stays a runtime value and every format
   flag is re-tested on every conversion: javascript, skip_white_space,
   allow_leading_plus, fixed/scientific, the decimal point, the JSON
   dispatch. GCC inlines the whole parser into each call site, where
   chars_format::general is a constant, and all of it folds away.

   Fix: from_chars dispatches chars_format::general to a
   from_chars_fixed_format<T, UC, Fmt> instantiation, so the format is a
   compile-time constant inside the parser body whether or not the user's
   call is inlined. The call forwarder gets fastfloat_clang_really_inline
   so that it folds into that instantiation.

2. Clang if-converts the rare over/underflow fix-ups at the end of
   compute_float and the result_out_of_range test into cmov chains that
   every conversion executes. Marking them fastfloat_clang_unlikely keeps
   them as never-taken branches.

Both are applied under clang only, and GCC's generated code is identical
to before (verified on the object code). That is on purpose: forcing
always_inline on from_chars, or even on the trivial call forwarder,
regressed GCC by up to 14% on mesh.txt by changing its inlining order;
dispatching to the fixed-format instantiation made it stop inlining into
the loop; and the unlikely hints alone cost it 2-6% on mesh.txt by
reordering blocks (GCC already emits branches there).

realbenchmark, cycles per float, 5 interleaved rounds, medians:

clang 21, Xeon Gold 6548N        before   after
  canada.txt ASCII  (64)          49.7    44.6   -10%
  canada.txt UTF-16 (64)          57.4    52.0    -9%
  mesh.txt   ASCII  (64)          26.5    22.4   -16%
  mesh.txt   UTF-16 (64)          28.1    24.0   -15%
  (32-bit: -8% to -14%)
gcc 14, same machine: generated code unchanged.
Apple clang 17, arm64: +3% to +18% Mfloat/s.
2026-09-21 22:30:43 -04:00
Daniel Lemire
7e45de0c3b Restore the simple loop in fastfloat_strncasecmp
PR #356 replaced the 3/5-character case-insensitive compare used by
parse_infnan with SWAR helpers (fastfloat_strncasecmp3/5 plus a generic
memcpy-based fastfloat_strncasecmp). That code only runs for "nan",
"inf" and "infinity" inputs, so it cannot help ordinary parsing, but it
grew the inlined inf/nan tail of the parser: on x86-64 (GCC 14 and
clang 21) the per-commit benchmarks dropped by 1.5-2.5% because the
extra dead code changed inlining decisions and register allocation on
the hot path (+7 instructions/float on canada.txt).

Go back to the plain loop, which the compiler unrolls for the constant
lengths 3 and 5, and drop the now-unused generic SWAR function. The
`actual < 256` guard of the original is unnecessary: OR-ing 0x20 into
a value can only equal an ASCII lowercase letter when the value is that
letter or its uppercase form, so the plain OR is exact for every
character type.
2026-09-21 22:05:34 -04:00
Raúl Marín
b2e41e8b88 Keep chars_format::javascript out of the common hot path
8.3.0 added the ECMAScript leading-zero rule as a runtime test on fmt inside
parse_number_string, so unlike basic_json_fmt its error returns and Annex B
octal scan land in the parser body that every from_chars instantiation
force-inlines. from_chars_caller<double>::call grew from 935 to 1031
instructions and other formats lost about 3% on short inputs.

Make the rule a compile-time parameter of the parser body, selected once at the
entry point, and take chars_format::javascript out to a cold conversion before
anything else is live so the branch is a tail call. parse_number_string keeps
its signature and behaviour.

benchmarks/benchmark.cpp, Intel Xeon 6975P-C, clang 22.1.8, cycles per float,
best of 7:

              canada.txt    mesh.txt
  8.2.10         47.03        24.78
  8.3.0          47.34        26.08
  this           46.28        24.86
2026-09-21 19:47:21 +00:00
Daniel Lemire
b0ab987b3d Version 8.3.0 2026-09-15 11:03:18 -04:00
Daniel Lemire
3254622f74 Add chars_format::javascript_sloppy (Annex B NonOctalDecimalIntegerLiteral)
chars_format::javascript follows the strict-mode grammar, which rejects
any leading zero. In sloppy mode, JavaScript also accepts a leading zero
when one of the digits is 8 or 9 ("08.5" is 8.5, Annex B's
NonOctalDecimalIntegerLiteral). A leading zero followed only by octal
digits is a LegacyOctalIntegerLiteral ("0775"), which is not a decimal
number: it is rejected with the new parse_error::legacy_octal_integer_part
so the caller can parse it in base 8.

javascript_sloppy sets one extra bit on top of javascript, and the check
lives inside the existing runtime JavaScript branch of
parse_number_string<false>, so the general path is unchanged. An
interleaved A/B with realbenchmark on canada.txt and mesh.txt (ASCII and
UTF-16, 32/64-bit) shows identical instructions per float and cycles
within noise.

Document both formats in the README.
2026-09-15 10:52:49 -04:00
jadidbourbaki
20a9b35333 Gate the subnormal tie test on a compile-time predicate 2026-09-15 09:11:27 -04:00
Caolán McNamara
27b2a9dd06 drop the unreachable tail of parse_mantissa
counter is always zero when the loops end, so the final
add_native never runs

seen in Coverity as cid#488376 DEADCODE dead_error_line
2026-09-15 09:50:00 +01:00
jadidbourbaki
abb4c8659a Fix std::float16_t rounding of subnormal ties and fast-path overflow 2026-09-14 22:15:45 -04:00
Daniel Lemire
4fcb7e3249 Fix clang-format 17 on FASTFLOAT_ASSERT macros 2026-09-14 20:46:52 -04:00
Daniel Lemire
f837a78057 Add chars_format::javascript (ECMAScript DecimalLiteral)
The JSON format rejects leading zeros, which JavaScript also rejects,
but it further requires digits on both sides of the decimal point,
which JavaScript does not: ".5", "5." and "5.e3" are all valid
DecimalLiteral productions. Users lexing JavaScript therefore could not
use chars_format::json and had to re-check the integer span themselves
after a general-mode parse.

chars_format::javascript is general mode plus the leading-zero rule
(reported as leading_zeros_in_integer_part), with no_infnan. It is a
runtime check on the format, like fortran, so it reuses the existing
parse_number_string<false> instantiation. A leading minus sign is
accepted, and a leading plus sign with allow_leading_plus.

Claude-Session: https://claude.ai/code/session_01KbAWRBpE3MPy5cXECc5vWZ
2026-09-14 19:36:49 -04:00
Daniel Lemire
12e310aa8f Avoid old-style casts
Replace C-style casts with static_cast/const_cast so -Wold-style-cast
is clean, and enable that warning in the Unix test builds and the
amalgamated-header CI job.
2026-08-20 13:30:32 -04:00
Kai Pastor
c335514c6e Fix compat with min() macro 2026-07-23 09:58:09 +02:00
Kai Pastor
ec239e0e04 Fix compat with max() macro 2026-07-21 11:26:09 +02:00
Daniel Lemire
f6df0f2917
Merge pull request #398 from redis-performance/exp062-063-combo
Enable the 4-digit SWAR follow-up on GCC + skip the rounding-mode probe for long mantissas
2026-07-13 15:13:16 -04:00
fcostaoliveira
5082489e35 EXP-063: test the mode-independent mantissa bound before the rounds_to_nearest probe 2026-07-03 14:34:47 +01:00
fcostaoliveira
f4f36e04f7 EXP-062: enable the 4-digit SWAR fraction follow-up on all compilers (drop __clang__ gate) 2026-07-03 14:34:47 +01:00
sahvx655-wq
c539b5399c guard is_space against negative signed code units 2026-06-18 19:51:56 +05:30
Daniel Lemire
34164f547b 8.2.10 2026-06-14 09:52:47 -04:00
Daniel Lemire
a7249f86ed replace checked re-parse with O(1) simdjson-style overflow check
The previous commit detects multi-wrap u64 overflow at the max_digits
boundary by re-parsing the digits through a checked multiply-add loop
(O(max_digits)). Replace that with the constant-time check used in
simdjson: the leading digit plus a single threshold comparison.

For a max_digits-length value, min_safe_u64(base) == base^(max_digits-1)
is the smallest such value and also the width of each leading-digit band
[d*ms, (d+1)*ms). Since that width is < 2^64, the only band that can
straddle 2^64 is d == dmax (the largest leading digit that still fits),
and there it straddles at most once, so a single threshold dmax*ms
separates wrapped from non-wrapped values. A leading digit above dmax
always overflows; below dmax always fits. dmax and the threshold derive
from the existing min_safe_u64 table, so no new tables are needed and
dmax*ms cannot itself overflow.

Add a programmatic, self-verifying test for parse_int_string overflow
detection covering bases 2..36, complementing the hand-picked strings
added earlier. Every generated input is cross-checked against an
independent trusted oracle (a plain 64-bit checked multiply-add); on
success the parsed value is also compared exactly and full consumption
of the input is asserted.

Per base it exercises:
  - an exact-boundary sweep of the 64 values straddling 2^64
    (UINT64_MAX-31 .. 2^64+31), built by walking the digit string;
  - UINT64_MAX, 2^64 and the all-max-digit value, each also with
    leading zeros;
  - random max_digits-length values across every leading digit, with
    the heaviest sampling on the lead == dmax band that straddles 2^64,
    and full coverage of lead > dmax (the multi-wrap region the naive
    min_safe check accepted by mistake);
  - max_digits-1 (never overflows) and max_digits+1 (always overflows).
A small signed (int64_t) section checks the exact INT64_MIN/INT64_MAX
limits round-trip and that INT64_MAX+1 / INT64_MIN-1 are rejected in
every base.
2026-06-13 21:34:34 -04:00
sahvx655-wq
632cc97b5b detect uint64 overflow that wraps past min_safe in parse_int_string 2026-06-13 21:21:29 -04:00
Daniel Lemire
8234a89623 8.2.9 2026-06-11 20:29:24 -04:00
Daniel Lemire
0dce102cb4
Merge pull request #391 from sahvx655-wq/int-fast-path-wide-units
reject non-digit wide code units in uint8/uint16 integer fast path
2026-06-11 20:28:10 -04:00
Joao Paulo Magalhaes
8e6edc8ad2
Fix compile error with gcc 9: use of [[unlikely]] 2026-06-10 15:37:26 +01:00
sahvx655-wq
82882b237d gate uint8/uint16 base-10 fast paths to single-byte code units 2026-06-10 12:12:34 +05:30
Daniel Lemire
e8ec8e8f34 8.2.8 2026-06-08 15:29:36 -04:00
Joao Paulo Magalhaes
23e245f2b3
Fix compile error in clang<10: fails on pragma -Wc++20-extensions
This fixes a compile error in all clang versions lower than 10,
triggered by the use of the pragma ignore with what is an unknown
warning on those compiler versions:

```
/__w/ext/fast_float/include/fast_float/parse_number.h:361:34: error: unknown warning group '-Wc++20-extensions', ignored [-Werror,-Wunknown-pragmas]
```

The fix requires looking at __clang_major__, which is unfortunately
different in Apple, so a version dispatch is required.
2026-06-08 12:39:48 +01:00
Daniel Lemire
e0b53eaf63 8.2.7 2026-06-07 14:14:42 -04:00
Daniel Lemire
29bd11571b one too many 2026-06-07 11:19:47 -04:00
Daniel Lemire
b1fbfe932a silencing -Wc++20-extensions at the point of use solely 2026-06-07 11:18:09 -04:00
Daniel Lemire
520fded4a3 adressing comments by @jwakely 2026-06-06 13:13:49 -04:00
Daniel Lemire
b72e07132c let us using 'unlikely' hints. 2026-06-05 22:01:27 -04:00
fcostaoliveira
3067491f41 clang-format (clang-format-17 comment reflow + signature wrap; no semantic change) 2026-06-03 09:35:26 +01:00
fcostaoliveira
cb5d9cd9a4 Skip materializing the integer/fraction spans on the hot path
parsed_number_string_t carries two span<UC const> members (integer, fraction)
that are only read on the rare slow paths (digit_comp, and the >19-significant-
digit truncation recompute). Materializing them on every parse forces the ~56/64-
byte struct to be written out and marshaled through the by-value return, which
shows up as backend/store pressure on the hot path.

This adds a runtime `store_spans` flag (default true, so all existing callers are
unchanged) to parse_number_string; from_chars_float_advanced parses with it false,
attempts the Clinger and Eisel-Lemire fast paths inline, and only re-parses with
spans on the two rare slow branches. The re-parse is pushed into a single
`fastfloat_noinline` (noinline+cold) helper so the force-inlined hot scanner is
emitted once rather than duplicated into the caller (without this the extra inline
copies regress some targets, e.g. ARM gcc, by bloating the hot frame and lengthening
the loop-carried dependency chain).

A runtime flag is used deliberately rather than a template parameter: a template
would create a second instantiation of the whole scanner whose icache cost wipes
out the gain.

Measured (per-parser microbench, median of 5, pinned core), fast_float from_chars
<double>/<float>, vs the current tip:
  - Intel Ice Lake (Xeon 8360Y): +17-19% (gcc), Intel TMA shows backend-bound
    26.0% -> 2.2% and retiring 60.3% -> 77.3% on short floats (the eliminated span
    spill), with -36% pipeline slots.
  - Intel Cascade Lake (Xeon 6248): +18-22% (gcc), +13-23% (clang).
  - ARM Neoverse-V2 (Graviton4): +73-196% (gcc), +8-11% (clang) -- the struct spill
    dominated the gcc hot loop there.
Correctness: the full float exhaustive suite (exhaustive32, exhaustive32_64,
exhaustive32_midpoint, random64) passes, and a 2^32 sweep is byte-identical to the
current tip. Public from_chars / from_chars_advanced / parsed_number_string_t are
unchanged.
2026-06-03 09:30:42 +01:00
Jonathan Wakely
1b11407da9 Fix spelling
Run clang-format to reformat the long lines.
2026-06-02 15:30:37 +01:00
Daniel Lemire
cfd12ebcf1 8.2.6 2026-06-01 18:07:41 -04:00
Daniel Lemire
ed861322d8
Merge pull request #382 from redis-performance/pr/four-digit-followup
Add a 4-digit SWAR follow-up to loop_parse_if_eight_digits (clang)
2026-06-01 15:45:15 -04:00
fcostaoliveira
7589a4fea5 Add a 4-digit SWAR follow-up to loop_parse_if_eight_digits (clang)
After the 8-digit SWAR block loop, consume a remaining 4-7 digit run in one
read4_to_u32 + parse_four_digits_unrolled step instead of byte-by-byte (reusing
the existing 4-digit helpers). The parsed result is identical; this is purely a
faster way to consume the same digits.

Gated to clang: on gcc the extra 4-digit check regresses inputs whose remainder
is < 4 digits (e.g. the 17-digit fraction of uniform [0,1] -> -3% on 'random'),
because the check becomes pure overhead there; clang does not show that.

m8g.metal-24xl (Graviton4), -O3 -march=native, simple_fastfloat_benchmark,
from_chars->double, clang 18, base vs patch back-to-back (2 samples):
  canada.txt +11.7%, mesh.txt +7.4%, random ~flat. No regression.
2026-06-01 11:55:50 +01:00
fcostaoliveira
b64d014e2f Unroll the integer-part digit scan (straight-line for the common 1-5 digit case)
parse_number_string scans the integer part one byte at a time in a while loop,
while the fraction already uses the 8-digit SWAR loop. Most integer parts are
1-5 digits, so the loop back-edge dominates. Peel the first five iterations into
nested ifs, falling through to the original while for longer runs. Semantics are
identical (i = 10*i + digit, advancing p); no behavior change.

AWS m8g.metal-24xl (Graviton4), -O3 -march=native, simple_fastfloat_benchmark,
from_chars->double. base vs patch measured back-to-back, mean of 2 runs:
  canada: gcc +3.1%, clang +2.8%
  mesh:   gcc +5.4%, clang +5.1%
  random: ~flat (1-digit integer part)
No regression; gcc and clang agree.

Alternatives benchmarked and rejected: reusing loop_parse_if_eight_digits for the
integer part regressed 5-8% (integer parts are too short for 8-digit SWAR setup);
a counted for(k<5) loop matched on gcc but clang optimized it worse (canada -0.9%).
The explicit peel is the only form solidly positive on both compilers.
2026-06-01 09:55:08 +01:00
Daniel Lemire
05087a303d 8.2.5 2026-04-16 14:39:03 -04:00
Michael Lippautz
001c04cc8a Remove <algorithm> include and replace std::min with ternary operators
Replaces uses of std::min with ternary operators in ascii_number.h, digit_comparison.h, and float_common.h to remove the dependency on the <algorithm> header in those files.
2026-04-16 17:17:19 +00:00
Michael Lippautz
b063de82c7
Include <algorithm> in float_common.h
`fastfloat_strncasecmp` relies on `std::min`.
2026-04-16 09:51:58 +02:00