mirror of
https://github.com/fastfloat/fast_float.git
synced 2026-06-15 08:26:08 +08:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34164f547b | ||
|
|
4eec7bec38 | ||
|
|
fd970ab05e | ||
|
|
a7249f86ed | ||
|
|
632cc97b5b | ||
|
|
8234a89623 | ||
|
|
0dce102cb4 | ||
|
|
30868f8734 | ||
|
|
8e6edc8ad2 | ||
|
|
82882b237d | ||
|
|
937198691a | ||
|
|
0352ba3fef | ||
|
|
6ae691372f | ||
|
|
8fe7a9405b | ||
|
|
e8ec8e8f34 | ||
|
|
c05156ff60 | ||
|
|
23e245f2b3 | ||
|
|
e0b53eaf63 | ||
|
|
3044c9b182 | ||
|
|
29bd11571b | ||
|
|
b1fbfe932a | ||
|
|
520fded4a3 | ||
|
|
b72e07132c | ||
|
|
3067491f41 | ||
|
|
cb5d9cd9a4 | ||
|
|
6258cbc5a1 | ||
|
|
254f10ce39 | ||
|
|
1b11407da9 | ||
|
|
f0ed8cdf52 | ||
|
|
b3ec8d89cf |
4
.github/workflows/emscripten.yml
vendored
4
.github/workflows/emscripten.yml
vendored
@ -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
|
||||||
|
|||||||
2
.github/workflows/lint_and_format_check.yml
vendored
2
.github/workflows/lint_and_format_check.yml
vendored
@ -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'
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/vs17-arm-ci.yml
vendored
4
.github/workflows/vs17-arm-ci.yml
vendored
@ -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
|
||||||
|
|||||||
8
.github/workflows/vs17-ci.yml
vendored
8
.github/workflows/vs17-ci.yml
vendored
@ -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
|
||||||
|
|||||||
8
.github/workflows/vs17-clang-ci.yml
vendored
8
.github/workflows/vs17-clang-ci.yml
vendored
@ -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
|
||||||
|
|||||||
8
.github/workflows/vs17-cxx20.yml
vendored
8
.github/workflows/vs17-cxx20.yml
vendored
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)); }
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
}
|
||||||
@ -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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user