Compare commits

..

30 Commits
v8.2.6 ... main

Author SHA1 Message Date
Daniel Lemire
34164f547b 8.2.10 2026-06-14 09:52:47 -04:00
Daniel Lemire
4eec7bec38
Merge pull request #394 from fastfloat/int-overflow-simdjson-approach
Int overflow check with a faster approach
2026-06-14 09:52:09 -04:00
Daniel Lemire
fd970ab05e updating visual studio 2026-06-13 21:41:53 -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
Daniel Lemire
30868f8734
Merge pull request #392 from biojppm/fix/gcc9_compile_error
Fix compile error with gcc 9: use of [[unlikely]]
2026-06-11 09:37:38 -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
937198691a
Merge pull request #389 from correctmost/cm/remove-unreachable-return
Remove an unreachable return statement
2026-06-09 11:18:15 -04:00
Daniel Lemire
0352ba3fef
Merge pull request #390 from correctmost/cm/remove-unreachable-block
Remove an else if statement that is always false
2026-06-09 11:17:35 -04:00
correctmost
6ae691372f Remove an else if statement that is always false
Commit b334317d added the same std::isnan(v) check as an earlier
condition.

The warning was reported by cppcheck.
2026-06-09 03:48:54 -04:00
correctmost
8fe7a9405b Remove an unreachable return statement
The redundant statement was reported by cppcheck.
2026-06-09 03:37:58 -04:00
Daniel Lemire
e8ec8e8f34 8.2.8 2026-06-08 15:29:36 -04:00
Daniel Lemire
c05156ff60
Merge pull request #388 from biojppm/fix/clang_compile_error
Fix compile error in clang<10: fails on pragma -Wc++20-extensions
2026-06-08 15:28:45 -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
3044c9b182
Merge pull request #387 from fastfloat/pr386
Using unlikely markers for PR386
2026-06-07 14:12:38 -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
Daniel Lemire
6258cbc5a1
Merge pull request #380 from fastfloat/dependabot/github_actions/github-actions-0eb558eb98
Bump the github-actions group across 1 directory with 3 updates
2026-06-02 14:02:10 -04:00
Daniel Lemire
254f10ce39
Merge pull request #385 from jwakely/patch-2
Fix spelling
2026-06-02 14:01:41 -04:00
Jonathan Wakely
1b11407da9 Fix spelling
Run clang-format to reformat the long lines.
2026-06-02 15:30:37 +01:00
Daniel Lemire
f0ed8cdf52 display the latest version. 2026-06-01 18:28:09 -04:00
dependabot[bot]
b3ec8d89cf
Bump the github-actions group across 1 directory with 3 updates
Bumps the github-actions group with 3 updates in the / directory: [actions/setup-node](https://github.com/actions/setup-node), [mymindstorm/setup-emsdk](https://github.com/mymindstorm/setup-emsdk) and [jidicula/clang-format-action](https://github.com/jidicula/clang-format-action).


Updates `actions/setup-node` from 6.3.0 to 6.4.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](53b83947a5...48b55a011b)

Updates `mymindstorm/setup-emsdk` from 14 to 16
- [Release notes](https://github.com/mymindstorm/setup-emsdk/releases)
- [Commits](6ab9eb1bda...4528d102f7)

Updates `jidicula/clang-format-action` from 4.17.0 to 4.18.0
- [Release notes](https://github.com/jidicula/clang-format-action/releases)
- [Commits](3a18028048...654a770daa)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: jidicula/clang-format-action
  dependency-version: 4.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: mymindstorm/setup-emsdk
  dependency-version: '16'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-01 00:15:05 +00:00
16 changed files with 573 additions and 72 deletions

View File

@ -5,8 +5,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.2.2 - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.2.2
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: mymindstorm/setup-emsdk@6ab9eb1bda2574c4ddb79809fc9247783eaf9021 # v14 - uses: mymindstorm/setup-emsdk@4528d102f7230f0e7b276855c01ea1159be0e984 # v16
- name: Verify - name: Verify
run: emcc -v run: emcc -v
- name: Checkout - name: Checkout

View File

@ -27,7 +27,7 @@ jobs:
- uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.7 - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.7
- name: Run clang-format - name: Run clang-format
uses: jidicula/clang-format-action@3a18028048f01a66653b4e3bf8d968d2e7e2cb8b # v4.17.0 uses: jidicula/clang-format-action@654a770daa28443dd111d133e4083e21c1075674 # v4.18.0
with: with:
clang-format-version: '17' clang-format-version: '17'

View File

@ -10,8 +10,8 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- {gen: Visual Studio 17 2022, arch: ARM64, cfg: Release} - {gen: Visual Studio 18 2026, arch: ARM64, cfg: Release}
- {gen: Visual Studio 17 2022, arch: ARM64, cfg: Debug} - {gen: Visual Studio 18 2026, arch: ARM64, cfg: Debug}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v6.0.2 uses: actions/checkout@v6.0.2

View File

@ -10,10 +10,10 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- {gen: Visual Studio 17 2022, arch: Win32, cfg: Release} - {gen: Visual Studio 18 2026, arch: Win32, cfg: Release}
#- {gen: Visual Studio 17 2022, arch: Win32, cfg: Debug} #- {gen: Visual Studio 18 2026, arch: Win32, cfg: Debug}
- {gen: Visual Studio 17 2022, arch: x64, cfg: Release} - {gen: Visual Studio 18 2026, arch: x64, cfg: Release}
- {gen: Visual Studio 17 2022, arch: x64, cfg: Debug} - {gen: Visual Studio 18 2026, arch: x64, cfg: Debug}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v6.0.2 uses: actions/checkout@v6.0.2

View File

@ -10,10 +10,10 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- {gen: Visual Studio 17 2022, arch: Win32, cfg: Release} - {gen: Visual Studio 18 2026, arch: Win32, cfg: Release}
- {gen: Visual Studio 17 2022, arch: Win32, cfg: Debug} - {gen: Visual Studio 18 2026, arch: Win32, cfg: Debug}
- {gen: Visual Studio 17 2022, arch: x64, cfg: Release} - {gen: Visual Studio 18 2026, arch: x64, cfg: Release}
- {gen: Visual Studio 17 2022, arch: x64, cfg: Debug} - {gen: Visual Studio 18 2026, arch: x64, cfg: Debug}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v6.0.2 uses: actions/checkout@v6.0.2

View File

@ -10,10 +10,10 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- {gen: Visual Studio 17 2022, arch: Win32, cfg: Release} - {gen: Visual Studio 18 2026, arch: Win32, cfg: Release}
- {gen: Visual Studio 17 2022, arch: Win32, cfg: Debug} - {gen: Visual Studio 18 2026, arch: Win32, cfg: Debug}
- {gen: Visual Studio 17 2022, arch: x64, cfg: Release} - {gen: Visual Studio 18 2026, arch: x64, cfg: Release}
- {gen: Visual Studio 17 2022, arch: x64, cfg: Debug} - {gen: Visual Studio 18 2026, arch: x64, cfg: Debug}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v6.0.2 uses: actions/checkout@v6.0.2

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.14) cmake_minimum_required(VERSION 3.14)
project(fast_float VERSION 8.2.6 LANGUAGES CXX) project(fast_float VERSION 8.2.10 LANGUAGES CXX)
set(FASTFLOAT_CXX_STANDARD 11 CACHE STRING "the C++ standard to use for fastfloat") set(FASTFLOAT_CXX_STANDARD 11 CACHE STRING "the C++ standard to use for fastfloat")
set(CMAKE_CXX_STANDARD ${FASTFLOAT_CXX_STANDARD}) set(CMAKE_CXX_STANDARD ${FASTFLOAT_CXX_STANDARD})
option(FASTFLOAT_TEST "Enable tests" OFF) option(FASTFLOAT_TEST "Enable tests" OFF)

View File

@ -531,7 +531,7 @@ sufficiently recent version of CMake (3.11 or better at least):
FetchContent_Declare( FetchContent_Declare(
fast_float fast_float
GIT_REPOSITORY https://github.com/fastfloat/fast_float.git GIT_REPOSITORY https://github.com/fastfloat/fast_float.git
GIT_TAG tags/v8.2.6 GIT_TAG tags/v8.2.10
GIT_SHALLOW TRUE) GIT_SHALLOW TRUE)
FetchContent_MakeAvailable(fast_float) FetchContent_MakeAvailable(fast_float)
@ -547,7 +547,7 @@ You may also use [CPM](https://github.com/cpm-cmake/CPM.cmake), like so:
CPMAddPackage( CPMAddPackage(
NAME fast_float NAME fast_float
GITHUB_REPOSITORY "fastfloat/fast_float" GITHUB_REPOSITORY "fastfloat/fast_float"
GIT_TAG v8.2.6) GIT_TAG v8.2.10)
``` ```
## Using as single header ## Using as single header
@ -559,7 +559,7 @@ if desired as described in the command line help.
You may directly download automatically generated single-header files: You may directly download automatically generated single-header files:
<https://github.com/fastfloat/fast_float/releases/download/v8.2.6/fast_float.h> <https://github.com/fastfloat/fast_float/releases/download/v8.2.10/fast_float.h>
## Benchmarking ## Benchmarking

View File

@ -221,7 +221,7 @@ constexpr double pi = parse("3.1415"); // computed at compile time</code></pre>
<pre data-lang="cmake"><code>FetchContent_Declare( <pre data-lang="cmake"><code>FetchContent_Declare(
fast_float fast_float
GIT_REPOSITORY https://github.com/fastfloat/fast_float.git GIT_REPOSITORY https://github.com/fastfloat/fast_float.git
GIT_TAG tags/v{{VERSION}} GIT_TAG tags/<span data-version>v{{VERSION}}</span>
GIT_SHALLOW TRUE) GIT_SHALLOW TRUE)
FetchContent_MakeAvailable(fast_float) FetchContent_MakeAvailable(fast_float)
target_link_libraries(myprogram PUBLIC fast_float)</code></pre> target_link_libraries(myprogram PUBLIC fast_float)</code></pre>
@ -232,13 +232,13 @@ target_link_libraries(myprogram PUBLIC fast_float)</code></pre>
<pre data-lang="cmake"><code>CPMAddPackage( <pre data-lang="cmake"><code>CPMAddPackage(
NAME fast_float NAME fast_float
GITHUB_REPOSITORY "fastfloat/fast_float" GITHUB_REPOSITORY "fastfloat/fast_float"
GIT_TAG v{{VERSION}})</code></pre> GIT_TAG <span data-version>v{{VERSION}}</span>)</code></pre>
</article> </article>
<article class="card"> <article class="card">
<h3>Single header</h3> <h3>Single header</h3>
<p>Download a pre-amalgamated header — no build system required.</p> <p>Download a pre-amalgamated header — no build system required.</p>
<pre data-lang="bash"><code>curl -LO https://github.com/fastfloat/fast_float/releases/download/v{{VERSION}}/fast_float.h</code></pre> <pre data-lang="bash"><code>curl -LO https://github.com/fastfloat/fast_float/releases/download/<span data-version>v{{VERSION}}</span>/fast_float.h</code></pre>
</article> </article>
<article class="card"> <article class="card">

View File

@ -330,10 +330,17 @@ report_parse_error(UC const *p, parse_error error) {
// Assuming that you use no more than 19 digits, this will // Assuming that you use no more than 19 digits, this will
// parse an ASCII string. // parse an ASCII string.
//
// store_spans is a *runtime* flag (not a template parameter, deliberately: a
// template would create a second instantiation of this whole function and the
// extra icache pressure wipes out the gain). When false, the integer/fraction
// spans (read only by the rare digit_comp slow path) are not materialized,
// which keeps the fat parsed_number_string_t off the hot path. The caller
// re-parses with store_spans=true if the slow path is actually reached.
template <bool basic_json_fmt, typename UC> template <bool basic_json_fmt, typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC> fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC>
parse_number_string(UC const *p, UC const *pend, parse_number_string(UC const *p, UC const *pend, parse_options_t<UC> options,
parse_options_t<UC> options) noexcept { bool store_spans = true) noexcept {
chars_format const fmt = detail::adjust_for_feature_macros(options.format); chars_format const fmt = detail::adjust_for_feature_macros(options.format);
UC const decimal_point = options.decimal_point; UC const decimal_point = options.decimal_point;
@ -402,7 +409,9 @@ parse_number_string(UC const *p, UC const *pend,
} }
UC const *const end_of_integer_part = p; UC const *const end_of_integer_part = p;
int64_t digit_count = int64_t(end_of_integer_part - start_digits); int64_t digit_count = int64_t(end_of_integer_part - start_digits);
if (store_spans) {
answer.integer = span<UC const>(start_digits, size_t(digit_count)); answer.integer = span<UC const>(start_digits, size_t(digit_count));
}
FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) {
// at least 1 digit in integer part, without leading zeros // at least 1 digit in integer part, without leading zeros
if (digit_count == 0) { if (digit_count == 0) {
@ -429,7 +438,9 @@ parse_number_string(UC const *p, UC const *pend,
i = i * 10 + digit; // in rare cases, this will overflow, but that's ok i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
} }
exponent = before - p; exponent = before - p;
if (store_spans) {
answer.fraction = span<UC const>(before, size_t(p - before)); answer.fraction = span<UC const>(before, size_t(p - before));
}
digit_count -= exponent; digit_count -= exponent;
} }
FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) {
@ -514,6 +525,11 @@ parse_number_string(UC const *p, UC const *pend,
if (digit_count > 19) { if (digit_count > 19) {
answer.too_many_digits = true; answer.too_many_digits = true;
// The truncation recompute below reads the integer/fraction spans. When
// store_spans is false we didn't materialize them, so just flag
// too_many_digits; the caller re-parses with store_spans=true to obtain
// the corrected mantissa/exponent before taking the slow path.
if (store_spans) {
// Let us start again, this time, avoiding overflows. // Let us start again, this time, avoiding overflows.
// We don't need to call if is_integer, since we use the // We don't need to call if is_integer, since we use the
// pre-tokenized spans from above. // pre-tokenized spans from above.
@ -539,6 +555,7 @@ parse_number_string(UC const *p, UC const *pend,
// We have now corrected both exponent and i, to a truncated value // We have now corrected both exponent and i, to a truncated value
} }
} }
}
answer.exponent = exponent; answer.exponent = exponent;
answer.mantissa = i; answer.mantissa = i;
return answer; return answer;
@ -583,7 +600,8 @@ parse_int_string(UC const *p, UC const *pend, T &value,
UC const *const start_digits = p; UC const *const start_digits = p;
FASTFLOAT_IF_CONSTEXPR17((std::is_same<T, std::uint8_t>::value)) { FASTFLOAT_IF_CONSTEXPR17(
(std::is_same<T, std::uint8_t>::value && sizeof(UC) == 1)) {
if (base == 10) { if (base == 10) {
const size_t len = (size_t)(pend - p); const size_t len = (size_t)(pend - p);
if (len == 0) { if (len == 0) {
@ -675,7 +693,8 @@ parse_int_string(UC const *p, UC const *pend, T &value,
} }
} }
FASTFLOAT_IF_CONSTEXPR17((std::is_same<T, std::uint16_t>::value)) { FASTFLOAT_IF_CONSTEXPR17(
(std::is_same<T, std::uint16_t>::value && sizeof(UC) == 1)) {
if (base == 10) { if (base == 10) {
const size_t len = size_t(pend - p); const size_t len = size_t(pend - p);
if (len == 0) { if (len == 0) {
@ -762,10 +781,28 @@ parse_int_string(UC const *p, UC const *pend, T &value,
} }
// this check can be eliminated for all other types, but they will all require // this check can be eliminated for all other types, but they will all require
// a max_digits(base) equivalent // a max_digits(base) equivalent
if (digit_count == max_digits && i < min_safe_u64(base)) { if (digit_count == max_digits) {
// At the max_digits boundary the accumulator `i` may have wrapped around
// 2^64. A plain `i < min_safe_u64(base)` test is not sufficient: for any
// base whose max_digits-length range exceeds 2^64 (base 10 reaches
// ~5.4 * 2^64 at 20 digits) the value can wrap a whole multiple of 2^64 and
// land back above min_safe, slipping through. Decide exactly in O(1) using
// the leading digit, following the approach used in simdjson:
// ms == min_safe_u64(base) == base^(max_digits-1), the smallest
// max_digits-length value.
// dmax == the largest leading digit whose number can still fit in u64.
// The leading-digit band [d*ms, (d+1)*ms) has width ms < 2^64, so within
// the single band where d == dmax the value straddles 2^64 at most once,
// and a single threshold separates wrapped from non-wrapped values. A
// leading digit above dmax always overflows; below dmax always fits.
uint64_t const ms = min_safe_u64(base);
uint64_t const dmax = (std::numeric_limits<uint64_t>::max)() / ms;
uint64_t const lead = ch_to_digit(*start_digits);
if (lead > dmax || (lead == dmax && i < dmax * ms)) {
answer.ec = std::errc::result_out_of_range; answer.ec = std::errc::result_out_of_range;
return answer; return answer;
} }
}
// check other types overflow // check other types overflow
if (!std::is_same<T, uint64_t>::value) { if (!std::is_same<T, uint64_t>::value) {

View File

@ -7,12 +7,12 @@
namespace fast_float { namespace fast_float {
/** /**
* This function parses the character sequence [first,last) for a number. It * This function parses the character sequence [first,last) for a number. It
* parses floating-point numbers expecting a locale-indepent format equivalent * parses floating-point numbers expecting a locale-independent format
* to what is used by std::strtod in the default ("C") locale. The resulting * equivalent to what is used by std::strtod in the default ("C") locale. The
* floating-point value is the closest floating-point values (using either float * resulting floating-point value is the closest floating-point values (using
* or double), using the "round to even" convention for values that would * either float or double), using the "round to even" convention for values that
* otherwise fall right in-between two values. That is, we provide exact parsing * would otherwise fall right in-between two values. That is, we provide exact
* according to the IEEE standard. * parsing according to the IEEE standard.
* *
* Given a successful parse, the pointer (`ptr`) in the returned value is set to * Given a successful parse, the pointer (`ptr`) in the returned value is set to
* point right after the parsed number, and the `value` referenced is set to the * point right after the parsed number, and the `value` referenced is set to the

View File

@ -18,7 +18,7 @@
#define FASTFLOAT_VERSION_MAJOR 8 #define FASTFLOAT_VERSION_MAJOR 8
#define FASTFLOAT_VERSION_MINOR 2 #define FASTFLOAT_VERSION_MINOR 2
#define FASTFLOAT_VERSION_PATCH 6 #define FASTFLOAT_VERSION_PATCH 10
#define FASTFLOAT_STRINGIZE_IMPL(x) #x #define FASTFLOAT_STRINGIZE_IMPL(x) #x
#define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x) #define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x)
@ -197,6 +197,32 @@ using parse_options = parse_options_t<char>;
#define fastfloat_really_inline inline __attribute__((always_inline)) #define fastfloat_really_inline inline __attribute__((always_inline))
#endif #endif
// Branch-probability hint marking the rare slow-path branches as cold, so the
// optimizer keeps the out-of-line slow-path re-parse off the hot path (and does
// not duplicate the force-inlined hot scanner into the caller, which bloated
// the hot frame and hurt ILP on some targets). Used at the call site as
// if fastfloat_unlikely(cond) { ... }
// (the macro supplies the parentheses). It expands to the standard [[unlikely]]
// attribute when supported, otherwise to __builtin_expect on GCC/Clang, or
// to a no-op elsewhere (e.g. pre-C++20 MSVC, which has no equivalent hint).
#ifdef __has_cpp_attribute
#if __has_cpp_attribute(unlikely) >= 201803L
// g++-9 hits hits this branch, but then fails to compile
// [[unlikely]]. This happens only with g++-9.
#if !defined(__GNUC__) || (__GNUC__ != 9)
#define FASTFLOAT_USE_UNLIKELY_ATTR
#endif
#endif
#endif
#ifdef FASTFLOAT_USE_UNLIKELY_ATTR
#define fastfloat_unlikely(x) (x) [[unlikely]]
#elif defined(__GNUC__) || defined(__clang__)
#define fastfloat_unlikely(x) (__builtin_expect(!!(x), 0))
#else
#define fastfloat_unlikely(x) (x)
#endif
#ifndef FASTFLOAT_ASSERT #ifndef FASTFLOAT_ASSERT
#define FASTFLOAT_ASSERT(x) \ #define FASTFLOAT_ASSERT(x) \
{ ((void)(x)); } { ((void)(x)); }

View File

@ -289,6 +289,23 @@ from_chars_advanced(parsed_number_string_t<UC> &pns, T &value) noexcept {
return answer; return answer;
} }
// Slow path: re-parse materializing the integer/fraction spans the hot no-span
// parse skipped, then run the full algorithm. The two callers reach it only
// through a fastfloat_unlikely branch, so the optimizer keeps this re-parse off
// the hot path on its own (no function-level noinline needed).
// from_chars_advanced already handles both the too_many_digits disambiguation
// and the am.power2<0 digit_comp recompute, so both slow branches collapse to
// one helper call.
template <typename T, typename UC>
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
parse_number_slow_path(UC const *first, UC const *last, T &value,
parse_options_t<UC> options, bool bjf) noexcept {
parsed_number_string_t<UC> pns =
bjf ? parse_number_string<true, UC>(first, last, options, true)
: parse_number_string<false, UC>(first, last, options, true);
return from_chars_advanced(pns, value);
}
template <typename T, typename UC> template <typename T, typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC> fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
from_chars_float_advanced(UC const *first, UC const *last, T &value, from_chars_float_advanced(UC const *first, UC const *last, T &value,
@ -312,10 +329,15 @@ from_chars_float_advanced(UC const *first, UC const *last, T &value,
answer.ptr = first; answer.ptr = first;
return answer; return answer;
} }
bool const bjf = uint64_t(fmt & detail::basic_json_fmt) != 0;
// 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,
// so this reuses the single parse_number_string instantiation.
parsed_number_string_t<UC> pns = parsed_number_string_t<UC> pns =
uint64_t(fmt & detail::basic_json_fmt) bjf ? parse_number_string<true, UC>(first, last, options, false)
? parse_number_string<true, UC>(first, last, options) : parse_number_string<false, UC>(first, last, options, false);
: parse_number_string<false, UC>(first, last, options);
if (!pns.valid) { if (!pns.valid) {
if (uint64_t(fmt & chars_format::no_infnan)) { if (uint64_t(fmt & chars_format::no_infnan)) {
answer.ec = std::errc::invalid_argument; answer.ec = std::errc::invalid_argument;
@ -326,8 +348,49 @@ from_chars_float_advanced(UC const *first, UC const *last, T &value,
} }
} }
// call overload that takes parsed_number_string_t directly. // Slow path A (rare): > 19 significant digits. The no-span parse left the
return from_chars_advanced(pns, value); // mantissa un-truncated and skipped the span-based recompute; the cold helper
// re-parses with spans and runs the full algorithm.
//
// We have to disable -Wc++20-extensions for the [[unlikely]] attribute
// See comment for @jwakely at
// https://github.com/fastfloat/fast_float/pull/387#discussion_r3366943539
// This is unfortunate.
#ifdef __clang__
#pragma clang diagnostic push
#if (!defined(__APPLE_CC__) && __clang_major__ >= 10) || (__clang_major__ >= 13)
#pragma clang diagnostic ignored "-Wc++20-extensions"
#endif
#endif
if fastfloat_unlikely (pns.too_many_digits) {
return parse_number_slow_path<T, UC>(first, last, value, options, bjf);
}
answer.ec = std::errc(); // be optimistic
answer.ptr = pns.lastmatch;
if (clinger_fast_path_impl(pns.mantissa, pns.exponent, pns.negative, value)) {
return answer;
}
adjusted_mantissa am =
compute_float<binary_format<T>>(pns.exponent, pns.mantissa);
// Slow path B (rare): Eisel-Lemire could not resolve; digit_comp needs the
// integer/fraction spans. Route to the cold helper (clinger there is a
// dead-effect since it already failed here; the cold re-parse + digit_comp
// via from_chars_advanced reproduces this branch).
if fastfloat_unlikely (am.power2 < 0) {
return parse_number_slow_path<T, UC>(first, last, value, options, bjf);
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif
to_float(pns.negative, am, value);
// Test for over/underflow.
if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) ||
am.power2 == binary_format<T>::infinite_power()) {
answer.ec = std::errc::result_out_of_range;
}
return answer;
} }
template <typename T, typename UC, typename> template <typename T, typename UC, typename>

View File

@ -17,7 +17,10 @@
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <string_view> #include <string_view>
#include <string>
#include <cstring> #include <cstring>
#include <random>
#include <algorithm>
#include "fast_float/fast_float.h" #include "fast_float/fast_float.h"
#include <cstdint> #include <cstdint>
@ -821,6 +824,61 @@ int main() {
++base_unsigned; ++base_unsigned;
} }
// unsigned out of range error base test, multi-wrap (64 bit)
// These values overflow uint64_t, but the accumulator wraps a whole multiple
// of 2^64 and lands back at or above the smallest max_digits-length value, so
// a single comparison against that bound does not catch the overflow. Bases
// 2, 4 and 16 are excluded because their max_digits-length range fits within
// a single 2^64 span.
std::vector<int> const unsigned_multiwrap_base{
3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36};
std::vector<std::string_view> const unsigned_multiwrap_base_test{
"22222222222222222222222222222222222222222",
"4400000000000000000000000000",
"5555555555555555555555555",
"66666666666666666666666",
"7777777777777777777777",
"888888888888888888888",
"46893488147419103233",
"AAAAAAAAAAAAAAAAAAA",
"BBBBBBBBBBBBBBBBBB",
"427772311192C9BAAB",
"DDDDDDDDDDDDDDDDD",
"532C82996D3A44919",
"GGGGGGGGGGGGGGGG",
"HHHHHHHHHHHHHHHH",
"3835GEGDF36622EG",
"JJJJJJJJJJJJJJJ",
"KKKKKKKKKKKKKKK",
"LLLLLLLLLLLLLLL",
"444BGHB4EG5DA2D",
"NNNNNNNNNNNNNN",
"JE5H4MNDLJGNLO",
"PPPPPPPPPPPPPP",
"QQQQQQQQQQQQQQ",
"RRRRRRRRRRRRRR",
"4H7QS52310IHQK",
"TTTTTTTTTTTTTT",
"UUUUUUUUUUUUU",
"VVVVVVVVVVVVV",
"WWWWWWWWWWWWW",
"XXXXXXXXXXXXX",
"YYYYYYYYYYYYY",
"6U831JL976P6O"};
for (std::size_t i = 0; i < unsigned_multiwrap_base_test.size(); ++i) {
auto const &f = unsigned_multiwrap_base_test[i];
uint64_t result;
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result,
unsigned_multiwrap_base[i]);
if (answer.ec != std::errc::result_out_of_range) {
std::cerr << "expected error for should be 'result_out_of_range': \"" << f
<< "\"" << std::endl;
return EXIT_FAILURE;
}
}
// just within range base test (64 bit) // just within range base test (64 bit)
std::vector<std::string_view> const int_within_range_base_test{ std::vector<std::string_view> const int_within_range_base_test{
"111111111111111111111111111111111111111111111111111111111111111", "111111111111111111111111111111111111111111111111111111111111111",
@ -1295,6 +1353,330 @@ int main() {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
} }
// The uint8_t and uint16_t base-10 paths use a byte-oriented fast path. A
// wider code unit whose low byte is an ASCII digit (e.g. U+2131..U+2139) must
// not be mistaken for that digit. The generic path already rejects these for
// int; the fixed-width fast paths must agree. Lengths of 1..5 exercise both
// the uint8_t path and the 4-digit uint16_t SWAR path.
{
const std::u16string bad16[] = {
u"", u"ℱℲ", u"ℱℲℳ", u"ℱℲℳℴ", u"ℱℲℳℴℵ",
};
const std::u32string bad32[] = {
U"",
U"ℱℲℳ",
U"ℱℲℳℴ",
U"ℱℲℳℴℵ",
};
bool failed = false;
for (auto const &s : bad16) {
uint8_t r8 = 123;
auto a8 = fast_float::from_chars(s.data(), s.data() + s.size(), r8);
if (a8.ec == std::errc()) {
failed = true;
std::cerr << "Incorrectly parsed wide units as uint8_t " << unsigned(r8)
<< "." << std::endl;
}
uint16_t r16 = 123;
auto a16 = fast_float::from_chars(s.data(), s.data() + s.size(), r16);
if (a16.ec == std::errc()) {
failed = true;
std::cerr << "Incorrectly parsed wide units as uint16_t " << r16 << "."
<< std::endl;
}
}
for (auto const &s : bad32) {
uint8_t r8 = 123;
auto a8 = fast_float::from_chars(s.data(), s.data() + s.size(), r8);
if (a8.ec == std::errc()) {
failed = true;
std::cerr << "Incorrectly parsed wide units as uint8_t " << unsigned(r8)
<< "." << std::endl;
}
uint16_t r16 = 123;
auto a16 = fast_float::from_chars(s.data(), s.data() + s.size(), r16);
if (a16.ec == std::errc()) {
failed = true;
std::cerr << "Incorrectly parsed wide units as uint16_t " << r16 << "."
<< std::endl;
}
}
if (failed) {
return EXIT_FAILURE;
}
}
// Comprehensive, oracle-checked u64 overflow detection across every base.
//
// The accumulator in parse_int_string is allowed to overflow and the result
// is validated afterwards. At the max_digits boundary a value can wrap one or
// more whole multiples of 2^64 (a 20-digit base-10 number reaches ~5.4*2^64),
// so the boundary check must be exact. This section validates from_chars for
// bases 2..36 against an independent, trusted oracle: a plain 64-bit checked
// multiply-add. It hammers the single leading-digit band that straddles 2^64
// (where wrapped and non-wrapped values are hardest to tell apart) and also
// covers max_digits-1 (always in range) and max_digits+1 (always overflow).
{
auto digit_to_char = [](int d) -> char {
return d < 10 ? char('0' + d) : char('A' + (d - 10));
};
auto char_to_digit = [](char c) -> int {
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'A' && c <= 'Z') {
return c - 'A' + 10;
}
return c - 'a' + 10;
};
// Trusted oracle: parse `s` in `base` with a checked 64-bit multiply-add.
// Returns true on u64 overflow; otherwise writes the value to `out`.
auto oracle = [&](std::string const &s, int base, uint64_t &out) -> bool {
uint64_t v = 0;
for (char c : s) {
uint64_t const d = uint64_t(char_to_digit(c));
if (v > (UINT64_MAX - d) / uint64_t(base)) {
return true;
}
v = uint64_t(base) * v + d;
}
out = v;
return false;
};
auto to_base = [&](uint64_t v, int base) -> std::string {
if (v == 0) {
return "0";
}
std::string s;
while (v != 0) {
s += digit_to_char(int(v % uint64_t(base)));
v /= uint64_t(base);
}
std::reverse(s.begin(), s.end());
return s;
};
// Add one (in base `base`) to the digit string `s`, carrying as needed.
auto increment = [&](std::string s, int base) -> std::string {
int carry = 1;
for (std::size_t k = s.size(); k-- > 0 && carry != 0;) {
int const d = char_to_digit(s[k]) + carry;
carry = d / base;
s[k] = digit_to_char(d % base);
}
if (carry != 0) {
s.insert(s.begin(), digit_to_char(carry));
}
return s;
};
// Subtract one (in base `base`) from a non-zero, non-negative string.
auto decrement = [&](std::string s, int base) -> std::string {
int borrow = 1;
for (std::size_t k = s.size(); k-- > 0 && borrow != 0;) {
int d = char_to_digit(s[k]) - borrow;
borrow = d < 0 ? 1 : 0;
if (d < 0) {
d += base;
}
s[k] = digit_to_char(d);
}
std::size_t lead = s.find_first_not_of('0'); // drop any leading zero
return lead == std::string::npos ? "0" : s.substr(lead);
};
std::mt19937_64 rng(0xC0FFEEULL);
long long checked = 0;
auto verify = [&](std::string const &s, int base) -> bool {
uint64_t expected = 0;
bool const ov = oracle(s, base, expected);
uint64_t result = 0xDEADBEEFULL;
auto answer =
fast_float::from_chars(s.data(), s.data() + s.size(), result, base);
++checked;
if (ov) {
if (answer.ec != std::errc::result_out_of_range) {
std::cerr << "base " << base
<< ": expected result_out_of_range for \"" << s << "\""
<< std::endl;
return false;
}
} else {
if (answer.ec != std::errc()) {
std::cerr << "base " << base << ": unexpected error for \"" << s
<< "\"" << std::endl;
return false;
}
if (result != expected) {
std::cerr << "base " << base << ": \"" << s << "\" -> " << result
<< ", expected " << expected << std::endl;
return false;
}
if (answer.ptr != s.data() + s.size()) {
std::cerr << "base " << base << ": did not consume all of \"" << s
<< "\"" << std::endl;
return false;
}
}
return true;
};
// Leading zeros are stripped before the digit count, so the outcome must be
// unchanged. Checked only on hand-picked values (it exercises shared code).
auto verify_zeros = [&](std::string const &digits, int base) -> bool {
return verify(digits, base) && verify("0" + digits, base) &&
verify(std::string(40, '0') + digits, base);
};
auto random_tail = [&](std::string &s, int n, int base) {
for (int k = 0; k < n; ++k) {
// bias toward the extremes (0 and base-1) to hit boundaries often
std::uint64_t const r = rng();
int const mode = int(r % 4);
int const dig = mode == 0 ? 0
: mode == 1 ? base - 1
: int((r >> 2) % std::uint64_t(base));
s += digit_to_char(dig);
}
};
for (int base = 2; base <= 36; ++base) {
// M = max number of base-`base` digits a u64 can hold.
std::string const maxstr = to_base(UINT64_MAX, base);
int const M = int(maxstr.size());
// b^(M-1): smallest M-digit value, and width of each leading-digit band.
uint64_t bM1 = 1;
for (int k = 0; k < M - 1; ++k) {
bM1 *= uint64_t(base);
}
int const dmax = int(UINT64_MAX / bM1); // largest leading digit that fits
// Exact-boundary sweep straddling 2^64 (the hardest transition): the
// 64 values UINT64_MAX-31 .. UINT64_MAX (in range) and 2^64 .. 2^64+31
// (overflow), built by walking the digit string up and down.
std::string below = maxstr, above = increment(maxstr, base);
for (int k = 0; k < 32; ++k) {
if (!verify(below, base) || !verify(above, base)) {
return EXIT_FAILURE;
}
below = decrement(below, base);
above = increment(above, base);
}
// Hand-picked values, also checked with leading zeros.
std::string const allmax(std::size_t(M), digit_to_char(base - 1));
if (!verify_zeros(maxstr, base) || // largest in-range value
!verify_zeros(increment(maxstr, base), base) || // smallest overflow
!verify_zeros(allmax, base)) { // largest M-digit (multi-wrap)
return EXIT_FAILURE;
}
// Randomized M-digit values across every leading digit. Bands with
// lead > dmax always overflow (this is where the naive min_safe check
// wrongly accepted multi-wrap values); lead < dmax always fits; lead ==
// dmax straddles 2^64 and gets the heaviest sampling.
for (int lead = 1; lead < base; ++lead) {
int const trials = lead == dmax ? 4000 : 300;
for (int trial = 0; trial < trials; ++trial) {
std::string s(1, digit_to_char(lead));
random_tail(s, M - 1, base);
if (!verify(s, base)) {
return EXIT_FAILURE;
}
}
}
// max_digits-1 digits never overflow; max_digits+1 digits always do.
for (int trial = 0; trial < 500; ++trial) {
std::string shorts(1,
digit_to_char(1 + int(rng() % uint64_t(base - 1))));
random_tail(shorts, M - 2, base);
std::string longs(1,
digit_to_char(1 + int(rng() % uint64_t(base - 1))));
random_tail(longs, M, base);
if (!verify(shorts, base) || !verify(longs, base)) {
return EXIT_FAILURE;
}
}
}
if (checked < 100000) {
std::cerr << "overflow sweep ran too few cases: " << checked << std::endl;
return EXIT_FAILURE;
}
}
// Signed (int64_t) boundary: every value that overflows u64 also overflows
// i64, and the exact i64 limits must parse. Reuses the oracle indirectly via
// hand-built extremes per base.
{
auto digit_to_char = [](int d) -> char {
return d < 10 ? char('0' + d) : char('A' + (d - 10));
};
auto to_base_signed = [&](int64_t value, int base) -> std::string {
// value may be INT64_MIN; accumulate magnitude in u64 to avoid UB.
bool const neg = value < 0;
uint64_t mag = neg ? (~uint64_t(value) + 1) : uint64_t(value);
std::string s;
if (mag == 0) {
s += '0';
}
while (mag != 0) {
s += digit_to_char(int(mag % uint64_t(base)));
mag /= uint64_t(base);
}
if (neg) {
s += '-';
}
std::reverse(s.begin(), s.end());
return s;
};
for (int base = 2; base <= 36; ++base) {
struct {
int64_t v;
} const limits[] = {{INT64_MAX}, {INT64_MIN}, {0}, {-1}, {1}};
for (auto const &lim : limits) {
std::string const s = to_base_signed(lim.v, base);
int64_t result = 123;
auto answer =
fast_float::from_chars(s.data(), s.data() + s.size(), result, base);
if (answer.ec != std::errc() || result != lim.v) {
std::cerr << "base " << base << ": signed limit \"" << s
<< "\" failed to round-trip (got " << result << ")"
<< std::endl;
return EXIT_FAILURE;
}
}
// Increment a non-negative magnitude string (in `base`) by one.
auto inc_mag = [&](std::string m) -> std::string {
int carry = 1;
for (std::size_t k = m.size(); k-- > 0 && carry != 0;) {
int d = (m[k] >= '0' && m[k] <= '9') ? m[k] - '0'
: (m[k] >= 'A' && m[k] <= 'Z') ? m[k] - 'A' + 10
: m[k] - 'a' + 10;
d += carry;
carry = d / base;
m[k] = digit_to_char(d % base);
}
if (carry != 0) {
m.insert(m.begin(), digit_to_char(carry));
}
return m;
};
// INT64_MAX + 1 (= 2^63) overflows a positive int64_t.
// INT64_MIN - 1 (= -(2^63 + 1)) overflows a negative int64_t.
// Note that -(2^63) == INT64_MIN is in range and is covered above.
std::string const max_mag = to_base_signed(INT64_MAX, base); // 2^63 - 1
std::string const over = inc_mag(max_mag); // 2^63
std::string const under = "-" + inc_mag(over); // -(2^63 + 1)
for (std::string const &s : {over, under}) {
int64_t result = 123;
auto answer =
fast_float::from_chars(s.data(), s.data() + s.size(), result, base);
if (answer.ec != std::errc::result_out_of_range) {
std::cerr << "base " << base << ": expected result_out_of_range for "
<< "signed \"" << s << "\"" << std::endl;
return EXIT_FAILURE;
}
}
}
}
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -65,6 +65,4 @@ int main() {
#endif #endif
std::cout << "All tests passed successfully." << std::endl; std::cout << "All tests passed successfully." << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
return 0;
} }

View File

@ -45,11 +45,6 @@ void all_32bit_values() {
std::cerr << "I got " << std::hexfloat << result_value std::cerr << "I got " << std::hexfloat << result_value
<< " but I was expecting " << v << std::endl; << " but I was expecting " << v << std::endl;
abort(); abort();
} else if (std::isnan(v)) {
if (!std::isnan(result_value)) {
std::cerr << "not nan" << buffer << std::endl;
abort();
}
} else if (result_value != v) { } else if (result_value != v) {
std::cerr << "no match ? " << buffer << std::endl; std::cerr << "no match ? " << buffer << std::endl;
std::cout << "started with " << std::hexfloat << v << std::endl; std::cout << "started with " << std::hexfloat << v << std::endl;