3rdparty: Update fast_float to v8.0.2

This commit is contained in:
JordanTheToaster 2025-06-29 06:28:47 +01:00 committed by Ty
parent 377930d004
commit 8bcc2c94b9
11 changed files with 2627 additions and 1763 deletions

View File

@ -8,3 +8,4 @@ Lénárd Szolnoki
Jan Pharago Jan Pharago
Maya Warrier Maya Warrier
Taha Khokhar Taha Khokhar
Anders Dalvander

View File

@ -1,160 +1,233 @@
## fast_float number parsing library: 4x faster than strtod ## fast_float number parsing library: 4x faster than strtod
[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fast_float.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:fast_float)
[![Ubuntu 22.04 CI (GCC 11)](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml/badge.svg)](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml) [![Ubuntu 22.04 CI (GCC 11)](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml/badge.svg)](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml)
The fast_float library provides fast header-only implementations for the C++ from_chars The fast_float library provides fast header-only implementations for the C++
functions for `float` and `double` types as well as integer types. These functions convert ASCII strings representing decimal values (e.g., `1.3e10`) into binary types. We provide exact rounding (including from_chars functions for `float` and `double` types as well as integer types.
round to even). In our experience, these `fast_float` functions many times faster than comparable number-parsing functions from existing C++ standard libraries. These functions convert ASCII strings representing decimal values (e.g.,
`1.3e10`) into binary types. We provide exact rounding (including round to
even). In our experience, these `fast_float` functions many times faster than
comparable number-parsing functions from existing C++ standard libraries.
Specifically, `fast_float` provides the following two functions to parse floating-point numbers with a C++17-like syntax (the library itself only requires C++11): Specifically, `fast_float` provides the following two functions to parse
floating-point numbers with a C++17-like syntax (the library itself only
requires C++11):
```C++ ```C++
from_chars_result from_chars(const char* first, const char* last, float& value, ...); from_chars_result from_chars(char const *first, char const *last, float &value, ...);
from_chars_result from_chars(const char* first, const char* last, double& value, ...); from_chars_result from_chars(char const *first, char const *last, double &value, ...);
``` ```
You can also parse integer types: You can also parse integer types:
```C++
from_chars_result from_chars(char const *first, char const *last, int &value, ...);
from_chars_result from_chars(char const *first, char const *last, unsigned &value, ...);
```
The return type (`from_chars_result`) is defined as the struct: The return type (`from_chars_result`) is defined as the struct:
```C++ ```C++
struct from_chars_result { struct from_chars_result {
const char* ptr; char const *ptr;
std::errc ec; std::errc ec;
}; };
``` ```
It parses the character sequence [first,last) for a number. It parses floating-point numbers expecting It parses the character sequence `[first, last)` for a number. It parses
a locale-independent format equivalent to the C++17 from_chars function. floating-point numbers expecting a locale-independent format equivalent to the
The resulting floating-point value is the closest floating-point values (using either float or double), C++17 from_chars function. The resulting floating-point value is the closest
using the "round to even" convention for values that would otherwise fall right in-between two values. floating-point values (using either `float` or `double`), using the "round to
That is, we provide exact parsing according to the IEEE standard. even" convention for values that would otherwise fall right in-between two
values. That is, we provide exact parsing according to the IEEE standard.
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
parsed value. In case of error, the returned `ec` contains a representative
error, otherwise the default (`std::errc()`) value is stored.
Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the The implementation does not throw and does not allocate memory (e.g., with `new`
parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned or `malloc`).
`ec` contains a representative error, otherwise the default (`std::errc()`) value is stored.
The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`).
It will parse infinity and nan values. It will parse infinity and nan values.
Example: Example:
``` C++ ```C++
#include "fast_float/fast_float.h" #include "fast_float/fast_float.h"
#include <iostream> #include <iostream>
int main() { int main() {
const std::string input = "3.1416 xyz "; std::string input = "3.1416 xyz ";
double result; double result;
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result); auto answer = fast_float::from_chars(input.data(), input.data() + input.size(), result);
if(answer.ec != std::errc()) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; } if (answer.ec != std::errc()) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
std::cout << "parsed the number " << result << std::endl; std::cout << "parsed the number " << result << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
You can parse delimited numbers: You can parse delimited numbers:
```C++ ```C++
const std::string input = "234532.3426362,7869234.9823,324562.645"; std::string input = "234532.3426362,7869234.9823,324562.645";
double result; double result;
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result); auto answer = fast_float::from_chars(input.data(), input.data() + input.size(), result);
if(answer.ec != std::errc()) { if (answer.ec != std::errc()) {
// check error // check error
} }
// we have result == 234532.3426362. // we have result == 234532.3426362.
if(answer.ptr[0] != ',') { if (answer.ptr[0] != ',') {
// unexpected delimiter // unexpected delimiter
} }
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result); answer = fast_float::from_chars(answer.ptr + 1, input.data() + input.size(), result);
if(answer.ec != std::errc()) { if (answer.ec != std::errc()) {
// check error // check error
} }
// we have result == 7869234.9823. // we have result == 7869234.9823.
if(answer.ptr[0] != ',') { if (answer.ptr[0] != ',') {
// unexpected delimiter // unexpected delimiter
} }
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result); answer = fast_float::from_chars(answer.ptr + 1, input.data() + input.size(), result);
if(answer.ec != std::errc()) { if (answer.ec != std::errc()) {
// check error // check error
} }
// we have result == 324562.645. // we have result == 324562.645.
``` ```
Like the C++17 standard, the `fast_float::from_chars` functions take an optional
last argument of the type `fast_float::chars_format`. It is a bitset value: we
check whether `fmt & fast_float::chars_format::fixed` and `fmt &
fast_float::chars_format::scientific` are set to determine whether we allow the
fixed point and scientific notation respectively. The default is
`fast_float::chars_format::general` which allows both `fixed` and `scientific`.
The library seeks to follow the C++17 (see
[28.2.3.(6.1)](https://eel.is/c++draft/charconv.from.chars#6.1)) specification.
Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of * The `from_chars` function does not skip leading white-space characters (unless
the type `fast_float::chars_format`. It is a bitset value: we check whether `fast_float::chars_format::skip_white_space` is set).
`fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set * [A leading `+` sign](https://en.cppreference.com/w/cpp/utility/from_chars) is
to determine whether we allow the fixed point and scientific notation respectively. forbidden (unless `fast_float::chars_format::allow_leading_plus` is set).
The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. * It is generally impossible to represent a decimal value exactly as binary
floating-point number (`float` and `double` types). We seek the nearest value.
The library seeks to follow the C++17 (see [20.19.3](http://eel.is/c++draft/charconv.from.chars).(7.1)) specification. We round to an even mantissa when we are in-between two binary floating-point
* The `from_chars` function does not skip leading white-space characters. numbers.
* [A leading `+` sign](https://en.cppreference.com/w/cpp/utility/from_chars) is forbidden.
* It is generally impossible to represent a decimal value exactly as binary floating-point number (`float` and `double` types). We seek the nearest value. We round to an even mantissa when we are in-between two binary floating-point numbers.
Furthermore, we have the following restrictions: Furthermore, we have the following restrictions:
* We only support `float` and `double` types at this time.
* We support `float` and `double`, but not `long double`. We also support
fixed-width floating-point types such as `std::float64_t`, `std::float32_t`,
`std::float16_t`, and `std::bfloat16_t`.
* We only support the decimal format: we do not support hexadecimal strings. * We only support the decimal format: we do not support hexadecimal strings.
* For values that are either very large or very small (e.g., `1e9999`), we represent it using the infinity or negative infinity value and the returned `ec` is set to `std::errc::result_out_of_range`. * For values that are either very large or very small (e.g., `1e9999`), we
represent it using the infinity or negative infinity value and the returned
`ec` is set to `std::errc::result_out_of_range`.
We support Visual Studio, macOS, Linux, freeBSD. We support big and little endian. We support 32-bit and 64-bit systems. We support Visual Studio, macOS, Linux, freeBSD. We support big and little
endian. We support 32-bit and 64-bit systems.
We assume that the rounding mode is set to nearest (`std::fegetround() == FE_TONEAREST`).
We assume that the rounding mode is set to nearest (`std::fegetround() ==
FE_TONEAREST`).
## Integer types ## Integer types
You can also parse integer types using different bases (e.g., 2, 10, 16). The following code will You can also parse integer types using different bases (e.g., 2, 10, 16). The
print the number 22250738585072012 three times: following code will print the number 22250738585072012 three times:
```C++ ```C++
#include "fast_float/fast_float.h"
#include <iostream>
int main() {
uint64_t i; uint64_t i;
const char str[] = "22250738585072012"; std::string str = "22250738585072012";
auto answer = fast_float::from_chars(str, str + strlen(str), i); auto answer = fast_float::from_chars(str.data(), str.data() + str.size(), i);
if (answer.ec != std::errc()) { if (answer.ec != std::errc()) {
std::cerr << "parsing failure\n"; std::cerr << "parsing failure\n";
return EXIT_FAILURE; return EXIT_FAILURE;
} }
std::cout << "parsed the number "<< i << std::endl; std::cout << "parsed the number " << i << std::endl;
const char binstr[] = "1001111000011001110110111001001010110100111000110001100"; std::string binstr = "1001111000011001110110111001001010110100111000110001100";
answer = fast_float::from_chars(binstr, binstr + strlen(binstr), i, 2); answer = fast_float::from_chars(binstr.data(), binstr.data() + binstr.size(), i, 2);
if (answer.ec != std::errc()) { if (answer.ec != std::errc()) {
std::cerr << "parsing failure\n"; std::cerr << "parsing failure\n";
return EXIT_FAILURE; return EXIT_FAILURE;
} }
std::cout << "parsed the number "<< i << std::endl; std::cout << "parsed the number " << i << std::endl;
std::string hexstr = "4f0cedc95a718c";
const char hexstr[] = "4f0cedc95a718c"; answer = fast_float::from_chars(hexstr.data(), hexstr.data() + hexstr.size(), i, 16);
answer = fast_float::from_chars(hexstr, hexstr + strlen(hexstr), i, 16);
if (answer.ec != std::errc()) { if (answer.ec != std::errc()) {
std::cerr << "parsing failure\n"; std::cerr << "parsing failure\n";
return EXIT_FAILURE; return EXIT_FAILURE;
} }
std::cout << "parsed the number "<< i << std::endl; std::cout << "parsed the number " << i << std::endl;
return EXIT_SUCCESS;
}
```
## Behavior of result_out_of_range
When parsing floating-point values, the numbers can sometimes be too small
(e.g., `1e-1000`) or too large (e.g., `1e1000`). The C language established the
precedent that these small values are out of range. In such cases, it is
customary to parse small values to zero and large values to infinity. That is
the behaviour of the C language (e.g., `stdtod`). That is the behaviour followed
by the fast_float library.
Specifically, we follow Jonathan Wakely's interpretation of the standard:
> In any case, the resulting value is one of at most two floating-point values
> closest to the value of the string matching the pattern.
It is also the approach taken by the [Microsoft C++
library](https://github.com/microsoft/STL/blob/62205ab155d093e71dd9588a78f02c5396c3c14b/tests/std/tests/P0067R5_charconv/test.cpp#L943-L946).
Hence, we have the following examples:
```cpp
double result = -1;
std::string str = "3e-1000";
auto r = fast_float::from_chars(str.data(), str.data() + str.size(), result);
// r.ec == std::errc::result_out_of_range
// r.ptr == str.data() + 7
// result == 0
```
```cpp
double result = -1;
std::string str = "3e1000";
auto r = fast_float::from_chars(str.data(), str.data() + str.size(), result);
// r.ec == std::errc::result_out_of_range
// r.ptr == str.data() + 6
// result == std::numeric_limits<double>::infinity()
```
Users who wish for the value to be left unmodified given
`std::errc::result_out_of_range` may do so by adding two lines of code:
```cpp
double old_result = result; // make copy
auto r = fast_float::from_chars(start, end, result);
if (r.ec == std::errc::result_out_of_range) { result = old_result; }
``` ```
## C++20: compile-time evaluation (constexpr) ## C++20: compile-time evaluation (constexpr)
In C++20, you may use `fast_float::from_chars` to parse strings In C++20, you may use `fast_float::from_chars` to parse strings at compile-time,
at compile-time, as in the following example: as in the following example:
```C++ ```C++
// consteval forces compile-time evaluation of the function in C++20. // consteval forces compile-time evaluation of the function in C++20.
consteval double parse(std::string_view input) { consteval double parse(std::string_view input) {
double result; double result;
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result); auto answer = fast_float::from_chars(input.data(), input.data() + input.size(), result);
if(answer.ec != std::errc()) { return -1.0; } if (answer.ec != std::errc()) { return -1.0; }
return result; return result;
} }
@ -167,108 +240,107 @@ constexpr double constexptest() {
## C++23: Fixed width floating-point types ## C++23: Fixed width floating-point types
The library also supports fixed-width floating-point types such as `std::float32_t` and `std::float64_t`. E.g., you can write: The library also supports fixed-width floating-point types such as
`std::float64_t`, `std::float32_t`, `std::float16_t`, and `std::bfloat16_t`.
E.g., you can write:
```C++ ```C++
std::float32_t result; std::float32_t result;
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
`````` ```
## Non-ASCII Inputs ## Non-ASCII Inputs
We also support UTF-16 and UTF-32 inputs, as well as ASCII/UTF-8, as in the following example: We also support UTF-16 and UTF-32 inputs, as well as ASCII/UTF-8, as in the
following example:
``` C++ ```C++
#include "fast_float/fast_float.h" #include "fast_float/fast_float.h"
#include <iostream> #include <iostream>
int main() { int main() {
const std::u16string input = u"3.1416 xyz "; std::u16string input = u"3.1416 xyz ";
double result; double result;
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result); auto answer = fast_float::from_chars(input.data(), input.data() + input.size(), result);
if(answer.ec != std::errc()) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; } if (answer.ec != std::errc()) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
std::cout << "parsed the number " << result << std::endl; std::cout << "parsed the number " << result << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
## Advanced options: using commas as decimal separator, JSON and Fortran ## Advanced options: using commas as decimal separator, JSON and Fortran
The C++ standard stipulate that `from_chars` has to be locale-independent. In The C++ standard stipulate that `from_chars` has to be locale-independent. In
particular, the decimal separator has to be the period (`.`). However, particular, the decimal separator has to be the period (`.`). However, some
some users still want to use the `fast_float` library with in a locale-dependent users still want to use the `fast_float` library with in a locale-dependent
manner. Using a separate function called `from_chars_advanced`, we allow the users manner. Using a separate function called `from_chars_advanced`, we allow the
to pass a `parse_options` instance which contains a custom decimal separator (e.g., users to pass a `parse_options` instance which contains a custom decimal
the comma). You may use it as follows. separator (e.g., the comma). You may use it as follows.
```C++ ```C++
#include "fast_float/fast_float.h" #include "fast_float/fast_float.h"
#include <iostream> #include <iostream>
int main() { int main() {
const std::string input = "3,1416 xyz "; std::string input = "3,1416 xyz ";
double result; double result;
fast_float::parse_options options{fast_float::chars_format::general, ','}; fast_float::parse_options options{fast_float::chars_format::general, ','};
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); auto answer = fast_float::from_chars_advanced(input.data(), input.data() + input.size(), result, options);
if((answer.ec != std::errc()) || ((result != 3.1416))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; } if ((answer.ec != std::errc()) || ((result != 3.1416))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
std::cout << "parsed the number " << result << std::endl; std::cout << "parsed the number " << result << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
You can also parse Fortran-like inputs: ### You can also parse Fortran-like inputs
```C++ ```C++
#include "fast_float/fast_float.h" #include "fast_float/fast_float.h"
#include <iostream> #include <iostream>
int main() { int main() {
const std::string input = "1d+4"; std::string input = "1d+4";
double result; double result;
fast_float::parse_options options{ fast_float::chars_format::fortran }; fast_float::parse_options options{fast_float::chars_format::fortran};
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); auto answer = fast_float::from_chars_advanced(input.data(), input.data() + input.size(), result, options);
if((answer.ec != std::errc()) || ((result != 10000))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; } if ((answer.ec != std::errc()) || ((result != 10000))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
std::cout << "parsed the number " << result << std::endl; std::cout << "parsed the number " << result << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
You may also enforce the JSON format ([RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259#section-6)): ### You may also enforce the JSON format ([RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259#section-6))
```C++ ```C++
#include "fast_float/fast_float.h" #include "fast_float/fast_float.h"
#include <iostream> #include <iostream>
int main() { int main() {
const std::string input = "+.1"; // not valid std::string input = "+.1"; // not valid
double result; double result;
fast_float::parse_options options{ fast_float::chars_format::json }; fast_float::parse_options options{fast_float::chars_format::json};
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); auto answer = fast_float::from_chars_advanced(input.data(), input.data() + input.size(), result, options);
if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; } if (answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; }
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
By default the JSON format does not allow `inf`: By default the JSON format does not allow `inf`:
```C++ ```C++
#include "fast_float/fast_float.h" #include "fast_float/fast_float.h"
#include <iostream> #include <iostream>
int main() { int main() {
const std::string input = "inf"; // not valid in JSON std::string input = "inf"; // not valid in JSON
double result; double result;
fast_float::parse_options options{ fast_float::chars_format::json }; fast_float::parse_options options{fast_float::chars_format::json};
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); auto answer = fast_float::from_chars_advanced(input.data(), input.data() + input.size(), result, options);
if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; } if (answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; }
return EXIT_SUCCESS;
} }
``` ```
You can allow it with a non-standard `json_or_infnan` variant: You can allow it with a non-standard `json_or_infnan` variant:
```C++ ```C++
@ -276,55 +348,77 @@ You can allow it with a non-standard `json_or_infnan` variant:
#include <iostream> #include <iostream>
int main() { int main() {
const std::string input = "inf"; // not valid in JSON but we allow it with json_or_infnan std::string input = "inf"; // not valid in JSON but we allow it with json_or_infnan
double result; double result;
fast_float::parse_options options{ fast_float::chars_format::json_or_infnan }; fast_float::parse_options options{fast_float::chars_format::json_or_infnan};
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); auto answer = fast_float::from_chars_advanced(input.data(), input.data() + input.size(), result, options);
if(answer.ec != std::errc() || (!std::isinf(result))) { std::cerr << "should have parsed infinity\n"; return EXIT_FAILURE; } if (answer.ec != std::errc() || (!std::isinf(result))) { std::cerr << "should have parsed infinity\n"; return EXIT_FAILURE; }
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
`````` ```
## Relation With Other Work ## Users and Related Work
The fast_float library is part of: The fast_float library is part of:
- GCC (as of version 12): the `from_chars` function in GCC relies on fast_float. * GCC (as of version 12): the `from_chars` function in GCC relies on fast_float,
- [WebKit](https://github.com/WebKit/WebKit), the engine behind Safari (Apple's web browser) * [Chromium](https://github.com/Chromium/Chromium), the engine behind Google
Chrome, Microsoft Edge, and Opera,
* [WebKit](https://github.com/WebKit/WebKit), the engine behind Safari (Apple's
web browser),
* [DuckDB](https://duckdb.org),
* [Redis](https://github.com/redis/redis) and [Valkey](https://github.com/valkey-io/valkey),
* [Apache Arrow](https://github.com/apache/arrow/pull/8494) where it multiplied
the number parsing speed by two or three times,
* [Google Jsonnet](https://github.com/google/jsonnet),
* [ClickHouse](https://github.com/ClickHouse/ClickHouse).
The fastfloat algorithm is part of the [LLVM standard
libraries](https://github.com/llvm/llvm-project/commit/87c016078ad72c46505461e4ff8bfa04819fe7ba).
There is a [derived implementation part of
AdaCore](https://github.com/AdaCore/VSS).
The fastfloat algorithm is part of the [LLVM standard libraries](https://github.com/llvm/llvm-project/commit/87c016078ad72c46505461e4ff8bfa04819fe7ba). The fast_float library provides a performance similar to that of the
[fast_double_parser](https://github.com/lemire/fast_double_parser) library but
There is a [derived implementation part of AdaCore](https://github.com/AdaCore/VSS). using an updated algorithm reworked from the ground up, and while offering an
API more in line with the expectations of C++ programmers. The
fast_double_parser library is part of the [Microsoft LightGBM machine-learning
The fast_float library provides a performance similar to that of the [fast_double_parser](https://github.com/lemire/fast_double_parser) library but using an updated algorithm reworked from the ground up, and while offering an API more in line with the expectations of C++ programmers. The fast_double_parser library is part of the [Microsoft LightGBM machine-learning framework](https://github.com/microsoft/LightGBM). framework](https://github.com/microsoft/LightGBM).
## References ## References
- Daniel Lemire, [Number Parsing at a Gigabyte per Second](https://arxiv.org/abs/2101.11408), Software: Practice and Experience 51 (8), 2021. * Daniel Lemire, [Number Parsing at a Gigabyte per
- Noble Mushtak, Daniel Lemire, [Fast Number Parsing Without Fallback](https://arxiv.org/abs/2212.06644), Software: Practice and Experience 53 (7), 2023. Second](https://arxiv.org/abs/2101.11408), Software: Practice and Experience
51 (8), 2021.
* Noble Mushtak, Daniel Lemire, [Fast Number Parsing Without
Fallback](https://arxiv.org/abs/2212.06644), Software: Practice and Experience
53 (7), 2023.
## Other programming languages ## Other programming languages
- [There is an R binding](https://github.com/eddelbuettel/rcppfastfloat) called `rcppfastfloat`. * [There is an R binding](https://github.com/eddelbuettel/rcppfastfloat) called
- [There is a Rust port of the fast_float library](https://github.com/aldanor/fast-float-rust/) called `fast-float-rust`. `rcppfastfloat`.
- [There is a Java port of the fast_float library](https://github.com/wrandelshofer/FastDoubleParser) called `FastDoubleParser`. It used for important systems such as [Jackson](https://github.com/FasterXML/jackson-core). * [There is a Rust port of the fast_float
- [There is a C# port of the fast_float library](https://github.com/CarlVerret/csFastFloat) called `csFastFloat`. library](https://github.com/aldanor/fast-float-rust/) called
`fast-float-rust`.
* [There is a Java port of the fast_float
## Users library](https://github.com/wrandelshofer/FastDoubleParser) called
`FastDoubleParser`. It used for important systems such as
The fast_float library is used by [Apache Arrow](https://github.com/apache/arrow/pull/8494) where it multiplied the number parsing speed by two or three times. It is also used by [ClickHouse](https://github.com/ClickHouse/ClickHouse) and by [Google Jsonnet](https://github.com/google/jsonnet). It is part of GCC (as of GCC 12). It is part of WebKit (Safari). [Jackson](https://github.com/FasterXML/jackson-core).
* [There is a C# port of the fast_float
library](https://github.com/CarlVerret/csFastFloat) called `csFastFloat`.
## How fast is it? ## How fast is it?
It can parse random floating-point numbers at a speed of 1 GB/s on some systems. We find that it is often twice as fast as the best available competitor, and many times faster than many standard-library implementations. It can parse random floating-point numbers at a speed of 1 GB/s on some systems.
We find that it is often twice as fast as the best available competitor, and
many times faster than many standard-library implementations.
<img src="http://lemire.me/blog/wp-content/uploads/2020/11/fastfloat_speed.png" width="400"> <img src="https://lemire.me/blog/wp-content/uploads/2020/11/fastfloat_speed.png"
width="400" alt="fast_float is many times faster than many standard-library
implementations">
``` ```bash
$ ./build/benchmarks/benchmark $ ./build/benchmarks/benchmark
# parsing random integers in the range [0,1) # parsing random integers in the range [0,1)
volume = 2.09808 MB volume = 2.09808 MB
@ -335,75 +429,122 @@ abseil : 430.45 MB/s (+/- 2.2 %) 20.52 Mfl
fastfloat : 1042.38 MB/s (+/- 9.9 %) 49.68 Mfloat/s fastfloat : 1042.38 MB/s (+/- 9.9 %) 49.68 Mfloat/s
``` ```
See https://github.com/lemire/simple_fastfloat_benchmark for our benchmarking code. See the [Benchmarking](#benchmarking) section for instructions on how to run our benchmarks.
## Video ## Video
[![Go Systems 2020](http://img.youtube.com/vi/AVXgvlMeIm4/0.jpg)](http://www.youtube.com/watch?v=AVXgvlMeIm4)<br /> [![Go Systems 2020](https://img.youtube.com/vi/AVXgvlMeIm4/0.jpg)](https://www.youtube.com/watch?v=AVXgvlMeIm4)
## Using as a CMake dependency ## Using as a CMake dependency
This library is header-only by design. The CMake file provides the `fast_float` target This library is header-only by design. The CMake file provides the `fast_float`
which is merely a pointer to the `include` directory. target which is merely a pointer to the `include` directory.
If you drop the `fast_float` repository in your CMake project, you should be able to use If you drop the `fast_float` repository in your CMake project, you should be
it in this manner: able to use it in this manner:
```cmake ```cmake
add_subdirectory(fast_float) add_subdirectory(fast_float)
target_link_libraries(myprogram PUBLIC fast_float) target_link_libraries(myprogram PUBLIC fast_float)
``` ```
Or you may want to retrieve the dependency automatically if you have a sufficiently recent version of CMake (3.11 or better at least): Or you may want to retrieve the dependency automatically if you have a
sufficiently recent version of CMake (3.11 or better at least):
```cmake ```cmake
FetchContent_Declare( FetchContent_Declare(
fast_float fast_float
GIT_REPOSITORY https://github.com/lemire/fast_float.git GIT_REPOSITORY https://github.com/fastfloat/fast_float.git
GIT_TAG tags/v1.1.2 GIT_TAG tags/v8.0.2
GIT_SHALLOW TRUE) GIT_SHALLOW TRUE)
FetchContent_MakeAvailable(fast_float) FetchContent_MakeAvailable(fast_float)
target_link_libraries(myprogram PUBLIC fast_float) target_link_libraries(myprogram PUBLIC fast_float)
``` ```
You should change the `GIT_TAG` line so that you recover the version you wish to use. You should change the `GIT_TAG` line so that you recover the version you wish to
use.
You may also use [CPM](https://github.com/cpm-cmake/CPM.cmake), like so:
```cmake
CPMAddPackage(
NAME fast_float
GITHUB_REPOSITORY "fastfloat/fast_float"
GIT_TAG v8.0.2)
```
## Using as single header ## Using as single header
The script `script/amalgamate.py` may be used to generate a single header The script `script/amalgamate.py` may be used to generate a single header
version of the library if so desired. version of the library if so desired. Just run the script from the root
Just run the script from the root directory of this repository. directory of this repository. You can customize the license type and output file
You can customize the license type and output file if desired as described in if desired as described in the command line help.
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/v6.1.1/fast_float.h <https://github.com/fastfloat/fast_float/releases/download/v8.0.2/fast_float.h>
## RFC 7159 ## Benchmarking
If you need support for RFC 7159 (JSON standard), you may want to consider using the [fast_double_parser](https://github.com/lemire/fast_double_parser/) library instead. The project has its own benchmarks with realistic data inputs. Under Linux or macOS,
you can use it as follows if your system supports C++17:
```
cmake -B build -D FASTFLOAT_BENCHMARKS=ON
cmake --build build
./build/benchmarks/realbenchmark
```
Importantly, by default, the benchmark is built in Release mode.
The instructions are similar under Windows.
Under Linux and macOS, it is recommended to run the benchmarks in a privileged manner to get access
to hardware performance counters. You may be able to do so with the `sudo` command
in some cases:
```
sudo ./build/benchmarks/realbenchmark
```
If you have a text file containing one number per line (`myfile.txt`), you can run a benchmark over it like so:
```
cmake -B build -D FASTFLOAT_BENCHMARKS=ON
cmake --build build
./build/benchmarks/realbenchmark myfile.txt
```
## Packages
* The fast_float library is part of the [Conan package
manager](https://conan.io/center/recipes/fast_float).
* It is part of the [brew package
manager](https://formulae.brew.sh/formula/fast_float).
* Some Linux distribution like Fedora include fast_float (e.g., as
`fast_float-devel`).
## Credit ## Credit
Though this work is inspired by many different people, this work benefited especially from exchanges with Though this work is inspired by many different people, this work benefited
Michael Eisel, who motivated the original research with his key insights, and with Nigel Tao who provided especially from exchanges with Michael Eisel, who motivated the original
invaluable feedback. Rémy Oudompheng first implemented a fast path we use in the case of long digits. research with his key insights, and with Nigel Tao who provided invaluable
feedback. Rémy Oudompheng first implemented a fast path we use in the case of
long digits.
The library includes code adapted from Google Wuffs (written by Nigel Tao) which was originally published The library includes code adapted from Google Wuffs (written by Nigel Tao) which
under the Apache 2.0 license. was originally published under the Apache 2.0 license.
## License ## License
<sup> <sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> or <a href="LICENSE-BOOST">BOOST license</a> . 2.0</a> or <a href="LICENSE-MIT">MIT license</a> or <a
href="LICENSE-BOOST">BOOST license</a>.
</sup> </sup>
<br> <br/>
<sub> <sub>
Unless you explicitly state otherwise, any contribution intentionally submitted Unless you explicitly state otherwise, any contribution intentionally submitted

View File

@ -20,8 +20,7 @@
namespace fast_float { namespace fast_float {
template <typename UC> template <typename UC> fastfloat_really_inline constexpr bool has_simd_opt() {
fastfloat_really_inline constexpr bool has_simd_opt() {
#ifdef FASTFLOAT_HAS_SIMD #ifdef FASTFLOAT_HAS_SIMD
return std::is_same<UC, char16_t>::value; return std::is_same<UC, char16_t>::value;
#else #else
@ -37,24 +36,20 @@ fastfloat_really_inline constexpr bool is_integer(UC c) noexcept {
} }
fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) {
return (val & 0xFF00000000000000) >> 56 return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 |
| (val & 0x00FF000000000000) >> 40 (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 |
| (val & 0x0000FF0000000000) >> 24 (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 |
| (val & 0x000000FF00000000) >> 8 (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56;
| (val & 0x00000000FF000000) << 8
| (val & 0x0000000000FF0000) << 24
| (val & 0x000000000000FF00) << 40
| (val & 0x00000000000000FF) << 56;
} }
// Read 8 UC into a u64. Truncates UC if not char. // Read 8 UC into a u64. Truncates UC if not char.
template <typename UC> template <typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
uint64_t read8_to_u64(const UC *chars) { read8_to_u64(UC const *chars) {
if (cpp20_and_in_constexpr() || !std::is_same<UC, char>::value) { if (cpp20_and_in_constexpr() || !std::is_same<UC, char>::value) {
uint64_t val = 0; uint64_t val = 0;
for(int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
val |= uint64_t(uint8_t(*chars)) << (i*8); val |= uint64_t(uint8_t(*chars)) << (i * 8);
++chars; ++chars;
} }
return val; return val;
@ -70,44 +65,41 @@ uint64_t read8_to_u64(const UC *chars) {
#ifdef FASTFLOAT_SSE2 #ifdef FASTFLOAT_SSE2
fastfloat_really_inline fastfloat_really_inline uint64_t simd_read8_to_u64(__m128i const data) {
uint64_t simd_read8_to_u64(const __m128i data) { FASTFLOAT_SIMD_DISABLE_WARNINGS
FASTFLOAT_SIMD_DISABLE_WARNINGS __m128i const packed = _mm_packus_epi16(data, data);
const __m128i packed = _mm_packus_epi16(data, data);
#ifdef FASTFLOAT_64BIT #ifdef FASTFLOAT_64BIT
return uint64_t(_mm_cvtsi128_si64(packed)); return uint64_t(_mm_cvtsi128_si64(packed));
#else #else
uint64_t value; uint64_t value;
// Visual Studio + older versions of GCC don't support _mm_storeu_si64 // Visual Studio + older versions of GCC don't support _mm_storeu_si64
_mm_storel_epi64(reinterpret_cast<__m128i*>(&value), packed); _mm_storel_epi64(reinterpret_cast<__m128i *>(&value), packed);
return value; return value;
#endif #endif
FASTFLOAT_SIMD_RESTORE_WARNINGS FASTFLOAT_SIMD_RESTORE_WARNINGS
} }
fastfloat_really_inline fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) {
uint64_t simd_read8_to_u64(const char16_t* chars) { FASTFLOAT_SIMD_DISABLE_WARNINGS
FASTFLOAT_SIMD_DISABLE_WARNINGS return simd_read8_to_u64(
return simd_read8_to_u64(_mm_loadu_si128(reinterpret_cast<const __m128i*>(chars))); _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars)));
FASTFLOAT_SIMD_RESTORE_WARNINGS FASTFLOAT_SIMD_RESTORE_WARNINGS
} }
#elif defined(FASTFLOAT_NEON) #elif defined(FASTFLOAT_NEON)
fastfloat_really_inline uint64_t simd_read8_to_u64(uint16x8_t const data) {
fastfloat_really_inline FASTFLOAT_SIMD_DISABLE_WARNINGS
uint64_t simd_read8_to_u64(const uint16x8_t data) {
FASTFLOAT_SIMD_DISABLE_WARNINGS
uint8x8_t utf8_packed = vmovn_u16(data); uint8x8_t utf8_packed = vmovn_u16(data);
return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0); return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0);
FASTFLOAT_SIMD_RESTORE_WARNINGS FASTFLOAT_SIMD_RESTORE_WARNINGS
} }
fastfloat_really_inline fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) {
uint64_t simd_read8_to_u64(const char16_t* chars) { FASTFLOAT_SIMD_DISABLE_WARNINGS
FASTFLOAT_SIMD_DISABLE_WARNINGS return simd_read8_to_u64(
return simd_read8_to_u64(vld1q_u16(reinterpret_cast<const uint16_t*>(chars))); vld1q_u16(reinterpret_cast<uint16_t const *>(chars)));
FASTFLOAT_SIMD_RESTORE_WARNINGS FASTFLOAT_SIMD_RESTORE_WARNINGS
} }
#endif // FASTFLOAT_SSE2 #endif // FASTFLOAT_SSE2
@ -119,101 +111,84 @@ template <typename UC>
template <typename UC, FASTFLOAT_ENABLE_IF(!has_simd_opt<UC>()) = 0> template <typename UC, FASTFLOAT_ENABLE_IF(!has_simd_opt<UC>()) = 0>
#endif #endif
// dummy for compile // dummy for compile
uint64_t simd_read8_to_u64(UC const*) { uint64_t simd_read8_to_u64(UC const *) {
return 0; return 0;
} }
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
void write_u64(uint8_t *chars, uint64_t val) {
if (cpp20_and_in_constexpr()) {
for(int i = 0; i < 8; ++i) {
*chars = uint8_t(val);
val >>= 8;
++chars;
}
return;
}
#if FASTFLOAT_IS_BIG_ENDIAN == 1
// Need to read as-if the number was in little-endian order.
val = byteswap(val);
#endif
::memcpy(chars, &val, sizeof(uint64_t));
}
// credit @aqrit // credit @aqrit
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t
uint32_t parse_eight_digits_unrolled(uint64_t val) { parse_eight_digits_unrolled(uint64_t val) {
const uint64_t mask = 0x000000FF000000FF; uint64_t const mask = 0x000000FF000000FF;
const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) uint64_t const mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) uint64_t const mul2 = 0x0000271000000001; // 1 + (10000ULL << 32)
val -= 0x3030303030303030; val -= 0x3030303030303030;
val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8;
val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32;
return uint32_t(val); return uint32_t(val);
} }
// Call this if chars are definitely 8 digits. // Call this if chars are definitely 8 digits.
template <typename UC> template <typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t
uint32_t parse_eight_digits_unrolled(UC const * chars) noexcept { parse_eight_digits_unrolled(UC const *chars) noexcept {
if (cpp20_and_in_constexpr() || !has_simd_opt<UC>()) { if (cpp20_and_in_constexpr() || !has_simd_opt<UC>()) {
return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay
} }
return parse_eight_digits_unrolled(simd_read8_to_u64(chars)); return parse_eight_digits_unrolled(simd_read8_to_u64(chars));
} }
// credit @aqrit // credit @aqrit
fastfloat_really_inline constexpr bool is_made_of_eight_digits_fast(uint64_t val) noexcept { fastfloat_really_inline constexpr bool
is_made_of_eight_digits_fast(uint64_t val) noexcept {
return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) &
0x8080808080808080)); 0x8080808080808080));
} }
#ifdef FASTFLOAT_HAS_SIMD #ifdef FASTFLOAT_HAS_SIMD
// Call this if chars might not be 8 digits. // Call this if chars might not be 8 digits.
// Using this style (instead of is_made_of_eight_digits_fast() then parse_eight_digits_unrolled()) // Using this style (instead of is_made_of_eight_digits_fast() then
// ensures we don't load SIMD registers twice. // parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice.
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
bool simd_parse_if_eight_digits_unrolled(const char16_t* chars, uint64_t& i) noexcept { simd_parse_if_eight_digits_unrolled(char16_t const *chars,
uint64_t &i) noexcept {
if (cpp20_and_in_constexpr()) { if (cpp20_and_in_constexpr()) {
return false; return false;
} }
#ifdef FASTFLOAT_SSE2 #ifdef FASTFLOAT_SSE2
FASTFLOAT_SIMD_DISABLE_WARNINGS FASTFLOAT_SIMD_DISABLE_WARNINGS
const __m128i data = _mm_loadu_si128(reinterpret_cast<const __m128i*>(chars)); __m128i const data =
_mm_loadu_si128(reinterpret_cast<__m128i const *>(chars));
// (x - '0') <= 9 // (x - '0') <= 9
// http://0x80.pl/articles/simd-parsing-int-sequences.html // http://0x80.pl/articles/simd-parsing-int-sequences.html
const __m128i t0 = _mm_add_epi16(data, _mm_set1_epi16(32720)); __m128i const t0 = _mm_add_epi16(data, _mm_set1_epi16(32720));
const __m128i t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759)); __m128i const t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759));
if (_mm_movemask_epi8(t1) == 0) { if (_mm_movemask_epi8(t1) == 0) {
i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data));
return true; return true;
} } else
else return false; return false;
FASTFLOAT_SIMD_RESTORE_WARNINGS FASTFLOAT_SIMD_RESTORE_WARNINGS
#elif defined(FASTFLOAT_NEON) #elif defined(FASTFLOAT_NEON)
FASTFLOAT_SIMD_DISABLE_WARNINGS FASTFLOAT_SIMD_DISABLE_WARNINGS
const uint16x8_t data = vld1q_u16(reinterpret_cast<const uint16_t*>(chars)); uint16x8_t const data = vld1q_u16(reinterpret_cast<uint16_t const *>(chars));
// (x - '0') <= 9 // (x - '0') <= 9
// http://0x80.pl/articles/simd-parsing-int-sequences.html // http://0x80.pl/articles/simd-parsing-int-sequences.html
const uint16x8_t t0 = vsubq_u16(data, vmovq_n_u16('0')); uint16x8_t const t0 = vsubq_u16(data, vmovq_n_u16('0'));
const uint16x8_t mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1)); uint16x8_t const mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1));
if (vminvq_u16(mask) == 0xFFFF) { if (vminvq_u16(mask) == 0xFFFF) {
i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data));
return true; return true;
} } else
else return false; return false;
FASTFLOAT_SIMD_RESTORE_WARNINGS FASTFLOAT_SIMD_RESTORE_WARNINGS
#else #else
(void)chars; (void)i; (void)chars;
(void)i;
return false; return false;
#endif // FASTFLOAT_SSE2 #endif // FASTFLOAT_SSE2
} }
@ -227,79 +202,119 @@ template <typename UC>
template <typename UC, FASTFLOAT_ENABLE_IF(!has_simd_opt<UC>()) = 0> template <typename UC, FASTFLOAT_ENABLE_IF(!has_simd_opt<UC>()) = 0>
#endif #endif
// dummy for compile // dummy for compile
bool simd_parse_if_eight_digits_unrolled(UC const*, uint64_t&) { bool simd_parse_if_eight_digits_unrolled(UC const *, uint64_t &) {
return 0; return 0;
} }
template <typename UC, FASTFLOAT_ENABLE_IF(!std::is_same<UC, char>::value) = 0> template <typename UC, FASTFLOAT_ENABLE_IF(!std::is_same<UC, char>::value) = 0>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
void loop_parse_if_eight_digits(const UC*& p, const UC* const pend, uint64_t& i) { loop_parse_if_eight_digits(UC const *&p, UC const *const pend, uint64_t &i) {
if (!has_simd_opt<UC>()) { if (!has_simd_opt<UC>()) {
return; return;
} }
while ((std::distance(p, pend) >= 8) && simd_parse_if_eight_digits_unrolled(p, i)) { // in rare cases, this will overflow, but that's ok while ((std::distance(p, pend) >= 8) &&
simd_parse_if_eight_digits_unrolled(
p, i)) { // in rare cases, this will overflow, but that's ok
p += 8; p += 8;
} }
} }
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
void loop_parse_if_eight_digits(const char*& p, const char* const pend, uint64_t& i) { loop_parse_if_eight_digits(char const *&p, char const *const pend,
uint64_t &i) {
// optimizes better than parse_if_eight_digits_unrolled() for UC = char. // optimizes better than parse_if_eight_digits_unrolled() for UC = char.
while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(read8_to_u64(p))) { while ((std::distance(p, pend) >= 8) &&
i = i * 100000000 + parse_eight_digits_unrolled(read8_to_u64(p)); // in rare cases, this will overflow, but that's ok is_made_of_eight_digits_fast(read8_to_u64(p))) {
i = i * 100000000 +
parse_eight_digits_unrolled(read8_to_u64(
p)); // in rare cases, this will overflow, but that's ok
p += 8; p += 8;
} }
} }
template <typename UC> enum class parse_error {
struct parsed_number_string_t { no_error,
// [JSON-only] The minus sign must be followed by an integer.
missing_integer_after_sign,
// A sign must be followed by an integer or dot.
missing_integer_or_dot_after_sign,
// [JSON-only] The integer part must not have leading zeros.
leading_zeros_in_integer_part,
// [JSON-only] The integer part must have at least one digit.
no_digits_in_integer_part,
// [JSON-only] If there is a decimal point, there must be digits in the
// fractional part.
no_digits_in_fractional_part,
// The mantissa must have at least one digit.
no_digits_in_mantissa,
// Scientific notation requires an exponential part.
missing_exponential_part,
};
template <typename UC> struct parsed_number_string_t {
int64_t exponent{0}; int64_t exponent{0};
uint64_t mantissa{0}; uint64_t mantissa{0};
UC const * lastmatch{nullptr}; UC const *lastmatch{nullptr};
bool negative{false}; bool negative{false};
bool valid{false}; bool valid{false};
bool too_many_digits{false}; bool too_many_digits{false};
// contains the range of the significant digits // contains the range of the significant digits
span<const UC> integer{}; // non-nullable span<UC const> integer{}; // non-nullable
span<const UC> fraction{}; // nullable span<UC const> fraction{}; // nullable
parse_error error{parse_error::no_error};
}; };
using byte_span = span<const char>; using byte_span = span<char const>;
using parsed_number_string = parsed_number_string_t<char>; using parsed_number_string = parsed_number_string_t<char>;
template <typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC>
report_parse_error(UC const *p, parse_error error) {
parsed_number_string_t<UC> answer;
answer.valid = false;
answer.lastmatch = p;
answer.error = error;
return answer;
}
// 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.
template <typename UC> template <bool basic_json_fmt, typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC>
parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, parse_options_t<UC> options) noexcept { parse_number_string(UC const *p, UC const *pend,
chars_format const fmt = options.format; parse_options_t<UC> options) noexcept {
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;
parsed_number_string_t<UC> answer; parsed_number_string_t<UC> answer;
answer.valid = false; answer.valid = false;
answer.too_many_digits = false; answer.too_many_digits = false;
// assume p < pend, so dereference without checks;
answer.negative = (*p == UC('-')); answer.negative = (*p == UC('-'));
#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
if ((*p == UC('-')) || (!(fmt & FASTFLOAT_JSONFMT) && *p == UC('+'))) { if ((*p == UC('-')) || (uint64_t(fmt & chars_format::allow_leading_plus) &&
#else !basic_json_fmt && *p == UC('+'))) {
if (*p == UC('-')) { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
#endif
++p; ++p;
if (p == pend) { if (p == pend) {
return answer; return report_parse_error<UC>(
p, parse_error::missing_integer_or_dot_after_sign);
} }
if (fmt & FASTFLOAT_JSONFMT) { FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) {
if (!is_integer(*p)) { // a sign must be followed by an integer if (!is_integer(*p)) { // a sign must be followed by an integer
return answer; return report_parse_error<UC>(p,
} parse_error::missing_integer_after_sign);
} else { }
if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot }
return answer; else {
if (!is_integer(*p) &&
(*p !=
decimal_point)) { // a sign must be followed by an integer or the dot
return report_parse_error<UC>(
p, parse_error::missing_integer_or_dot_after_sign);
} }
} }
} }
UC const * const start_digits = p; UC const *const start_digits = p;
uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad)
@ -307,24 +322,29 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
// a multiplication by 10 is cheaper than an arbitrary integer // a multiplication by 10 is cheaper than an arbitrary integer
// multiplication // multiplication
i = 10 * i + i = 10 * i +
uint64_t(*p - UC('0')); // might overflow, we will handle the overflow later uint64_t(*p -
UC('0')); // might overflow, we will handle the overflow later
++p; ++p;
} }
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);
answer.integer = span<const UC>(start_digits, size_t(digit_count)); answer.integer = span<UC const>(start_digits, size_t(digit_count));
if (fmt & FASTFLOAT_JSONFMT) { 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 || (start_digits[0] == UC('0') && digit_count > 1)) { if (digit_count == 0) {
return answer; return report_parse_error<UC>(p, parse_error::no_digits_in_integer_part);
}
if ((start_digits[0] == UC('0') && digit_count > 1)) {
return report_parse_error<UC>(start_digits,
parse_error::leading_zeros_in_integer_part);
} }
} }
int64_t exponent = 0; int64_t exponent = 0;
const bool has_decimal_point = (p != pend) && (*p == decimal_point); bool const has_decimal_point = (p != pend) && (*p == decimal_point);
if (has_decimal_point) { if (has_decimal_point) {
++p; ++p;
UC const * before = p; UC const *before = p;
// can occur at most twice without overflowing, but let it occur more, since // can occur at most twice without overflowing, but let it occur more, since
// for integers with many digits, digit parsing is the primary bottleneck. // for integers with many digits, digit parsing is the primary bottleneck.
loop_parse_if_eight_digits(p, pend, i); loop_parse_if_eight_digits(p, pend, i);
@ -335,41 +355,45 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
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;
answer.fraction = span<const UC>(before, size_t(p - before)); answer.fraction = span<UC const>(before, size_t(p - before));
digit_count -= exponent; digit_count -= exponent;
} }
if (fmt & FASTFLOAT_JSONFMT) { FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) {
// at least 1 digit in fractional part // at least 1 digit in fractional part
if (has_decimal_point && exponent == 0) { if (has_decimal_point && exponent == 0) {
return answer; return report_parse_error<UC>(p,
parse_error::no_digits_in_fractional_part);
} }
}
else if (digit_count == 0) { // we must have encountered at least one integer!
return answer;
} }
int64_t exp_number = 0; // explicit exponential part else if (digit_count == 0) { // we must have encountered at least one integer!
if ( ((fmt & chars_format::scientific) && return report_parse_error<UC>(p, parse_error::no_digits_in_mantissa);
(p != pend) && }
((UC('e') == *p) || (UC('E') == *p))) int64_t exp_number = 0; // explicit exponential part
|| if ((uint64_t(fmt & chars_format::scientific) && (p != pend) &&
((fmt & FASTFLOAT_FORTRANFMT) && ((UC('e') == *p) || (UC('E') == *p))) ||
(p != pend) && (uint64_t(fmt & detail::basic_fortran_fmt) && (p != pend) &&
((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || (UC('D') == *p)))) { ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) ||
UC const * location_of_e = p; (UC('D') == *p)))) {
if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || (UC('D') == *p)) { UC const *location_of_e = p;
if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) ||
(UC('D') == *p)) {
++p; ++p;
} }
bool neg_exp = false; bool neg_exp = false;
if ((p != pend) && (UC('-') == *p)) { if ((p != pend) && (UC('-') == *p)) {
neg_exp = true; neg_exp = true;
++p; ++p;
} else if ((p != pend) && (UC('+') == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) } else if ((p != pend) &&
(UC('+') ==
*p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1)
++p; ++p;
} }
if ((p == pend) || !is_integer(*p)) { if ((p == pend) || !is_integer(*p)) {
if(!(fmt & chars_format::fixed)) { if (!uint64_t(fmt & chars_format::fixed)) {
// We are in error. // The exponential part is invalid for scientific notation, so it must
return answer; // be a trailing token for fixed notation. However, fixed notation is
// disabled, so report a scientific notation error.
return report_parse_error<UC>(p, parse_error::missing_exponential_part);
} }
// Otherwise, we will be ignoring the 'e'. // Otherwise, we will be ignoring the 'e'.
p = location_of_e; p = location_of_e;
@ -381,12 +405,17 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
} }
++p; ++p;
} }
if(neg_exp) { exp_number = - exp_number; } if (neg_exp) {
exp_number = -exp_number;
}
exponent += exp_number; exponent += exp_number;
} }
} else { } else {
// If it scientific and not fixed, we have to bail out. // If it scientific and not fixed, we have to bail out.
if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } if (uint64_t(fmt & chars_format::scientific) &&
!uint64_t(fmt & chars_format::fixed)) {
return report_parse_error<UC>(p, parse_error::missing_exponential_part);
}
} }
answer.lastmatch = p; answer.lastmatch = p;
answer.valid = true; answer.valid = true;
@ -401,9 +430,11 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
// We have to handle the case where we have 0.0000somenumber. // We have to handle the case where we have 0.0000somenumber.
// We need to be mindful of the case where we only have zeroes... // We need to be mindful of the case where we only have zeroes...
// E.g., 0.000000000...000. // E.g., 0.000000000...000.
UC const * start = start_digits; UC const *start = start_digits;
while ((start != pend) && (*start == UC('0') || *start == decimal_point)) { while ((start != pend) && (*start == UC('0') || *start == decimal_point)) {
if(*start == UC('0')) { digit_count --; } if (*start == UC('0')) {
digit_count--;
}
start++; start++;
} }
@ -414,18 +445,17 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
// pre-tokenized spans from above. // pre-tokenized spans from above.
i = 0; i = 0;
p = answer.integer.ptr; p = answer.integer.ptr;
UC const* int_end = p + answer.integer.len(); UC const *int_end = p + answer.integer.len();
const uint64_t minimal_nineteen_digit_integer{ 1000000000000000000 }; uint64_t const minimal_nineteen_digit_integer{1000000000000000000};
while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { while ((i < minimal_nineteen_digit_integer) && (p != int_end)) {
i = i * 10 + uint64_t(*p - UC('0')); i = i * 10 + uint64_t(*p - UC('0'));
++p; ++p;
} }
if (i >= minimal_nineteen_digit_integer) { // We have a big integers if (i >= minimal_nineteen_digit_integer) { // We have a big integers
exponent = end_of_integer_part - p + exp_number; exponent = end_of_integer_part - p + exp_number;
} } else { // We have a value with a fractional component.
else { // We have a value with a fractional component.
p = answer.fraction.ptr; p = answer.fraction.ptr;
UC const* frac_end = p + answer.fraction.len(); UC const *frac_end = p + answer.fraction.len();
while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
i = i * 10 + uint64_t(*p - UC('0')); i = i * 10 + uint64_t(*p - UC('0'));
++p; ++p;
@ -441,35 +471,43 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
} }
template <typename T, typename UC> template <typename T, typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
from_chars_result_t<UC> parse_int_string(UC const* p, UC const* pend, T& value, int base) { parse_int_string(UC const *p, UC const *pend, T &value,
from_chars_result_t<UC> answer; parse_options_t<UC> options) {
chars_format const fmt = detail::adjust_for_feature_macros(options.format);
UC const* const first = p; int const base = options.base;
bool negative = (*p == UC('-')); from_chars_result_t<UC> answer;
UC const *const first = p;
bool const negative = (*p == UC('-'));
#ifdef FASTFLOAT_VISUAL_STUDIO
#pragma warning(push)
#pragma warning(disable : 4127)
#endif
if (!std::is_signed<T>::value && negative) { if (!std::is_signed<T>::value && negative) {
#ifdef FASTFLOAT_VISUAL_STUDIO
#pragma warning(pop)
#endif
answer.ec = std::errc::invalid_argument; answer.ec = std::errc::invalid_argument;
answer.ptr = first; answer.ptr = first;
return answer; return answer;
} }
#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default if ((*p == UC('-')) ||
if ((*p == UC('-')) || (*p == UC('+'))) { (uint64_t(fmt & chars_format::allow_leading_plus) && (*p == UC('+')))) {
#else
if (*p == UC('-')) {
#endif
++p; ++p;
} }
UC const* const start_num = p; UC const *const start_num = p;
while (p!= pend && *p == UC('0')) { while (p != pend && *p == UC('0')) {
++p; ++p;
} }
const bool has_leading_zeros = p > start_num; bool const has_leading_zeros = p > start_num;
UC const* const start_digits = p; UC const *const start_digits = p;
uint64_t i = 0; uint64_t i = 0;
if (base == 10) { if (base == 10) {
@ -481,9 +519,9 @@ from_chars_result_t<UC> parse_int_string(UC const* p, UC const* pend, T& value,
break; break;
} }
i = uint64_t(base) * i + digit; // might overflow, check this later i = uint64_t(base) * i + digit; // might overflow, check this later
p++; p++;
} }
size_t digit_count = size_t(p - start_digits); size_t digit_count = size_t(p - start_digits);
if (digit_count == 0) { if (digit_count == 0) {
@ -491,12 +529,11 @@ from_chars_result_t<UC> parse_int_string(UC const* p, UC const* pend, T& value,
value = 0; value = 0;
answer.ec = std::errc(); answer.ec = std::errc();
answer.ptr = p; answer.ptr = p;
} } else {
else {
answer.ec = std::errc::invalid_argument; answer.ec = std::errc::invalid_argument;
answer.ptr = first; answer.ptr = first;
} }
return answer; return answer;
} }
answer.ptr = p; answer.ptr = p;
@ -507,7 +544,8 @@ from_chars_result_t<UC> parse_int_string(UC const* p, UC const* pend, T& value,
answer.ec = std::errc::result_out_of_range; answer.ec = std::errc::result_out_of_range;
return answer; return answer;
} }
// this check can be eliminated for all other types, but they will all require a max_digits(base) equivalent // this check can be eliminated for all other types, but they will all require
// a max_digits(base) equivalent
if (digit_count == max_digits && i < min_safe_u64(base)) { if (digit_count == max_digits && i < min_safe_u64(base)) {
answer.ec = std::errc::result_out_of_range; answer.ec = std::errc::result_out_of_range;
return answer; return answer;
@ -524,18 +562,22 @@ from_chars_result_t<UC> parse_int_string(UC const* p, UC const* pend, T& value,
if (negative) { if (negative) {
#ifdef FASTFLOAT_VISUAL_STUDIO #ifdef FASTFLOAT_VISUAL_STUDIO
#pragma warning(push) #pragma warning(push)
#pragma warning(disable: 4146) #pragma warning(disable : 4146)
#endif #endif
// this weird workaround is required because: // this weird workaround is required because:
// - converting unsigned to signed when its value is greater than signed max is UB pre-C++23. // - converting unsigned to signed when its value is greater than signed max
// is UB pre-C++23.
// - reinterpret_casting (~i + 1) would work, but it is not constexpr // - reinterpret_casting (~i + 1) would work, but it is not constexpr
// this is always optimized into a neg instruction (note: T is an integer type) // this is always optimized into a neg instruction (note: T is an integer
value = T(-std::numeric_limits<T>::max() - T(i - uint64_t(std::numeric_limits<T>::max()))); // type)
value = T(-std::numeric_limits<T>::max() -
T(i - uint64_t(std::numeric_limits<T>::max())));
#ifdef FASTFLOAT_VISUAL_STUDIO #ifdef FASTFLOAT_VISUAL_STUDIO
#pragma warning(pop) #pragma warning(pop)
#endif #endif
} else {
value = T(i);
} }
else { value = T(i); }
answer.ec = std::errc(); answer.ec = std::errc();
return answer; return answer;

View File

@ -37,15 +37,14 @@ constexpr size_t bigint_limbs = bigint_bits / limb_bits;
// vector-like type that is allocated on the stack. the entire // vector-like type that is allocated on the stack. the entire
// buffer is pre-allocated, and only the length changes. // buffer is pre-allocated, and only the length changes.
template <uint16_t size> template <uint16_t size> struct stackvec {
struct stackvec {
limb data[size]; limb data[size];
// we never need more than 150 limbs // we never need more than 150 limbs
uint16_t length{0}; uint16_t length{0};
stackvec() = default; stackvec() = default;
stackvec(const stackvec &) = delete; stackvec(stackvec const &) = delete;
stackvec &operator=(const stackvec &) = delete; stackvec &operator=(stackvec const &) = delete;
stackvec(stackvec &&) = delete; stackvec(stackvec &&) = delete;
stackvec &operator=(stackvec &&other) = delete; stackvec &operator=(stackvec &&other) = delete;
@ -54,16 +53,18 @@ struct stackvec {
FASTFLOAT_ASSERT(try_extend(s)); FASTFLOAT_ASSERT(try_extend(s));
} }
FASTFLOAT_CONSTEXPR14 limb& operator[](size_t index) noexcept { FASTFLOAT_CONSTEXPR14 limb &operator[](size_t index) noexcept {
FASTFLOAT_DEBUG_ASSERT(index < length); FASTFLOAT_DEBUG_ASSERT(index < length);
return data[index]; return data[index];
} }
FASTFLOAT_CONSTEXPR14 const limb& operator[](size_t index) const noexcept {
FASTFLOAT_CONSTEXPR14 const limb &operator[](size_t index) const noexcept {
FASTFLOAT_DEBUG_ASSERT(index < length); FASTFLOAT_DEBUG_ASSERT(index < length);
return data[index]; return data[index];
} }
// index from the end of the container // index from the end of the container
FASTFLOAT_CONSTEXPR14 const limb& rindex(size_t index) const noexcept { FASTFLOAT_CONSTEXPR14 const limb &rindex(size_t index) const noexcept {
FASTFLOAT_DEBUG_ASSERT(index < length); FASTFLOAT_DEBUG_ASSERT(index < length);
size_t rindex = length - index - 1; size_t rindex = length - index - 1;
return data[rindex]; return data[rindex];
@ -73,20 +74,19 @@ struct stackvec {
FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept { FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept {
length = uint16_t(len); length = uint16_t(len);
} }
constexpr size_t len() const noexcept {
return length; constexpr size_t len() const noexcept { return length; }
}
constexpr bool is_empty() const noexcept { constexpr bool is_empty() const noexcept { return length == 0; }
return length == 0;
} constexpr size_t capacity() const noexcept { return size; }
constexpr size_t capacity() const noexcept {
return size;
}
// append item to vector, without bounds checking // append item to vector, without bounds checking
FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept { FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept {
data[length] = value; data[length] = value;
length++; length++;
} }
// append item to vector, returning if item was added // append item to vector, returning if item was added
FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept { FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept {
if (len() < capacity()) { if (len() < capacity()) {
@ -96,12 +96,14 @@ struct stackvec {
return false; return false;
} }
} }
// add items to the vector, from a span, without bounds checking // add items to the vector, from a span, without bounds checking
FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept { FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept {
limb* ptr = data + length; limb *ptr = data + length;
std::copy_n(s.ptr, s.len(), ptr); std::copy_n(s.ptr, s.len(), ptr);
set_len(len() + s.len()); set_len(len() + s.len());
} }
// try to add items to the vector, returning if items were added // try to add items to the vector, returning if items were added
FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept { FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept {
if (len() + s.len() <= capacity()) { if (len() + s.len() <= capacity()) {
@ -111,6 +113,7 @@ struct stackvec {
return false; return false;
} }
} }
// resize the vector, without bounds checking // resize the vector, without bounds checking
// if the new size is longer than the vector, assign value to each // if the new size is longer than the vector, assign value to each
// appended item. // appended item.
@ -118,14 +121,15 @@ struct stackvec {
void resize_unchecked(size_t new_len, limb value) noexcept { void resize_unchecked(size_t new_len, limb value) noexcept {
if (new_len > len()) { if (new_len > len()) {
size_t count = new_len - len(); size_t count = new_len - len();
limb* first = data + len(); limb *first = data + len();
limb* last = first + count; limb *last = first + count;
::std::fill(first, last, value); ::std::fill(first, last, value);
set_len(new_len); set_len(new_len);
} else { } else {
set_len(new_len); set_len(new_len);
} }
} }
// try to resize the vector, returning if the vector was resized. // try to resize the vector, returning if the vector was resized.
FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept { FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept {
if (new_len > capacity()) { if (new_len > capacity()) {
@ -135,6 +139,7 @@ struct stackvec {
return true; return true;
} }
} }
// check if any limbs are non-zero after the given index. // check if any limbs are non-zero after the given index.
// this needs to be done in reverse order, since the index // this needs to be done in reverse order, since the index
// is relative to the most significant limbs. // is relative to the most significant limbs.
@ -147,6 +152,7 @@ struct stackvec {
} }
return false; return false;
} }
// normalize the big integer, so most-significant zero limbs are removed. // normalize the big integer, so most-significant zero limbs are removed.
FASTFLOAT_CONSTEXPR14 void normalize() noexcept { FASTFLOAT_CONSTEXPR14 void normalize() noexcept {
while (len() > 0 && rindex(0) == 0) { while (len() > 0 && rindex(0) == 0) {
@ -155,21 +161,21 @@ struct stackvec {
} }
}; };
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t
uint64_t empty_hi64(bool& truncated) noexcept { empty_hi64(bool &truncated) noexcept {
truncated = false; truncated = false;
return 0; return 0;
} }
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
uint64_t uint64_hi64(uint64_t r0, bool& truncated) noexcept { uint64_hi64(uint64_t r0, bool &truncated) noexcept {
truncated = false; truncated = false;
int shl = leading_zeroes(r0); int shl = leading_zeroes(r0);
return r0 << shl; return r0 << shl;
} }
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept { uint64_hi64(uint64_t r0, uint64_t r1, bool &truncated) noexcept {
int shl = leading_zeroes(r0); int shl = leading_zeroes(r0);
if (shl == 0) { if (shl == 0) {
truncated = r1 != 0; truncated = r1 != 0;
@ -181,20 +187,20 @@ uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept {
} }
} }
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept { uint32_hi64(uint32_t r0, bool &truncated) noexcept {
return uint64_hi64(r0, truncated); return uint64_hi64(r0, truncated);
} }
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
uint64_t uint32_hi64(uint32_t r0, uint32_t r1, bool& truncated) noexcept { uint32_hi64(uint32_t r0, uint32_t r1, bool &truncated) noexcept {
uint64_t x0 = r0; uint64_t x0 = r0;
uint64_t x1 = r1; uint64_t x1 = r1;
return uint64_hi64((x0 << 32) | x1, truncated); return uint64_hi64((x0 << 32) | x1, truncated);
} }
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool& truncated) noexcept { uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool &truncated) noexcept {
uint64_t x0 = r0; uint64_t x0 = r0;
uint64_t x1 = r1; uint64_t x1 = r1;
uint64_t x2 = r2; uint64_t x2 = r2;
@ -205,17 +211,17 @@ uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool& truncated) noe
// we want an efficient operation. for msvc, where // we want an efficient operation. for msvc, where
// we don't have built-in intrinsics, this is still // we don't have built-in intrinsics, this is still
// pretty fast. // pretty fast.
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb
limb scalar_add(limb x, limb y, bool& overflow) noexcept { scalar_add(limb x, limb y, bool &overflow) noexcept {
limb z; limb z;
// gcc and clang // gcc and clang
#if defined(__has_builtin) #if defined(__has_builtin)
#if __has_builtin(__builtin_add_overflow) #if __has_builtin(__builtin_add_overflow)
if (!cpp20_and_in_constexpr()) { if (!cpp20_and_in_constexpr()) {
overflow = __builtin_add_overflow(x, y, &z); overflow = __builtin_add_overflow(x, y, &z);
return z; return z;
} }
#endif #endif
#endif #endif
// generic, this still optimizes correctly on MSVC. // generic, this still optimizes correctly on MSVC.
@ -225,24 +231,24 @@ limb scalar_add(limb x, limb y, bool& overflow) noexcept {
} }
// multiply two small integers, getting both the high and low bits. // multiply two small integers, getting both the high and low bits.
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb
limb scalar_mul(limb x, limb y, limb& carry) noexcept { scalar_mul(limb x, limb y, limb &carry) noexcept {
#ifdef FASTFLOAT_64BIT_LIMB #ifdef FASTFLOAT_64BIT_LIMB
#if defined(__SIZEOF_INT128__) #if defined(__SIZEOF_INT128__)
// GCC and clang both define it as an extension. // GCC and clang both define it as an extension.
__uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry);
carry = limb(z >> limb_bits); carry = limb(z >> limb_bits);
return limb(z); return limb(z);
#else #else
// fallback, no native 128-bit integer multiplication with carry. // fallback, no native 128-bit integer multiplication with carry.
// on msvc, this optimizes identically, somehow. // on msvc, this optimizes identically, somehow.
value128 z = full_multiplication(x, y); value128 z = full_multiplication(x, y);
bool overflow; bool overflow;
z.low = scalar_add(z.low, carry, overflow); z.low = scalar_add(z.low, carry, overflow);
z.high += uint64_t(overflow); // cannot overflow z.high += uint64_t(overflow); // cannot overflow
carry = z.high; carry = z.high;
return z.low; return z.low;
#endif #endif
#else #else
uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry);
carry = limb(z >> limb_bits); carry = limb(z >> limb_bits);
@ -253,8 +259,8 @@ limb scalar_mul(limb x, limb y, limb& carry) noexcept {
// add scalar value to bigint starting from offset. // add scalar value to bigint starting from offset.
// used in grade school multiplication // used in grade school multiplication
template <uint16_t size> template <uint16_t size>
inline FASTFLOAT_CONSTEXPR20 inline FASTFLOAT_CONSTEXPR20 bool small_add_from(stackvec<size> &vec, limb y,
bool small_add_from(stackvec<size>& vec, limb y, size_t start) noexcept { size_t start) noexcept {
size_t index = start; size_t index = start;
limb carry = y; limb carry = y;
bool overflow; bool overflow;
@ -271,15 +277,15 @@ bool small_add_from(stackvec<size>& vec, limb y, size_t start) noexcept {
// add scalar value to bigint. // add scalar value to bigint.
template <uint16_t size> template <uint16_t size>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
bool small_add(stackvec<size>& vec, limb y) noexcept { small_add(stackvec<size> &vec, limb y) noexcept {
return small_add_from(vec, y, 0); return small_add_from(vec, y, 0);
} }
// multiply bigint by scalar value. // multiply bigint by scalar value.
template <uint16_t size> template <uint16_t size>
inline FASTFLOAT_CONSTEXPR20 inline FASTFLOAT_CONSTEXPR20 bool small_mul(stackvec<size> &vec,
bool small_mul(stackvec<size>& vec, limb y) noexcept { limb y) noexcept {
limb carry = 0; limb carry = 0;
for (size_t index = 0; index < vec.len(); index++) { for (size_t index = 0; index < vec.len(); index++) {
vec[index] = scalar_mul(vec[index], y, carry); vec[index] = scalar_mul(vec[index], y, carry);
@ -293,12 +299,12 @@ bool small_mul(stackvec<size>& vec, limb y) noexcept {
// add bigint to bigint starting from index. // add bigint to bigint starting from index.
// used in grade school multiplication // used in grade school multiplication
template <uint16_t size> template <uint16_t size>
FASTFLOAT_CONSTEXPR20 FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec<size> &x, limb_span y,
bool large_add_from(stackvec<size>& x, limb_span y, size_t start) noexcept { size_t start) noexcept {
// the effective x buffer is from `xstart..x.len()`, so exit early // the effective x buffer is from `xstart..x.len()`, so exit early
// if we can't get that current range. // if we can't get that current range.
if (x.len() < start || y.len() > x.len() - start) { if (x.len() < start || y.len() > x.len() - start) {
FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); FASTFLOAT_TRY(x.try_resize(y.len() + start, 0));
} }
bool carry = false; bool carry = false;
@ -324,15 +330,14 @@ bool large_add_from(stackvec<size>& x, limb_span y, size_t start) noexcept {
// add bigint to bigint. // add bigint to bigint.
template <uint16_t size> template <uint16_t size>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
bool large_add_from(stackvec<size>& x, limb_span y) noexcept { large_add_from(stackvec<size> &x, limb_span y) noexcept {
return large_add_from(x, y, 0); return large_add_from(x, y, 0);
} }
// grade-school multiplication algorithm // grade-school multiplication algorithm
template <uint16_t size> template <uint16_t size>
FASTFLOAT_CONSTEXPR20 FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec<size> &x, limb_span y) noexcept {
bool long_mul(stackvec<size>& x, limb_span y) noexcept {
limb_span xs = limb_span(x.data, x.len()); limb_span xs = limb_span(x.data, x.len());
stackvec<size> z(xs); stackvec<size> z(xs);
limb_span zs = limb_span(z.data, z.len()); limb_span zs = limb_span(z.data, z.len());
@ -360,8 +365,7 @@ bool long_mul(stackvec<size>& x, limb_span y) noexcept {
// grade-school multiplication algorithm // grade-school multiplication algorithm
template <uint16_t size> template <uint16_t size>
FASTFLOAT_CONSTEXPR20 FASTFLOAT_CONSTEXPR20 bool large_mul(stackvec<size> &x, limb_span y) noexcept {
bool large_mul(stackvec<size>& x, limb_span y) noexcept {
if (y.len() == 1) { if (y.len() == 1) {
FASTFLOAT_TRY(small_mul(x, y[0])); FASTFLOAT_TRY(small_mul(x, y[0]));
} else { } else {
@ -370,36 +374,58 @@ bool large_mul(stackvec<size>& x, limb_span y) noexcept {
return true; return true;
} }
template <typename = void> template <typename = void> struct pow5_tables {
struct pow5_tables {
static constexpr uint32_t large_step = 135; static constexpr uint32_t large_step = 135;
static constexpr uint64_t small_power_of_5[] = { static constexpr uint64_t small_power_of_5[] = {
1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL, 78125UL, 390625UL, 1UL,
1953125UL, 9765625UL, 48828125UL, 244140625UL, 1220703125UL, 5UL,
6103515625UL, 30517578125UL, 152587890625UL, 762939453125UL, 25UL,
3814697265625UL, 19073486328125UL, 95367431640625UL, 476837158203125UL, 125UL,
2384185791015625UL, 11920928955078125UL, 59604644775390625UL, 625UL,
298023223876953125UL, 1490116119384765625UL, 7450580596923828125UL, 3125UL,
15625UL,
78125UL,
390625UL,
1953125UL,
9765625UL,
48828125UL,
244140625UL,
1220703125UL,
6103515625UL,
30517578125UL,
152587890625UL,
762939453125UL,
3814697265625UL,
19073486328125UL,
95367431640625UL,
476837158203125UL,
2384185791015625UL,
11920928955078125UL,
59604644775390625UL,
298023223876953125UL,
1490116119384765625UL,
7450580596923828125UL,
}; };
#ifdef FASTFLOAT_64BIT_LIMB #ifdef FASTFLOAT_64BIT_LIMB
constexpr static limb large_power_of_5[] = { constexpr static limb large_power_of_5[] = {
1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL,
10482974169319127550UL, 198276706040285095UL}; 10482974169319127550UL, 198276706040285095UL};
#else #else
constexpr static limb large_power_of_5[] = { constexpr static limb large_power_of_5[] = {
4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U,
1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U};
#endif #endif
}; };
template <typename T> #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE
constexpr uint32_t pow5_tables<T>::large_step;
template <typename T> template <typename T> constexpr uint32_t pow5_tables<T>::large_step;
constexpr uint64_t pow5_tables<T>::small_power_of_5[];
template <typename T> template <typename T> constexpr uint64_t pow5_tables<T>::small_power_of_5[];
constexpr limb pow5_tables<T>::large_power_of_5[];
template <typename T> constexpr limb pow5_tables<T>::large_power_of_5[];
#endif
// big integer type. implements a small subset of big integer // big integer type. implements a small subset of big integer
// arithmetic, using simple algorithms since asymptotically // arithmetic, using simple algorithms since asymptotically
@ -409,13 +435,14 @@ struct bigint : pow5_tables<> {
// storage of the limbs, in little-endian order. // storage of the limbs, in little-endian order.
stackvec<bigint_limbs> vec; stackvec<bigint_limbs> vec;
FASTFLOAT_CONSTEXPR20 bigint(): vec() {} FASTFLOAT_CONSTEXPR20 bigint() : vec() {}
bigint(const bigint &) = delete;
bigint &operator=(const bigint &) = delete; bigint(bigint const &) = delete;
bigint &operator=(bigint const &) = delete;
bigint(bigint &&) = delete; bigint(bigint &&) = delete;
bigint &operator=(bigint &&other) = delete; bigint &operator=(bigint &&other) = delete;
FASTFLOAT_CONSTEXPR20 bigint(uint64_t value): vec() { FASTFLOAT_CONSTEXPR20 bigint(uint64_t value) : vec() {
#ifdef FASTFLOAT_64BIT_LIMB #ifdef FASTFLOAT_64BIT_LIMB
vec.push_unchecked(value); vec.push_unchecked(value);
#else #else
@ -427,7 +454,7 @@ struct bigint : pow5_tables<> {
// get the high 64 bits from the vector, and if bits were truncated. // get the high 64 bits from the vector, and if bits were truncated.
// this is to get the significant digits for the float. // this is to get the significant digits for the float.
FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool& truncated) const noexcept { FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool &truncated) const noexcept {
#ifdef FASTFLOAT_64BIT_LIMB #ifdef FASTFLOAT_64BIT_LIMB
if (vec.len() == 0) { if (vec.len() == 0) {
return empty_hi64(truncated); return empty_hi64(truncated);
@ -446,7 +473,8 @@ struct bigint : pow5_tables<> {
} else if (vec.len() == 2) { } else if (vec.len() == 2) {
return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated);
} else { } else {
uint64_t result = uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); uint64_t result =
uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated);
truncated |= vec.nonzero(3); truncated |= vec.nonzero(3);
return result; return result;
} }
@ -459,7 +487,7 @@ struct bigint : pow5_tables<> {
// positive, this is larger, otherwise they are equal. // positive, this is larger, otherwise they are equal.
// the limbs are stored in little-endian order, so we // the limbs are stored in little-endian order, so we
// must compare the limbs in ever order. // must compare the limbs in ever order.
FASTFLOAT_CONSTEXPR20 int compare(const bigint& other) const noexcept { FASTFLOAT_CONSTEXPR20 int compare(bigint const &other) const noexcept {
if (vec.len() > other.vec.len()) { if (vec.len() > other.vec.len()) {
return 1; return 1;
} else if (vec.len() < other.vec.len()) { } else if (vec.len() < other.vec.len()) {
@ -512,12 +540,12 @@ struct bigint : pow5_tables<> {
return false; return false;
} else if (!vec.is_empty()) { } else if (!vec.is_empty()) {
// move limbs // move limbs
limb* dst = vec.data + n; limb *dst = vec.data + n;
const limb* src = vec.data; limb const *src = vec.data;
std::copy_backward(src, src + vec.len(), dst + vec.len()); std::copy_backward(src, src + vec.len(), dst + vec.len());
// fill in empty limbs // fill in empty limbs
limb* first = vec.data; limb *first = vec.data;
limb* last = first + n; limb *last = first + n;
::std::fill(first, last, 0); ::std::fill(first, last, 0);
vec.set_len(n + vec.len()); vec.set_len(n + vec.len());
return true; return true;
@ -560,18 +588,12 @@ struct bigint : pow5_tables<> {
return int(limb_bits * vec.len()) - lz; return int(limb_bits * vec.len()) - lz;
} }
FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { return small_mul(vec, y); }
return small_mul(vec, y);
}
FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { return small_add(vec, y); }
return small_add(vec, y);
}
// multiply as if by 2 raised to a power. // multiply as if by 2 raised to a power.
FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { return shl(exp); }
return shl(exp);
}
// multiply as if by 5 raised to a power. // multiply as if by 5 raised to a power.
FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept { FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept {
@ -597,9 +619,8 @@ struct bigint : pow5_tables<> {
// Work around clang bug https://godbolt.org/z/zedh7rrhc // Work around clang bug https://godbolt.org/z/zedh7rrhc
// This is similar to https://github.com/llvm/llvm-project/issues/47746, // This is similar to https://github.com/llvm/llvm-project/issues/47746,
// except the workaround described there don't work here // except the workaround described there don't work here
FASTFLOAT_TRY( FASTFLOAT_TRY(small_mul(
small_mul(vec, limb(((void)small_power_of_5[0], small_power_of_5[exp]))) vec, limb(((void)small_power_of_5[0], small_power_of_5[exp]))));
);
} }
return true; return true;

View File

@ -8,7 +8,7 @@
#endif #endif
// Testing for https://wg21.link/N3652, adopted in C++14 // Testing for https://wg21.link/N3652, adopted in C++14
#if __cpp_constexpr >= 201304 #if defined(__cpp_constexpr) && __cpp_constexpr >= 201304
#define FASTFLOAT_CONSTEXPR14 constexpr #define FASTFLOAT_CONSTEXPR14 constexpr
#else #else
#define FASTFLOAT_CONSTEXPR14 #define FASTFLOAT_CONSTEXPR14
@ -20,16 +20,23 @@
#define FASTFLOAT_HAS_BIT_CAST 0 #define FASTFLOAT_HAS_BIT_CAST 0
#endif #endif
#if defined(__cpp_lib_is_constant_evaluated) && __cpp_lib_is_constant_evaluated >= 201811L #if defined(__cpp_lib_is_constant_evaluated) && \
__cpp_lib_is_constant_evaluated >= 201811L
#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 #define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1
#else #else
#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 #define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0
#endif #endif
#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L
#define FASTFLOAT_IF_CONSTEXPR17(x) if constexpr (x)
#else
#define FASTFLOAT_IF_CONSTEXPR17(x) if (x)
#endif
// Testing for relevant C++20 constexpr library features // Testing for relevant C++20 constexpr library features
#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED \ #if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST && \
&& FASTFLOAT_HAS_BIT_CAST \ defined(__cpp_lib_constexpr_algorithms) && \
&& __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/
#define FASTFLOAT_CONSTEXPR20 constexpr #define FASTFLOAT_CONSTEXPR20 constexpr
#define FASTFLOAT_IS_CONSTEXPR 1 #define FASTFLOAT_IS_CONSTEXPR 1
#else #else
@ -37,4 +44,10 @@
#define FASTFLOAT_IS_CONSTEXPR 0 #define FASTFLOAT_IS_CONSTEXPR 0
#endif #endif
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 0
#else
#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 1
#endif
#endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H #endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H

View File

@ -12,27 +12,34 @@
namespace fast_float { namespace fast_float {
// This will compute or rather approximate w * 5**q and return a pair of 64-bit words approximating // This will compute or rather approximate w * 5**q and return a pair of 64-bit
// the result, with the "high" part corresponding to the most significant bits and the // words approximating the result, with the "high" part corresponding to the
// low part corresponding to the least significant bits. // most significant bits and the low part corresponding to the least significant
// bits.
// //
template <int bit_precision> template <int bit_precision>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128
value128 compute_product_approximation(int64_t q, uint64_t w) { compute_product_approximation(int64_t q, uint64_t w) {
const int index = 2 * int(q - powers::smallest_power_of_five); int const index = 2 * int(q - powers::smallest_power_of_five);
// For small values of q, e.g., q in [0,27], the answer is always exact because // For small values of q, e.g., q in [0,27], the answer is always exact
// The line value128 firstproduct = full_multiplication(w, power_of_five_128[index]); // because The line value128 firstproduct = full_multiplication(w,
// gives the exact answer. // power_of_five_128[index]); gives the exact answer.
value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]); value128 firstproduct =
static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]"); full_multiplication(w, powers::power_of_five_128[index]);
constexpr uint64_t precision_mask = (bit_precision < 64) ? static_assert((bit_precision >= 0) && (bit_precision <= 64),
(uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) " precision should be in (0,64]");
: uint64_t(0xFFFFFFFFFFFFFFFF); constexpr uint64_t precision_mask =
if((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower) (bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision)
// regarding the second product, we only need secondproduct.high, but our expectation is that the compiler will optimize this extra work away if needed. : uint64_t(0xFFFFFFFFFFFFFFFF);
value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]); if ((firstproduct.high & precision_mask) ==
precision_mask) { // could further guard with (lower + w < lower)
// regarding the second product, we only need secondproduct.high, but our
// expectation is that the compiler will optimize this extra work away if
// needed.
value128 secondproduct =
full_multiplication(w, powers::power_of_five_128[index + 1]);
firstproduct.low += secondproduct.high; firstproduct.low += secondproduct.high;
if(secondproduct.high > firstproduct.low) { if (secondproduct.high > firstproduct.low) {
firstproduct.high++; firstproduct.high++;
} }
} }
@ -55,43 +62,45 @@ namespace detail {
* where * where
* p = log(5**-q)/log(2) = -q * log(5)/log(2) * p = log(5**-q)/log(2) = -q * log(5)/log(2)
*/ */
constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept {
return (((152170 + 65536) * q) >> 16) + 63; return (((152170 + 65536) * q) >> 16) + 63;
} }
} // namespace detail } // namespace detail
// create an adjusted mantissa, biased by the invalid power2 // create an adjusted mantissa, biased by the invalid power2
// for significant digits already multiplied by 10 ** q. // for significant digits already multiplied by 10 ** q.
template <typename binary> template <typename binary>
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 fastfloat_really_inline FASTFLOAT_CONSTEXPR14 adjusted_mantissa
adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept {
int hilz = int(w >> 63) ^ 1; int hilz = int(w >> 63) ^ 1;
adjusted_mantissa answer; adjusted_mantissa answer;
answer.mantissa = w << hilz; answer.mantissa = w << hilz;
int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent();
answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + invalid_am_bias); answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 +
invalid_am_bias);
return answer; return answer;
} }
// w * 10 ** q, without rounding the representation up. // w * 10 ** q, without rounding the representation up.
// the power2 in the exponent will be adjusted by invalid_am_bias. // the power2 in the exponent will be adjusted by invalid_am_bias.
template <typename binary> template <typename binary>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept { compute_error(int64_t q, uint64_t w) noexcept {
int lz = leading_zeroes(w); int lz = leading_zeroes(w);
w <<= lz; w <<= lz;
value128 product = compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w); value128 product =
compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w);
return compute_error_scaled<binary>(q, product.high, lz); return compute_error_scaled<binary>(q, product.high, lz);
} }
// w * 10 ** q // Computers w * 10 ** q.
// The returned value should be a valid ieee64 number that simply need to be packed. // The returned value should be a valid number that simply needs to be
// However, in some very rare cases, the computation will fail. In such cases, we // packed. However, in some very rare cases, the computation will fail. In such
// return an adjusted_mantissa with a negative power of 2: the caller should recompute // cases, we return an adjusted_mantissa with a negative power of 2: the caller
// in such cases. // should recompute in such cases.
template <typename binary> template <typename binary>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { compute_float(int64_t q, uint64_t w) noexcept {
adjusted_mantissa answer; adjusted_mantissa answer;
if ((w == 0) || (q < binary::smallest_power_of_ten())) { if ((w == 0) || (q < binary::smallest_power_of_ten())) {
answer.power2 = 0; answer.power2 = 0;
@ -105,7 +114,8 @@ adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept {
answer.mantissa = 0; answer.mantissa = 0;
return answer; return answer;
} }
// At this point in time q is in [powers::smallest_power_of_five, powers::largest_power_of_five]. // At this point in time q is in [powers::smallest_power_of_five,
// powers::largest_power_of_five].
// We want the most significant bit of i to be 1. Shift if needed. // We want the most significant bit of i to be 1. Shift if needed.
int lz = leading_zeroes(w); int lz = leading_zeroes(w);
@ -114,26 +124,32 @@ adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept {
// The required precision is binary::mantissa_explicit_bits() + 3 because // The required precision is binary::mantissa_explicit_bits() + 3 because
// 1. We need the implicit bit // 1. We need the implicit bit
// 2. We need an extra bit for rounding purposes // 2. We need an extra bit for rounding purposes
// 3. We might lose a bit due to the "upperbit" routine (result too small, requiring a shift) // 3. We might lose a bit due to the "upperbit" routine (result too small,
// requiring a shift)
value128 product = compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w); value128 product =
compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w);
// The computed 'product' is always sufficient. // The computed 'product' is always sufficient.
// Mathematical proof: // Mathematical proof:
// Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to appear) // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to
// See script/mushtak_lemire.py // appear) See script/mushtak_lemire.py
// The "compute_product_approximation" function can be slightly slower than a branchless approach: // The "compute_product_approximation" function can be slightly slower than a
// value128 product = compute_product(q, w); // branchless approach: value128 product = compute_product(q, w); but in
// but in practice, we can win big with the compute_product_approximation if its additional branch // practice, we can win big with the compute_product_approximation if its
// is easily predicted. Which is best is data specific. // additional branch is easily predicted. Which is best is data specific.
int upperbit = int(product.high >> 63); int upperbit = int(product.high >> 63);
int shift = upperbit + 64 - binary::mantissa_explicit_bits() - 3;
answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); answer.mantissa = product.high >> shift;
answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - binary::minimum_exponent()); answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz -
binary::minimum_exponent());
if (answer.power2 <= 0) { // we have a subnormal? if (answer.power2 <= 0) { // we have a subnormal?
// Here have that answer.power2 <= 0 so -answer.power2 >= 0 // Here have that answer.power2 <= 0 so -answer.power2 >= 0
if(-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. if (-answer.power2 + 1 >=
64) { // if we have more than 64 bits below the minimum exponent, you
// have a zero for sure.
answer.power2 = 0; answer.power2 = 0;
answer.mantissa = 0; answer.mantissa = 0;
// result should be zero // result should be zero
@ -142,7 +158,8 @@ adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept {
// next line is safe because -answer.power2 + 1 < 64 // next line is safe because -answer.power2 + 1 < 64
answer.mantissa >>= -answer.power2 + 1; answer.mantissa >>= -answer.power2 + 1;
// Thankfully, we can't have both "round-to-even" and subnormals because // Thankfully, we can't have both "round-to-even" and subnormals because
// "round-to-even" only occurs for powers close to 0. // "round-to-even" only occurs for powers close to 0 in the 32-bit and
// and 64-bit case (with no more than 19 digits).
answer.mantissa += (answer.mantissa & 1); // round up answer.mantissa += (answer.mantissa & 1); // round up
answer.mantissa >>= 1; answer.mantissa >>= 1;
// There is a weird scenario where we don't have a subnormal but just. // There is a weird scenario where we don't have a subnormal but just.
@ -152,20 +169,26 @@ adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept {
// up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer
// subnormal, but we can only know this after rounding. // subnormal, but we can only know this after rounding.
// So we only declare a subnormal if we are smaller than the threshold. // So we only declare a subnormal if we are smaller than the threshold.
answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1; answer.power2 =
(answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits()))
? 0
: 1;
return answer; return answer;
} }
// usually, we round *up*, but if we fall right in between and and we have an // usually, we round *up*, but if we fall right in between and and we have an
// even basis, we need to round down // even basis, we need to round down
// We are only concerned with the cases where 5**q fits in single 64-bit word. // We are only concerned with the cases where 5**q fits in single 64-bit word.
if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) && if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) &&
((answer.mantissa & 3) == 1) ) { // we may fall between two floats! (q <= binary::max_exponent_round_to_even()) &&
((answer.mantissa & 3) == 1)) { // we may fall between two floats!
// To be in-between two floats we need that in doing // To be in-between two floats we need that in doing
// answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); // answer.mantissa = product.high >> (upperbit + 64 -
// ... we dropped out only zeroes. But if this happened, then we can go back!!! // binary::mantissa_explicit_bits() - 3);
if((answer.mantissa << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == product.high) { // ... we dropped out only zeroes. But if this happened, then we can go
answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up // back!!!
if ((answer.mantissa << shift) == product.high) {
answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up
} }
} }

View File

@ -13,19 +13,34 @@
namespace fast_float { namespace fast_float {
// 1e0 to 1e19 // 1e0 to 1e19
constexpr static uint64_t powers_of_ten_uint64[] = { constexpr static uint64_t powers_of_ten_uint64[] = {1UL,
1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL, 10UL,
1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL, 100UL,
100000000000000UL, 1000000000000000UL, 10000000000000000UL, 100000000000000000UL, 1000UL,
1000000000000000000UL, 10000000000000000000UL}; 10000UL,
100000UL,
1000000UL,
10000000UL,
100000000UL,
1000000000UL,
10000000000UL,
100000000000UL,
1000000000000UL,
10000000000000UL,
100000000000000UL,
1000000000000000UL,
10000000000000000UL,
100000000000000000UL,
1000000000000000000UL,
10000000000000000000UL};
// calculate the exponent, in scientific notation, of the number. // calculate the exponent, in scientific notation, of the number.
// this algorithm is not even close to optimized, but it has no practical // this algorithm is not even close to optimized, but it has no practical
// effect on performance: in order to have a faster algorithm, we'd need // effect on performance: in order to have a faster algorithm, we'd need
// to slow down performance for faster algorithms, and this is still fast. // to slow down performance for faster algorithms, and this is still fast.
template <typename UC> template <typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int32_t
int32_t scientific_exponent(parsed_number_string_t<UC> & num) noexcept { scientific_exponent(parsed_number_string_t<UC> &num) noexcept {
uint64_t mantissa = num.mantissa; uint64_t mantissa = num.mantissa;
int32_t exponent = int32_t(num.exponent); int32_t exponent = int32_t(num.exponent);
while (mantissa >= 10000) { while (mantissa >= 10000) {
@ -45,15 +60,16 @@ int32_t scientific_exponent(parsed_number_string_t<UC> & num) noexcept {
// this converts a native floating-point number to an extended-precision float. // this converts a native floating-point number to an extended-precision float.
template <typename T> template <typename T>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
adjusted_mantissa to_extended(T value) noexcept { to_extended(T value) noexcept {
using equiv_uint = typename binary_format<T>::equiv_uint; using equiv_uint = equiv_uint_t<T>;
constexpr equiv_uint exponent_mask = binary_format<T>::exponent_mask(); constexpr equiv_uint exponent_mask = binary_format<T>::exponent_mask();
constexpr equiv_uint mantissa_mask = binary_format<T>::mantissa_mask(); constexpr equiv_uint mantissa_mask = binary_format<T>::mantissa_mask();
constexpr equiv_uint hidden_bit_mask = binary_format<T>::hidden_bit_mask(); constexpr equiv_uint hidden_bit_mask = binary_format<T>::hidden_bit_mask();
adjusted_mantissa am; adjusted_mantissa am;
int32_t bias = binary_format<T>::mantissa_explicit_bits() - binary_format<T>::minimum_exponent(); int32_t bias = binary_format<T>::mantissa_explicit_bits() -
binary_format<T>::minimum_exponent();
equiv_uint bits; equiv_uint bits;
#if FASTFLOAT_HAS_BIT_CAST #if FASTFLOAT_HAS_BIT_CAST
bits = std::bit_cast<equiv_uint>(value); bits = std::bit_cast<equiv_uint>(value);
@ -66,7 +82,8 @@ adjusted_mantissa to_extended(T value) noexcept {
am.mantissa = bits & mantissa_mask; am.mantissa = bits & mantissa_mask;
} else { } else {
// normal // normal
am.power2 = int32_t((bits & exponent_mask) >> binary_format<T>::mantissa_explicit_bits()); am.power2 = int32_t((bits & exponent_mask) >>
binary_format<T>::mantissa_explicit_bits());
am.power2 -= bias; am.power2 -= bias;
am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; am.mantissa = (bits & mantissa_mask) | hidden_bit_mask;
} }
@ -78,8 +95,8 @@ adjusted_mantissa to_extended(T value) noexcept {
// we are given a native float that represents b, so we need to adjust it // we are given a native float that represents b, so we need to adjust it
// halfway between b and b+u. // halfway between b and b+u.
template <typename T> template <typename T>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
adjusted_mantissa to_extended_halfway(T value) noexcept { to_extended_halfway(T value) noexcept {
adjusted_mantissa am = to_extended(value); adjusted_mantissa am = to_extended(value);
am.mantissa <<= 1; am.mantissa <<= 1;
am.mantissa += 1; am.mantissa += 1;
@ -89,15 +106,18 @@ adjusted_mantissa to_extended_halfway(T value) noexcept {
// round an extended-precision float to the nearest machine float. // round an extended-precision float to the nearest machine float.
template <typename T, typename callback> template <typename T, typename callback>
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am,
void round(adjusted_mantissa& am, callback cb) noexcept { callback cb) noexcept {
int32_t mantissa_shift = 64 - binary_format<T>::mantissa_explicit_bits() - 1; int32_t mantissa_shift = 64 - binary_format<T>::mantissa_explicit_bits() - 1;
if (-am.power2 >= mantissa_shift) { if (-am.power2 >= mantissa_shift) {
// have a denormal float // have a denormal float
int32_t shift = -am.power2 + 1; int32_t shift = -am.power2 + 1;
cb(am, std::min<int32_t>(shift, 64)); cb(am, std::min<int32_t>(shift, 64));
// check for round-up: if rounding-nearest carried us to the hidden bit. // check for round-up: if rounding-nearest carried us to the hidden bit.
am.power2 = (am.mantissa < (uint64_t(1) << binary_format<T>::mantissa_explicit_bits())) ? 0 : 1; am.power2 = (am.mantissa <
(uint64_t(1) << binary_format<T>::mantissa_explicit_bits()))
? 0
: 1;
return; return;
} }
@ -105,7 +125,8 @@ void round(adjusted_mantissa& am, callback cb) noexcept {
cb(am, mantissa_shift); cb(am, mantissa_shift);
// check for carry // check for carry
if (am.mantissa >= (uint64_t(2) << binary_format<T>::mantissa_explicit_bits())) { if (am.mantissa >=
(uint64_t(2) << binary_format<T>::mantissa_explicit_bits())) {
am.mantissa = (uint64_t(1) << binary_format<T>::mantissa_explicit_bits()); am.mantissa = (uint64_t(1) << binary_format<T>::mantissa_explicit_bits());
am.power2++; am.power2++;
} }
@ -119,16 +140,11 @@ void round(adjusted_mantissa& am, callback cb) noexcept {
} }
template <typename callback> template <typename callback>
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void
void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept { round_nearest_tie_even(adjusted_mantissa &am, int32_t shift,
const uint64_t mask callback cb) noexcept {
= (shift == 64) uint64_t const mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1;
? UINT64_MAX uint64_t const halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1);
: (uint64_t(1) << shift) - 1;
const uint64_t halfway
= (shift == 0)
? 0
: uint64_t(1) << (shift - 1);
uint64_t truncated_bits = am.mantissa & mask; uint64_t truncated_bits = am.mantissa & mask;
bool is_above = truncated_bits > halfway; bool is_above = truncated_bits > halfway;
bool is_halfway = truncated_bits == halfway; bool is_halfway = truncated_bits == halfway;
@ -145,8 +161,8 @@ void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) n
am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above));
} }
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void
void round_down(adjusted_mantissa& am, int32_t shift) noexcept { round_down(adjusted_mantissa &am, int32_t shift) noexcept {
if (shift == 64) { if (shift == 64) {
am.mantissa = 0; am.mantissa = 0;
} else { } else {
@ -154,11 +170,13 @@ void round_down(adjusted_mantissa& am, int32_t shift) noexcept {
} }
am.power2 += shift; am.power2 += shift;
} }
template <typename UC> template <typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
void skip_zeros(UC const * & first, UC const * last) noexcept { skip_zeros(UC const *&first, UC const *last) noexcept {
uint64_t val; uint64_t val;
while (!cpp20_and_in_constexpr() && std::distance(first, last) >= int_cmp_len<UC>()) { while (!cpp20_and_in_constexpr() &&
std::distance(first, last) >= int_cmp_len<UC>()) {
::memcpy(&val, first, sizeof(uint64_t)); ::memcpy(&val, first, sizeof(uint64_t));
if (val != int_cmp_zeros<UC>()) { if (val != int_cmp_zeros<UC>()) {
break; break;
@ -176,11 +194,12 @@ void skip_zeros(UC const * & first, UC const * last) noexcept {
// determine if any non-zero digits were truncated. // determine if any non-zero digits were truncated.
// all characters must be valid digits. // all characters must be valid digits.
template <typename UC> template <typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
bool is_truncated(UC const * first, UC const * last) noexcept { is_truncated(UC const *first, UC const *last) noexcept {
// do 8-bit optimizations, can just compare to 8 literal 0s. // do 8-bit optimizations, can just compare to 8 literal 0s.
uint64_t val; uint64_t val;
while (!cpp20_and_in_constexpr() && std::distance(first, last) >= int_cmp_len<UC>()) { while (!cpp20_and_in_constexpr() &&
std::distance(first, last) >= int_cmp_len<UC>()) {
::memcpy(&val, first, sizeof(uint64_t)); ::memcpy(&val, first, sizeof(uint64_t));
if (val != int_cmp_zeros<UC>()) { if (val != int_cmp_zeros<UC>()) {
return true; return true;
@ -195,16 +214,17 @@ bool is_truncated(UC const * first, UC const * last) noexcept {
} }
return false; return false;
} }
template <typename UC> template <typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
bool is_truncated(span<const UC> s) noexcept { is_truncated(span<UC const> s) noexcept {
return is_truncated(s.ptr, s.ptr + s.len()); return is_truncated(s.ptr, s.ptr + s.len());
} }
template <typename UC> template <typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
void parse_eight_digits(const UC*& p, limb& value, size_t& counter, size_t& count) noexcept { parse_eight_digits(UC const *&p, limb &value, size_t &counter,
size_t &count) noexcept {
value = value * 100000000 + parse_eight_digits_unrolled(p); value = value * 100000000 + parse_eight_digits_unrolled(p);
p += 8; p += 8;
counter += 8; counter += 8;
@ -212,22 +232,23 @@ void parse_eight_digits(const UC*& p, limb& value, size_t& counter, size_t& coun
} }
template <typename UC> template <typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void
void parse_one_digit(UC const *& p, limb& value, size_t& counter, size_t& count) noexcept { parse_one_digit(UC const *&p, limb &value, size_t &counter,
size_t &count) noexcept {
value = value * 10 + limb(*p - UC('0')); value = value * 10 + limb(*p - UC('0'));
p++; p++;
counter++; counter++;
count++; count++;
} }
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
void add_native(bigint& big, limb power, limb value) noexcept { add_native(bigint &big, limb power, limb value) noexcept {
big.mul(power); big.mul(power);
big.add(value); big.add(value);
} }
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
void round_up_bigint(bigint& big, size_t& count) noexcept { round_up_bigint(bigint &big, size_t &count) noexcept {
// need to round-up the digits, but need to avoid rounding // need to round-up the digits, but need to avoid rounding
// ....9999 to ...10000, which could cause a false halfway point. // ....9999 to ...10000, which could cause a false halfway point.
add_native(big, 10, 1); add_native(big, 10, 1);
@ -236,8 +257,9 @@ void round_up_bigint(bigint& big, size_t& count) noexcept {
// parse the significant digits into a big integer // parse the significant digits into a big integer
template <typename UC> template <typename UC>
inline FASTFLOAT_CONSTEXPR20 inline FASTFLOAT_CONSTEXPR20 void
void parse_mantissa(bigint& result, parsed_number_string_t<UC>& num, size_t max_digits, size_t& digits) noexcept { parse_mantissa(bigint &result, parsed_number_string_t<UC> &num,
size_t max_digits, size_t &digits) noexcept {
// try to minimize the number of big integer and scalar multiplication. // try to minimize the number of big integer and scalar multiplication.
// therefore, try to parse 8 digits at a time, and multiply by the largest // therefore, try to parse 8 digits at a time, and multiply by the largest
// scalar value (9 or 19 digits) for each step. // scalar value (9 or 19 digits) for each step.
@ -251,12 +273,13 @@ void parse_mantissa(bigint& result, parsed_number_string_t<UC>& num, size_t max_
#endif #endif
// process all integer digits. // process all integer digits.
UC const * p = num.integer.ptr; UC const *p = num.integer.ptr;
UC const * pend = p + num.integer.len(); UC const *pend = p + num.integer.len();
skip_zeros(p, pend); skip_zeros(p, pend);
// process all digits, in increments of step per loop // process all digits, in increments of step per loop
while (p != pend) { while (p != pend) {
while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { while ((std::distance(p, pend) >= 8) && (step - counter >= 8) &&
(max_digits - digits >= 8)) {
parse_eight_digits(p, value, counter, digits); parse_eight_digits(p, value, counter, digits);
} }
while (counter < step && p != pend && digits < max_digits) { while (counter < step && p != pend && digits < max_digits) {
@ -289,7 +312,8 @@ void parse_mantissa(bigint& result, parsed_number_string_t<UC>& num, size_t max_
} }
// process all digits, in increments of step per loop // process all digits, in increments of step per loop
while (p != pend) { while (p != pend) {
while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { while ((std::distance(p, pend) >= 8) && (step - counter >= 8) &&
(max_digits - digits >= 8)) {
parse_eight_digits(p, value, counter, digits); parse_eight_digits(p, value, counter, digits);
} }
while (counter < step && p != pend && digits < max_digits) { while (counter < step && p != pend && digits < max_digits) {
@ -317,19 +341,23 @@ void parse_mantissa(bigint& result, parsed_number_string_t<UC>& num, size_t max_
} }
template <typename T> template <typename T>
inline FASTFLOAT_CONSTEXPR20 inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcept { positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept {
FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent)));
adjusted_mantissa answer; adjusted_mantissa answer;
bool truncated; bool truncated;
answer.mantissa = bigmant.hi64(truncated); answer.mantissa = bigmant.hi64(truncated);
int bias = binary_format<T>::mantissa_explicit_bits() - binary_format<T>::minimum_exponent(); int bias = binary_format<T>::mantissa_explicit_bits() -
binary_format<T>::minimum_exponent();
answer.power2 = bigmant.bit_length() - 64 + bias; answer.power2 = bigmant.bit_length() - 64 + bias;
round<T>(answer, [truncated](adjusted_mantissa& a, int32_t shift) { round<T>(answer, [truncated](adjusted_mantissa &a, int32_t shift) {
round_nearest_tie_even(a, shift, [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { round_nearest_tie_even(
return is_above || (is_halfway && truncated) || (is_odd && is_halfway); a, shift,
}); [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool {
return is_above || (is_halfway && truncated) ||
(is_odd && is_halfway);
});
}); });
return answer; return answer;
@ -341,15 +369,17 @@ adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcep
// we then need to scale by `2^(f- e)`, and then the two significant digits // we then need to scale by `2^(f- e)`, and then the two significant digits
// are of the same magnitude. // are of the same magnitude.
template <typename T> template <typename T>
inline FASTFLOAT_CONSTEXPR20 inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp(
adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int32_t exponent) noexcept { bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept {
bigint& real_digits = bigmant; bigint &real_digits = bigmant;
int32_t real_exp = exponent; int32_t real_exp = exponent;
// get the value of `b`, rounded down, and get a bigint representation of b+h // get the value of `b`, rounded down, and get a bigint representation of b+h
adjusted_mantissa am_b = am; adjusted_mantissa am_b = am;
// gcc7 buf: use a lambda to remove the noexcept qualifier bug with -Wnoexcept-type. // gcc7 buf: use a lambda to remove the noexcept qualifier bug with
round<T>(am_b, [](adjusted_mantissa&a, int32_t shift) { round_down(a, shift); }); // -Wnoexcept-type.
round<T>(am_b,
[](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); });
T b; T b;
to_float(false, am_b, b); to_float(false, am_b, b);
adjusted_mantissa theor = to_extended_halfway(b); adjusted_mantissa theor = to_extended_halfway(b);
@ -371,18 +401,19 @@ adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int
// compare digits, and use it to director rounding // compare digits, and use it to director rounding
int ord = real_digits.compare(theor_digits); int ord = real_digits.compare(theor_digits);
adjusted_mantissa answer = am; adjusted_mantissa answer = am;
round<T>(answer, [ord](adjusted_mantissa& a, int32_t shift) { round<T>(answer, [ord](adjusted_mantissa &a, int32_t shift) {
round_nearest_tie_even(a, shift, [ord](bool is_odd, bool _, bool __) -> bool { round_nearest_tie_even(
(void)_; // not needed, since we've done our comparison a, shift, [ord](bool is_odd, bool _, bool __) -> bool {
(void)__; // not needed, since we've done our comparison (void)_; // not needed, since we've done our comparison
if (ord > 0) { (void)__; // not needed, since we've done our comparison
return true; if (ord > 0) {
} else if (ord < 0) { return true;
return false; } else if (ord < 0) {
} else { return false;
return is_odd; } else {
} return is_odd;
}); }
});
}); });
return answer; return answer;
@ -402,8 +433,8 @@ adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int
// the actual digits. we then compare the big integer representations // the actual digits. we then compare the big integer representations
// of both, and use that to direct rounding. // of both, and use that to direct rounding.
template <typename T, typename UC> template <typename T, typename UC>
inline FASTFLOAT_CONSTEXPR20 inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
adjusted_mantissa digit_comp(parsed_number_string_t<UC>& num, adjusted_mantissa am) noexcept { digit_comp(parsed_number_string_t<UC> &num, adjusted_mantissa am) noexcept {
// remove the invalid exponent bias // remove the invalid exponent bias
am.power2 -= invalid_am_bias; am.power2 -= invalid_am_bias;

View File

@ -6,43 +6,54 @@
namespace fast_float { namespace fast_float {
/** /**
* This function parses the character sequence [first,last) for a number. It parses floating-point numbers expecting * This function parses the character sequence [first,last) for a number. It
* a locale-indepent format equivalent to what is used by std::strtod in the default ("C") locale. * parses floating-point numbers expecting a locale-indepent format equivalent
* The resulting floating-point value is the closest floating-point values (using either float or double), * to what is used by std::strtod in the default ("C") locale. The resulting
* using the "round to even" convention for values that would otherwise fall right in-between two values. * floating-point value is the closest floating-point values (using either float
* That is, we provide exact parsing according to the IEEE standard. * or double), using the "round to even" convention for values that would
* otherwise fall right in-between two values. That is, we provide exact parsing
* according to the IEEE standard.
* *
* Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the * Given a successful parse, the pointer (`ptr`) in the returned value is set to
* parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned * point right after the parsed number, and the `value` referenced is set to the
* `ec` contains a representative error, otherwise the default (`std::errc()`) value is stored. * parsed value. In case of error, the returned `ec` contains a representative
* error, otherwise the default (`std::errc()`) value is stored.
* *
* The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`). * The implementation does not throw and does not allocate memory (e.g., with
* `new` or `malloc`).
* *
* Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of * Like the C++17 standard, the `fast_float::from_chars` functions take an
* the type `fast_float::chars_format`. It is a bitset value: we check whether * optional last argument of the type `fast_float::chars_format`. It is a bitset
* `fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set * value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt &
* to determine whether we allow the fixed point and scientific notation respectively. * fast_float::chars_format::scientific` are set to determine whether we allow
* The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. * the fixed point and scientific notation respectively. The default is
* `fast_float::chars_format::general` which allows both `fixed` and
* `scientific`.
*/ */
template<typename T, typename UC = char, typename = FASTFLOAT_ENABLE_IF(is_supported_float_type<T>())> template <typename T, typename UC = char,
FASTFLOAT_CONSTEXPR20 typename = FASTFLOAT_ENABLE_IF(is_supported_float_type<T>::value)>
from_chars_result_t<UC> from_chars(UC const * first, UC const * last, FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
T &value, chars_format fmt = chars_format::general) noexcept; from_chars(UC const *first, UC const *last, T &value,
chars_format fmt = chars_format::general) noexcept;
/** /**
* Like from_chars, but accepts an `options` argument to govern number parsing. * Like from_chars, but accepts an `options` argument to govern number parsing.
* Both for floating-point types and integer types.
*/ */
template<typename T, typename UC = char> template <typename T, typename UC = char>
FASTFLOAT_CONSTEXPR20 FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
from_chars_result_t<UC> from_chars_advanced(UC const * first, UC const * last, from_chars_advanced(UC const *first, UC const *last, T &value,
T &value, parse_options_t<UC> options) noexcept; parse_options_t<UC> options) noexcept;
/** /**
* from_chars for integer types. * from_chars for integer types.
*/ */
template <typename T, typename UC = char, typename = FASTFLOAT_ENABLE_IF(!is_supported_float_type<T>())> template <typename T, typename UC = char,
FASTFLOAT_CONSTEXPR20 typename = FASTFLOAT_ENABLE_IF(is_supported_integer_type<T>::value)>
from_chars_result_t<UC> from_chars(UC const * first, UC const * last, T& value, int base = 10) noexcept; FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
from_chars(UC const *first, UC const *last, T &value, int base = 10) noexcept;
} // namespace fast_float } // namespace fast_float
#include "parse_number.h" #include "parse_number.h"
#endif // FASTFLOAT_FAST_FLOAT_H #endif // FASTFLOAT_FAST_FLOAT_H

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,8 @@
#include <cstring> #include <cstring>
#include <limits> #include <limits>
#include <system_error> #include <system_error>
namespace fast_float {
namespace fast_float {
namespace detail { namespace detail {
/** /**
@ -20,45 +20,49 @@ namespace detail {
* strings a null-free and fixed. * strings a null-free and fixed.
**/ **/
template <typename T, typename UC> template <typename T, typename UC>
from_chars_result_t<UC> FASTFLOAT_CONSTEXPR14 from_chars_result_t<UC>
parse_infnan(UC const * first, UC const * last, T &value) noexcept { FASTFLOAT_CONSTEXPR14 parse_infnan(UC const *first, UC const *last,
T &value, chars_format fmt) noexcept {
from_chars_result_t<UC> answer{}; from_chars_result_t<UC> answer{};
answer.ptr = first; answer.ptr = first;
answer.ec = std::errc(); // be optimistic answer.ec = std::errc(); // be optimistic
bool minusSign = false; // assume first < last, so dereference without checks;
if (*first == UC('-')) { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here bool const minusSign = (*first == UC('-'));
minusSign = true; // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
++first; if ((*first == UC('-')) ||
(uint64_t(fmt & chars_format::allow_leading_plus) &&
(*first == UC('+')))) {
++first;
} }
#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default
if (*first == UC('+')) {
++first;
}
#endif
if (last - first >= 3) { if (last - first >= 3) {
if (fastfloat_strncasecmp(first, str_const_nan<UC>(), 3)) { if (fastfloat_strncasecmp(first, str_const_nan<UC>(), 3)) {
answer.ptr = (first += 3); answer.ptr = (first += 3);
value = minusSign ? -std::numeric_limits<T>::quiet_NaN() : std::numeric_limits<T>::quiet_NaN(); value = minusSign ? -std::numeric_limits<T>::quiet_NaN()
// Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). : std::numeric_limits<T>::quiet_NaN();
if(first != last && *first == UC('(')) { // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7,
for(UC const * ptr = first + 1; ptr != last; ++ptr) { // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan).
if (first != last && *first == UC('(')) {
for (UC const *ptr = first + 1; ptr != last; ++ptr) {
if (*ptr == UC(')')) { if (*ptr == UC(')')) {
answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) answer.ptr = ptr + 1; // valid nan(n-char-seq-opt)
break; break;
} } else if (!((UC('a') <= *ptr && *ptr <= UC('z')) ||
else if(!((UC('a') <= *ptr && *ptr <= UC('z')) || (UC('A') <= *ptr && *ptr <= UC('Z')) || (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) (UC('A') <= *ptr && *ptr <= UC('Z')) ||
(UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_')))
break; // forbidden char, not nan(n-char-seq-opt) break; // forbidden char, not nan(n-char-seq-opt)
} }
} }
return answer; return answer;
} }
if (fastfloat_strncasecmp(first, str_const_inf<UC>(), 3)) { if (fastfloat_strncasecmp(first, str_const_inf<UC>(), 3)) {
if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, str_const_inf<UC>() + 3, 5)) { if ((last - first >= 8) &&
fastfloat_strncasecmp(first + 3, str_const_inf<UC>() + 3, 5)) {
answer.ptr = first + 8; answer.ptr = first + 8;
} else { } else {
answer.ptr = first + 3; answer.ptr = first + 3;
} }
value = minusSign ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::infinity(); value = minusSign ? -std::numeric_limits<T>::infinity()
: std::numeric_limits<T>::infinity();
return answer; return answer;
} }
} }
@ -86,73 +90,71 @@ fastfloat_really_inline bool rounds_to_nearest() noexcept {
// However, it is expected to be much faster than the fegetround() // However, it is expected to be much faster than the fegetround()
// function call. // function call.
// //
// The volatile keywoard prevents the compiler from computing the function // The volatile keyword prevents the compiler from computing the function
// at compile-time. // at compile-time.
// There might be other ways to prevent compile-time optimizations (e.g., asm). // There might be other ways to prevent compile-time optimizations (e.g.,
// The value does not need to be std::numeric_limits<float>::min(), any small // asm). The value does not need to be std::numeric_limits<float>::min(), any
// value so that 1 + x should round to 1 would do (after accounting for excess // small value so that 1 + x should round to 1 would do (after accounting for
// precision, as in 387 instructions). // excess precision, as in 387 instructions).
static volatile float fmin = std::numeric_limits<float>::min(); static float volatile fmin = std::numeric_limits<float>::min();
float fmini = fmin; // we copy it so that it gets loaded at most once. float fmini = fmin; // we copy it so that it gets loaded at most once.
// //
// Explanation: // Explanation:
// Only when fegetround() == FE_TONEAREST do we have that // Only when fegetround() == FE_TONEAREST do we have that
// fmin + 1.0f == 1.0f - fmin. // fmin + 1.0f == 1.0f - fmin.
// //
// FE_UPWARD: // FE_UPWARD:
// fmin + 1.0f > 1 // fmin + 1.0f > 1
// 1.0f - fmin == 1 // 1.0f - fmin == 1
// //
// FE_DOWNWARD or FE_TOWARDZERO: // FE_DOWNWARD or FE_TOWARDZERO:
// fmin + 1.0f == 1 // fmin + 1.0f == 1
// 1.0f - fmin < 1 // 1.0f - fmin < 1
// //
// Note: This may fail to be accurate if fast-math has been // Note: This may fail to be accurate if fast-math has been
// enabled, as rounding conventions may not apply. // enabled, as rounding conventions may not apply.
#ifdef FASTFLOAT_VISUAL_STUDIO #ifdef FASTFLOAT_VISUAL_STUDIO
# pragma warning(push) #pragma warning(push)
// todo: is there a VS warning? // todo: is there a VS warning?
// see https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 // see
#elif defined(__clang__) // https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013
# pragma clang diagnostic push #elif defined(__clang__)
# pragma clang diagnostic ignored "-Wfloat-equal" #pragma clang diagnostic push
#elif defined(__GNUC__) #pragma clang diagnostic ignored "-Wfloat-equal"
# pragma GCC diagnostic push #elif defined(__GNUC__)
# pragma GCC diagnostic ignored "-Wfloat-equal" #pragma GCC diagnostic push
#endif #pragma GCC diagnostic ignored "-Wfloat-equal"
#endif
return (fmini + 1.0f == 1.0f - fmini); return (fmini + 1.0f == 1.0f - fmini);
#ifdef FASTFLOAT_VISUAL_STUDIO #ifdef FASTFLOAT_VISUAL_STUDIO
# pragma warning(pop) #pragma warning(pop)
#elif defined(__clang__) #elif defined(__clang__)
# pragma clang diagnostic pop #pragma clang diagnostic pop
#elif defined(__GNUC__) #elif defined(__GNUC__)
# pragma GCC diagnostic pop #pragma GCC diagnostic pop
#endif #endif
} }
} // namespace detail } // namespace detail
template <typename T> template <typename T> struct from_chars_caller {
struct from_chars_caller
{
template <typename UC> template <typename UC>
FASTFLOAT_CONSTEXPR20 FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
static from_chars_result_t<UC> call(UC const * first, UC const * last, call(UC const *first, UC const *last, T &value,
T &value, parse_options_t<UC> options) noexcept { parse_options_t<UC> options) noexcept {
return from_chars_advanced(first, last, value, options); return from_chars_advanced(first, last, value, options);
} }
}; };
#if __STDCPP_FLOAT32_T__ == 1 #ifdef __STDCPP_FLOAT32_T__
template <> template <> struct from_chars_caller<std::float32_t> {
struct from_chars_caller<std::float32_t>
{
template <typename UC> template <typename UC>
FASTFLOAT_CONSTEXPR20 FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
static from_chars_result_t<UC> call(UC const * first, UC const * last, call(UC const *first, UC const *last, std::float32_t &value,
std::float32_t &value, parse_options_t<UC> options) noexcept{ parse_options_t<UC> options) noexcept {
// if std::float32_t is defined, and we are in C++23 mode; macro set for float32; // if std::float32_t is defined, and we are in C++23 mode; macro set for
// set value to float due to equivalence between float and float32_t // float32; set value to float due to equivalence between float and
// float32_t
float val; float val;
auto ret = from_chars_advanced(first, last, val, options); auto ret = from_chars_advanced(first, last, val, options);
value = val; value = val;
@ -161,16 +163,15 @@ struct from_chars_caller<std::float32_t>
}; };
#endif #endif
#if __STDCPP_FLOAT64_T__ == 1 #ifdef __STDCPP_FLOAT64_T__
template <> template <> struct from_chars_caller<std::float64_t> {
struct from_chars_caller<std::float64_t>
{
template <typename UC> template <typename UC>
FASTFLOAT_CONSTEXPR20 FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
static from_chars_result_t<UC> call(UC const * first, UC const * last, call(UC const *first, UC const *last, std::float64_t &value,
std::float64_t &value, parse_options_t<UC> options) noexcept{ parse_options_t<UC> options) noexcept {
// if std::float64_t is defined, and we are in C++23 mode; macro set for float64; // if std::float64_t is defined, and we are in C++23 mode; macro set for
// set value as double due to equivalence between double and float64_t // float64; set value as double due to equivalence between double and
// float64_t
double val; double val;
auto ret = from_chars_advanced(first, last, val, options); auto ret = from_chars_advanced(first, last, val, options);
value = val; value = val;
@ -179,52 +180,40 @@ struct from_chars_caller<std::float64_t>
}; };
#endif #endif
template <typename T, typename UC, typename>
template<typename T, typename UC, typename> FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
FASTFLOAT_CONSTEXPR20 from_chars(UC const *first, UC const *last, T &value,
from_chars_result_t<UC> from_chars(UC const * first, UC const * last, chars_format fmt /*= chars_format::general*/) noexcept {
T &value, chars_format fmt /*= chars_format::general*/) noexcept { return from_chars_caller<T>::call(first, last, value,
return from_chars_caller<T>::call(first, last, value, parse_options_t<UC>(fmt)); parse_options_t<UC>(fmt));
} }
template<typename T, typename UC> /**
FASTFLOAT_CONSTEXPR20 * This function overload takes parsed_number_string_t structure that is created
from_chars_result_t<UC> from_chars_advanced(UC const * first, UC const * last, * and populated either by from_chars_advanced function taking chars range and
T &value, parse_options_t<UC> options) noexcept { * parsing options or other parsing custom function implemented by user.
*/
template <typename T, typename UC>
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
from_chars_advanced(parsed_number_string_t<UC> &pns, T &value) noexcept {
static_assert (is_supported_float_type<T>(), "only some floating-point types are supported"); static_assert(is_supported_float_type<T>::value,
static_assert (is_supported_char_type<UC>(), "only char, wchar_t, char16_t and char32_t are supported"); "only some floating-point types are supported");
static_assert(is_supported_char_type<UC>::value,
"only char, wchar_t, char16_t and char32_t are supported");
from_chars_result_t<UC> answer; from_chars_result_t<UC> answer;
#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default
while ((first != last) && fast_float::is_space(uint8_t(*first))) {
first++;
}
#endif
if (first == last) {
answer.ec = std::errc::invalid_argument;
answer.ptr = first;
return answer;
}
parsed_number_string_t<UC> pns = parse_number_string<UC>(first, last, options);
if (!pns.valid) {
if (options.format & chars_format::no_infnan) {
answer.ec = std::errc::invalid_argument;
answer.ptr = first;
return answer;
} else {
return detail::parse_infnan(first, last, value);
}
}
answer.ec = std::errc(); // be optimistic answer.ec = std::errc(); // be optimistic
answer.ptr = pns.lastmatch; answer.ptr = pns.lastmatch;
// The implementation of the Clinger's fast path is convoluted because // The implementation of the Clinger's fast path is convoluted because
// we want round-to-nearest in all cases, irrespective of the rounding mode // we want round-to-nearest in all cases, irrespective of the rounding mode
// selected on the thread. // selected on the thread.
// We proceed optimistically, assuming that detail::rounds_to_nearest() returns // We proceed optimistically, assuming that detail::rounds_to_nearest()
// true. // returns true.
if (binary_format<T>::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format<T>::max_exponent_fast_path() && !pns.too_many_digits) { if (binary_format<T>::min_exponent_fast_path() <= pns.exponent &&
pns.exponent <= binary_format<T>::max_exponent_fast_path() &&
!pns.too_many_digits) {
// Unfortunately, the conventional Clinger's fast path is only possible // Unfortunately, the conventional Clinger's fast path is only possible
// when the system rounds to the nearest float. // when the system rounds to the nearest float.
// //
@ -232,68 +221,179 @@ from_chars_result_t<UC> from_chars_advanced(UC const * first, UC const * last,
// We could check it first (before the previous branch), but // We could check it first (before the previous branch), but
// there might be performance advantages at having the check // there might be performance advantages at having the check
// be last. // be last.
if(!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) {
// We have that fegetround() == FE_TONEAREST. // We have that fegetround() == FE_TONEAREST.
// Next is Clinger's fast path. // Next is Clinger's fast path.
if (pns.mantissa <=binary_format<T>::max_mantissa_fast_path()) { if (pns.mantissa <= binary_format<T>::max_mantissa_fast_path()) {
value = T(pns.mantissa); value = T(pns.mantissa);
if (pns.exponent < 0) { value = value / binary_format<T>::exact_power_of_ten(-pns.exponent); } if (pns.exponent < 0) {
else { value = value * binary_format<T>::exact_power_of_ten(pns.exponent); } value = value / binary_format<T>::exact_power_of_ten(-pns.exponent);
if (pns.negative) { value = -value; } } else {
value = value * binary_format<T>::exact_power_of_ten(pns.exponent);
}
if (pns.negative) {
value = -value;
}
return answer; return answer;
} }
} else { } else {
// We do not have that fegetround() == FE_TONEAREST. // We do not have that fegetround() == FE_TONEAREST.
// Next is a modified Clinger's fast path, inspired by Jakub Jelínek's proposal // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's
if (pns.exponent >= 0 && pns.mantissa <=binary_format<T>::max_mantissa_fast_path(pns.exponent)) { // proposal
if (pns.exponent >= 0 &&
pns.mantissa <=
binary_format<T>::max_mantissa_fast_path(pns.exponent)) {
#if defined(__clang__) || defined(FASTFLOAT_32BIT) #if defined(__clang__) || defined(FASTFLOAT_32BIT)
// Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD
if(pns.mantissa == 0) { if (pns.mantissa == 0) {
value = pns.negative ? T(-0.) : T(0.); value = pns.negative ? T(-0.) : T(0.);
return answer; return answer;
} }
#endif #endif
value = T(pns.mantissa) * binary_format<T>::exact_power_of_ten(pns.exponent); value = T(pns.mantissa) *
if (pns.negative) { value = -value; } binary_format<T>::exact_power_of_ten(pns.exponent);
if (pns.negative) {
value = -value;
}
return answer; return answer;
} }
} }
} }
adjusted_mantissa am = compute_float<binary_format<T>>(pns.exponent, pns.mantissa); adjusted_mantissa am =
if(pns.too_many_digits && am.power2 >= 0) { compute_float<binary_format<T>>(pns.exponent, pns.mantissa);
if(am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) { if (pns.too_many_digits && am.power2 >= 0) {
if (am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) {
am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa); am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa);
} }
} }
// If we called compute_float<binary_format<T>>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0), // If we called compute_float<binary_format<T>>(pns.exponent, pns.mantissa)
// then we need to go the long way around again. This is very uncommon. // and we have an invalid power (am.power2 < 0), then we need to go the long
if(am.power2 < 0) { am = digit_comp<T>(pns, am); } // way around again. This is very uncommon.
if (am.power2 < 0) {
am = digit_comp<T>(pns, am);
}
to_float(pns.negative, am, value); to_float(pns.negative, am, value);
// Test for over/underflow. // Test for over/underflow.
if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || am.power2 == binary_format<T>::infinite_power()) { 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; answer.ec = std::errc::result_out_of_range;
} }
return answer; return answer;
} }
template <typename T, typename UC>
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
from_chars_float_advanced(UC const *first, UC const *last, T &value,
parse_options_t<UC> options) noexcept {
template <typename T, typename UC, typename> static_assert(is_supported_float_type<T>::value,
FASTFLOAT_CONSTEXPR20 "only some floating-point types are supported");
from_chars_result_t<UC> from_chars(UC const* first, UC const* last, T& value, int base) noexcept { static_assert(is_supported_char_type<UC>::value,
static_assert (is_supported_char_type<UC>(), "only char, wchar_t, char16_t and char32_t are supported"); "only char, wchar_t, char16_t and char32_t are supported");
chars_format const fmt = detail::adjust_for_feature_macros(options.format);
from_chars_result_t<UC> answer; from_chars_result_t<UC> answer;
#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default if (uint64_t(fmt & chars_format::skip_white_space)) {
while ((first != last) && fast_float::is_space(uint8_t(*first))) { while ((first != last) && fast_float::is_space(*first)) {
first++; first++;
}
}
if (first == last) {
answer.ec = std::errc::invalid_argument;
answer.ptr = first;
return answer;
}
parsed_number_string_t<UC> pns =
uint64_t(fmt & detail::basic_json_fmt)
? parse_number_string<true, UC>(first, last, options)
: parse_number_string<false, UC>(first, last, options);
if (!pns.valid) {
if (uint64_t(fmt & chars_format::no_infnan)) {
answer.ec = std::errc::invalid_argument;
answer.ptr = first;
return answer;
} else {
return detail::parse_infnan(first, last, value, fmt);
}
}
// call overload that takes parsed_number_string_t directly.
return from_chars_advanced(pns, value);
}
template <typename T, typename UC, typename>
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
from_chars(UC const *first, UC const *last, T &value, int base) noexcept {
static_assert(is_supported_integer_type<T>::value,
"only integer types are supported");
static_assert(is_supported_char_type<UC>::value,
"only char, wchar_t, char16_t and char32_t are supported");
parse_options_t<UC> options;
options.base = base;
return from_chars_advanced(first, last, value, options);
}
template <typename T, typename UC>
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
from_chars_int_advanced(UC const *first, UC const *last, T &value,
parse_options_t<UC> options) noexcept {
static_assert(is_supported_integer_type<T>::value,
"only integer types are supported");
static_assert(is_supported_char_type<UC>::value,
"only char, wchar_t, char16_t and char32_t are supported");
chars_format const fmt = detail::adjust_for_feature_macros(options.format);
int const base = options.base;
from_chars_result_t<UC> answer;
if (uint64_t(fmt & chars_format::skip_white_space)) {
while ((first != last) && fast_float::is_space(*first)) {
first++;
}
} }
#endif
if (first == last || base < 2 || base > 36) { if (first == last || base < 2 || base > 36) {
answer.ec = std::errc::invalid_argument; answer.ec = std::errc::invalid_argument;
answer.ptr = first; answer.ptr = first;
return answer; return answer;
} }
return parse_int_string(first, last, value, base);
return parse_int_string(first, last, value, options);
}
template <size_t TypeIx> struct from_chars_advanced_caller {
static_assert(TypeIx > 0, "unsupported type");
};
template <> struct from_chars_advanced_caller<1> {
template <typename T, typename UC>
FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
call(UC const *first, UC const *last, T &value,
parse_options_t<UC> options) noexcept {
return from_chars_float_advanced(first, last, value, options);
}
};
template <> struct from_chars_advanced_caller<2> {
template <typename T, typename UC>
FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
call(UC const *first, UC const *last, T &value,
parse_options_t<UC> options) noexcept {
return from_chars_int_advanced(first, last, value, options);
}
};
template <typename T, typename UC>
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
from_chars_advanced(UC const *first, UC const *last, T &value,
parse_options_t<UC> options) noexcept {
return from_chars_advanced_caller<
size_t(is_supported_float_type<T>::value) +
2 * size_t(is_supported_integer_type<T>::value)>::call(first, last, value,
options);
} }
} // namespace fast_float } // namespace fast_float