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.
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.
- New release.yml (workflow_dispatch): bumps patch/minor/major, runs
script/release.py, tests the amalgamation and the full test suite,
commits and tags on main, and publishes a release with fast_float.h.
- on-release.yml: the upload failed with 'Validation Failed' because
fast_float.h was already attached to the release. Use
'gh release upload --clobber' instead of the deprecated
actions/upload-release-asset, and run the amalgamation test binary.
- release.py: also bump MODULE.bazel (stuck at 8.2.4) and exit non-zero
if amalgamation fails.
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.
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.