mirror of
https://github.com/fastfloat/fast_float.git
synced 2026-11-10 07:09:53 +08:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ec59686a1 | ||
|
|
9ed4b90638 | ||
|
|
0b21c2eadb | ||
|
|
8cd1810adb | ||
|
|
dbeabeeb3e | ||
|
|
9054aa02be | ||
|
|
33785d5d61 | ||
|
|
4b37bda237 | ||
|
|
78b6a96f28 | ||
|
|
0de7a21da5 | ||
|
|
638a8acd6d | ||
|
|
90513adca1 | ||
|
|
166d2b3df2 | ||
|
|
9cd6dfc8e3 | ||
|
|
033f64f608 | ||
|
|
e7e0cfcd4f |
8
.github/workflows/ubuntu22-clang.yml
vendored
8
.github/workflows/ubuntu22-clang.yml
vendored
@ -9,6 +9,14 @@ jobs:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Install clang++-14
|
||||
run: sudo apt-get install -y clang++-14
|
||||
- name: Check for pedantic warnings in pre-C++20 modes (issue 427)
|
||||
run: |
|
||||
set -xe
|
||||
printf '#include "fast_float/fast_float.h"\nint main() { return 0; }\n' > pedantic_check.cpp
|
||||
for std in c++11 c++14 c++17 c++20; do
|
||||
clang++-14 -std=$std -Wall -Wextra -pedantic-errors -Werror \
|
||||
-Iinclude -fsyntax-only pedantic_check.cpp
|
||||
done
|
||||
- name: Use cmake
|
||||
run: |
|
||||
mkdir build &&
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
|
||||
project(fast_float VERSION 8.3.2 LANGUAGES CXX)
|
||||
project(fast_float VERSION 8.3.3 LANGUAGES CXX)
|
||||
set(FASTFLOAT_CXX_STANDARD 11 CACHE STRING "the C++ standard to use for fastfloat")
|
||||
set(CMAKE_CXX_STANDARD ${FASTFLOAT_CXX_STANDARD})
|
||||
option(FASTFLOAT_TEST "Enable tests" OFF)
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
module(
|
||||
name = "fast_float",
|
||||
version = "8.3.2",
|
||||
version = "8.3.3",
|
||||
compatibility_level = 8,
|
||||
)
|
||||
|
||||
|
||||
67
README.md
67
README.md
@ -155,8 +155,9 @@ Furthermore, we have the following restrictions:
|
||||
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`).
|
||||
The results are rounded to the nearest floating-point value, whatever the
|
||||
rounding mode of the thread (`std::fegetround()`). See
|
||||
[Assuming the default rounding mode](#assuming-the-default-rounding-mode).
|
||||
|
||||
## Integer types
|
||||
|
||||
@ -482,9 +483,59 @@ float: 12345678 * 10^23 = 1.23456782e+30 (==expected)
|
||||
```
|
||||
|
||||
Overloads of `fast_float::integer_times_pow10()` are provided for
|
||||
signed and unsigned integer types: `int64_t`, `uint64_t`, etc.
|
||||
signed and unsigned integer types up to 64 bits: `int64_t`, `uint64_t`, etc.
|
||||
|
||||
|
||||
## Assuming the default rounding mode
|
||||
|
||||
The C and C++ languages let a program change the floating-point rounding
|
||||
mode of a thread with `std::fesetround` (e.g., `FE_UPWARD`). fast_float
|
||||
returns the value rounded to the nearest floating-point number (ties to
|
||||
even) whatever the current rounding mode.
|
||||
|
||||
Most numbers that people write, such as `1234.56` or `-0.25`, are converted
|
||||
with a single multiplication or division by an exact power of ten (Clinger's
|
||||
fast path). That operation is correctly rounded only in the default mode
|
||||
(`FE_TONEAREST`), so fast_float checks the rounding mode before it takes the
|
||||
fast path, every time. The check is cheap (a load from a `volatile` variable,
|
||||
two additions and a comparison), but when you parse short numbers in a tight
|
||||
loop, it can be a noticeable fraction of the time, depending on your compiler
|
||||
and processor.
|
||||
|
||||
If your program never changes the rounding mode, you can skip the check by
|
||||
defining the `FASTFLOAT_ROUNDS_TO_NEAREST` macro:
|
||||
|
||||
```C++
|
||||
#define FASTFLOAT_ROUNDS_TO_NEAREST
|
||||
#include "fast_float/fast_float.h"
|
||||
```
|
||||
|
||||
It affects `from_chars`, `from_chars_advanced` and `integer_times_pow10`.
|
||||
|
||||
Before you define it, consider the tradeoff:
|
||||
|
||||
* **You make a promise.** With the macro, fast_float trusts that the rounding
|
||||
mode is `FE_TONEAREST`. If some code in your process (yours, or a library
|
||||
you call) sets another mode, the numbers that take the fast path are
|
||||
rounded according to that mode, while the other numbers are still rounded
|
||||
to the nearest value. The results are then not correctly rounded, and not
|
||||
even consistent with one another. For example, with `FE_UPWARD`, `0.3`
|
||||
becomes `0.30000000000000004` but `0.30000000000000000000001` becomes
|
||||
`0.29999999999999999`: the larger input gives the smaller value. Nothing
|
||||
crashes and nothing warns you. Without the macro, fast_float is correct in
|
||||
every rounding mode.
|
||||
* **It is only a speedup on short inputs.** Long numbers (more than about 15
|
||||
significant digits, or large exponents) never take the fast path, so they
|
||||
gain nothing.
|
||||
* **Define it the same way everywhere.** The macro changes inline functions,
|
||||
so every translation unit that includes fast_float should see the same
|
||||
definition. The simplest is to set it for the whole build
|
||||
(`-DFASTFLOAT_ROUNDS_TO_NEAREST`, or
|
||||
`target_compile_definitions(myapp PRIVATE FASTFLOAT_ROUNDS_TO_NEAREST)` in
|
||||
CMake) rather than in a source file.
|
||||
|
||||
If in doubt, leave the macro undefined: the default is always correct.
|
||||
|
||||
## Users and Related Work
|
||||
|
||||
The fast_float library is part of:
|
||||
@ -587,7 +638,7 @@ sufficiently recent version of CMake (3.11 or better at least):
|
||||
FetchContent_Declare(
|
||||
fast_float
|
||||
GIT_REPOSITORY https://github.com/fastfloat/fast_float.git
|
||||
GIT_TAG tags/v8.3.2
|
||||
GIT_TAG tags/v8.3.3
|
||||
GIT_SHALLOW TRUE)
|
||||
|
||||
FetchContent_MakeAvailable(fast_float)
|
||||
@ -603,7 +654,7 @@ You may also use [CPM](https://github.com/cpm-cmake/CPM.cmake), like so:
|
||||
CPMAddPackage(
|
||||
NAME fast_float
|
||||
GITHUB_REPOSITORY "fastfloat/fast_float"
|
||||
GIT_TAG v8.3.2)
|
||||
GIT_TAG v8.3.3)
|
||||
```
|
||||
|
||||
## Using as single header
|
||||
@ -615,7 +666,7 @@ if desired as described in the command line help.
|
||||
|
||||
You may directly download automatically generated single-header files:
|
||||
|
||||
<https://github.com/fastfloat/fast_float/releases/download/v8.3.2/fast_float.h>
|
||||
<https://github.com/fastfloat/fast_float/releases/download/v8.3.3/fast_float.h>
|
||||
|
||||
## Benchmarking
|
||||
|
||||
@ -630,6 +681,10 @@ cmake --build build
|
||||
|
||||
Importantly, by default, the benchmark is built in Release mode.
|
||||
|
||||
The lines marked `assume nearest` time the same code compiled with
|
||||
`FASTFLOAT_ROUNDS_TO_NEAREST` (see [Assuming the default rounding
|
||||
mode](#assuming-the-default-rounding-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
|
||||
|
||||
@ -8,7 +8,7 @@ FetchContent_Declare(
|
||||
|
||||
FetchContent_MakeAvailable(counters)
|
||||
|
||||
add_executable(realbenchmark benchmark.cpp)
|
||||
add_executable(realbenchmark benchmark.cpp benchmark_rtn.cpp)
|
||||
target_link_libraries(realbenchmark PRIVATE counters::counters)
|
||||
add_executable(bench_ip bench_ip.cpp)
|
||||
add_executable(bench_uint16 bench_uint16.cpp)
|
||||
|
||||
@ -50,6 +50,12 @@ double findmax_fastfloat32(std::vector<std::basic_string<CharT>> &s) {
|
||||
return answer;
|
||||
}
|
||||
|
||||
// Defined in benchmark_rtn.cpp, with FASTFLOAT_ROUNDS_TO_NEAREST.
|
||||
template <typename CharT>
|
||||
double findmax_fastfloat64_rtn(std::vector<std::basic_string<CharT>> &s);
|
||||
template <typename CharT>
|
||||
double findmax_fastfloat32_rtn(std::vector<std::basic_string<CharT>> &s);
|
||||
|
||||
counters::event_collector collector{};
|
||||
|
||||
#ifdef USING_COUNTERS
|
||||
@ -193,8 +199,12 @@ void process(std::vector<std::string> &lines, size_t volume) {
|
||||
std::cout << "ASCII volume = " << volumeMB << " MB " << std::endl;
|
||||
pretty_print(volume, lines.size(), "fastfloat (64)",
|
||||
time_it_ns(lines, findmax_fastfloat64<char>, repeat));
|
||||
pretty_print(volume, lines.size(), "fastfloat (64, assume nearest)",
|
||||
time_it_ns(lines, findmax_fastfloat64_rtn<char>, repeat));
|
||||
pretty_print(volume, lines.size(), "fastfloat (32)",
|
||||
time_it_ns(lines, findmax_fastfloat32<char>, repeat));
|
||||
pretty_print(volume, lines.size(), "fastfloat (32, assume nearest)",
|
||||
time_it_ns(lines, findmax_fastfloat32_rtn<char>, repeat));
|
||||
|
||||
std::vector<std::u16string> lines16 = widen(lines);
|
||||
volume = 2 * volume;
|
||||
@ -202,8 +212,12 @@ void process(std::vector<std::string> &lines, size_t volume) {
|
||||
std::cout << "UTF-16 volume = " << volumeMB << " MB " << std::endl;
|
||||
pretty_print(volume, lines.size(), "fastfloat (64)",
|
||||
time_it_ns(lines16, findmax_fastfloat64<char16_t>, repeat));
|
||||
pretty_print(volume, lines.size(), "fastfloat (64, assume nearest)",
|
||||
time_it_ns(lines16, findmax_fastfloat64_rtn<char16_t>, repeat));
|
||||
pretty_print(volume, lines.size(), "fastfloat (32)",
|
||||
time_it_ns(lines16, findmax_fastfloat32<char16_t>, repeat));
|
||||
pretty_print(volume, lines.size(), "fastfloat (32, assume nearest)",
|
||||
time_it_ns(lines16, findmax_fastfloat32_rtn<char16_t>, repeat));
|
||||
}
|
||||
|
||||
void fileload(std::string filename) {
|
||||
|
||||
49
benchmarks/benchmark_rtn.cpp
Normal file
49
benchmarks/benchmark_rtn.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
// The same parsing loops as findmax_fastfloat64 and findmax_fastfloat32 in
|
||||
// benchmark.cpp, but compiled with FASTFLOAT_ROUNDS_TO_NEAREST: fast_float
|
||||
// assumes the default rounding mode and skips its check before the fast path.
|
||||
//
|
||||
// We rename the fast_float namespace in this file. Otherwise, the two
|
||||
// configurations would define the same inline functions differently, and the
|
||||
// linker could keep either one (an ODR violation).
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define FASTFLOAT_ROUNDS_TO_NEAREST
|
||||
#define fast_float fast_float_rtn
|
||||
#include "fast_float/fast_float.h"
|
||||
|
||||
template <typename CharT>
|
||||
double findmax_fastfloat64_rtn(std::vector<std::basic_string<CharT>> &s) {
|
||||
double answer = 0;
|
||||
double x = 0;
|
||||
for (auto &st : s) {
|
||||
auto [p, ec] = fast_float::from_chars(st.data(), st.data() + st.size(), x);
|
||||
if (p == st.data()) {
|
||||
throw std::runtime_error("bug in findmax_fastfloat_rtn");
|
||||
}
|
||||
answer = answer > x ? answer : x;
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
|
||||
template <typename CharT>
|
||||
double findmax_fastfloat32_rtn(std::vector<std::basic_string<CharT>> &s) {
|
||||
float answer = 0;
|
||||
float x = 0;
|
||||
for (auto &st : s) {
|
||||
auto [p, ec] = fast_float::from_chars(st.data(), st.data() + st.size(), x);
|
||||
if (p == st.data()) {
|
||||
throw std::runtime_error("bug in findmax_fastfloat_rtn");
|
||||
}
|
||||
answer = answer > x ? answer : x;
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
|
||||
template double findmax_fastfloat64_rtn<char>(std::vector<std::string> &);
|
||||
template double findmax_fastfloat32_rtn<char>(std::vector<std::string> &);
|
||||
template double
|
||||
findmax_fastfloat64_rtn<char16_t>(std::vector<std::u16string> &);
|
||||
template double
|
||||
findmax_fastfloat32_rtn<char16_t>(std::vector<std::u16string> &);
|
||||
@ -1,38 +1,205 @@
|
||||
#include "fast_float/fast_float.h"
|
||||
#include <fuzzer/FuzzedDataProvider.h>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__has_include)
|
||||
#if __has_include(<charconv>) && __cplusplus >= 201703L
|
||||
#include <charconv>
|
||||
#define FASTFLOAT_FUZZ_CHARCONV 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static void fail(char const *check) {
|
||||
fprintf(stderr, "fast_float fuzz failure: %s\n", check);
|
||||
abort();
|
||||
}
|
||||
|
||||
fast_float::chars_format arbitrary_format(FuzzedDataProvider &fdp) {
|
||||
using fast_float::chars_format;
|
||||
switch (fdp.ConsumeIntegralInRange<int>(0, 3)) {
|
||||
chars_format format;
|
||||
switch (fdp.ConsumeIntegralInRange<int>(0, 7)) {
|
||||
case 0:
|
||||
return chars_format::scientific;
|
||||
format = chars_format::scientific;
|
||||
break;
|
||||
case 1:
|
||||
return chars_format::fixed;
|
||||
format = chars_format::fixed;
|
||||
break;
|
||||
case 2:
|
||||
return chars_format::fixed;
|
||||
format = chars_format::json;
|
||||
break;
|
||||
case 3:
|
||||
format = chars_format::json_or_infnan;
|
||||
break;
|
||||
case 4:
|
||||
format = chars_format::fortran;
|
||||
break;
|
||||
case 5:
|
||||
format = chars_format::javascript;
|
||||
break;
|
||||
case 6:
|
||||
format = chars_format::javascript_sloppy;
|
||||
break;
|
||||
default:
|
||||
format = chars_format::general;
|
||||
break;
|
||||
}
|
||||
return chars_format::general;
|
||||
if (fdp.ConsumeBool()) {
|
||||
format = format | chars_format::allow_leading_plus;
|
||||
}
|
||||
if (fdp.ConsumeBool()) {
|
||||
format = format | chars_format::skip_white_space;
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
template <typename T> uint64_t bits_of(T value) {
|
||||
uint64_t bits = 0;
|
||||
memcpy(&bits, &value, sizeof(T));
|
||||
return bits;
|
||||
}
|
||||
|
||||
// The same ASCII text must parse identically for every character type: same
|
||||
// error code, same number of consumed code units, bit-identical value.
|
||||
template <typename UC>
|
||||
void check_widened(std::string const &input,
|
||||
fast_float::from_chars_result_t<char> expected,
|
||||
double expected_value, fast_float::chars_format format) {
|
||||
std::vector<UC> wide;
|
||||
wide.reserve(input.size());
|
||||
for (char c : input) {
|
||||
wide.push_back(UC(static_cast<unsigned char>(c)));
|
||||
}
|
||||
double value = 0.0;
|
||||
auto result = fast_float::from_chars(wide.data(), wide.data() + wide.size(),
|
||||
value, format);
|
||||
if (result.ec != expected.ec ||
|
||||
(result.ptr - wide.data()) != (expected.ptr - input.data())) {
|
||||
fail("wide result disagrees with char result");
|
||||
}
|
||||
if (expected.ec == std::errc() && bits_of(value) != bits_of(expected_value)) {
|
||||
fail("wide value disagrees with char value");
|
||||
}
|
||||
}
|
||||
|
||||
// Every number format accepted by the library is pure ASCII, so a successful
|
||||
// parse must never consume a code unit >= 0x80. A violation means some helper
|
||||
// inspected only the low byte of a wide code unit (the bug behind #391).
|
||||
// The generated sequences keep an ASCII low byte but take hostile high bits:
|
||||
// top-bit-set units (near the minimum of a signed wchar_t, the shape behind
|
||||
// #422), sign-extended negative units (the shape behind #395), and non-ASCII
|
||||
// code points whose low byte is an ASCII digit (the shape behind #391).
|
||||
template <typename UC>
|
||||
void check_arbitrary_units(FuzzedDataProvider &fdp,
|
||||
fast_float::chars_format format) {
|
||||
using unsigned_uc = typename std::make_unsigned<UC>::type;
|
||||
size_t count = fdp.ConsumeIntegralInRange<size_t>(0, 64);
|
||||
std::vector<UC> units;
|
||||
units.reserve(count);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
unsigned_uc unit = fdp.ConsumeIntegral<uint8_t>();
|
||||
switch (fdp.ConsumeIntegralInRange<int>(0, 4)) {
|
||||
case 0: // top bit set: near the minimum value when UC is signed
|
||||
unit = unsigned_uc(unit | (unsigned_uc(1) << (sizeof(UC) * 8 - 1)));
|
||||
break;
|
||||
case 1: // all high bits set: a small negative value when UC is signed
|
||||
unit = unsigned_uc(unit | ~unsigned_uc(0xFF));
|
||||
break;
|
||||
case 2: // arbitrary high byte: a non-ASCII code point, ASCII low byte
|
||||
unit = unsigned_uc(
|
||||
unit | unsigned_uc(unsigned_uc(fdp.ConsumeIntegral<uint8_t>()) << 8));
|
||||
break;
|
||||
default: // plain ASCII unit
|
||||
break;
|
||||
}
|
||||
units.push_back(UC(unit));
|
||||
}
|
||||
if (units.empty()) {
|
||||
return;
|
||||
}
|
||||
UC const *begin = units.data();
|
||||
UC const *end = begin + units.size();
|
||||
|
||||
double as_double = 0.0;
|
||||
auto result = fast_float::from_chars(begin, end, as_double, format);
|
||||
if (result.ec == std::errc()) {
|
||||
for (UC const *p = begin; p != result.ptr; ++p) {
|
||||
if (static_cast<unsigned_uc>(*p) >= 0x80) {
|
||||
fail("float parse consumed a non-ASCII code unit");
|
||||
}
|
||||
}
|
||||
}
|
||||
uint16_t as_uint16 = 0;
|
||||
auto int_result = fast_float::from_chars(begin, end, as_uint16);
|
||||
if (int_result.ec == std::errc()) {
|
||||
for (UC const *p = begin; p != int_result.ptr; ++p) {
|
||||
if (static_cast<unsigned_uc>(*p) >= 0x80) {
|
||||
fail("integer parse consumed a non-ASCII code unit");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fast_float's integer parsing is specified to match std::from_chars.
|
||||
template <typename T> void check_int(std::string const &input, int base) {
|
||||
T value{};
|
||||
auto result = fast_float::from_chars(
|
||||
input.data(), input.data() + input.size(), value, base);
|
||||
#ifdef FASTFLOAT_FUZZ_CHARCONV
|
||||
T expected{};
|
||||
auto std_result = std::from_chars(input.data(), input.data() + input.size(),
|
||||
expected, base);
|
||||
if (result.ec != std_result.ec || result.ptr != std_result.ptr) {
|
||||
fail("integer result disagrees with std::from_chars");
|
||||
}
|
||||
if (result.ec == std::errc() && value != expected) {
|
||||
fail("integer value disagrees with std::from_chars");
|
||||
}
|
||||
#else
|
||||
static_cast<void>(result);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void check_int_all_bases(std::string const &input, int base) {
|
||||
check_int<T>(input, 10);
|
||||
check_int<T>(input, base);
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(uint8_t const *data, size_t size) {
|
||||
FuzzedDataProvider fdp(data, size);
|
||||
fast_float::chars_format format = arbitrary_format(fdp);
|
||||
double result_d = 0.0;
|
||||
int base = fdp.ConsumeIntegralInRange<int>(2, 36);
|
||||
|
||||
std::string input_d = fdp.ConsumeRandomLengthString(128);
|
||||
auto answer = fast_float::from_chars(
|
||||
double result_d = 0.0;
|
||||
auto answer_d = fast_float::from_chars(
|
||||
input_d.data(), input_d.data() + input_d.size(), result_d, format);
|
||||
std::string input_f = fdp.ConsumeRandomLengthString(128);
|
||||
float result_f = 0.0;
|
||||
answer = fast_float::from_chars(
|
||||
input_f.data(), input_f.data() + input_f.size(), result_f, format);
|
||||
int result_i = 0;
|
||||
fast_float::from_chars(input_d.data(), input_d.data() + input_d.size(),
|
||||
result_f, format);
|
||||
check_widened<wchar_t>(input_d, answer_d, result_d, format);
|
||||
check_widened<char16_t>(input_d, answer_d, result_d, format);
|
||||
check_widened<char32_t>(input_d, answer_d, result_d, format);
|
||||
|
||||
std::string input_i = fdp.ConsumeRandomLengthString(128);
|
||||
answer = fast_float::from_chars(input_i.data(),
|
||||
input_i.data() + input_i.size(), result_i);
|
||||
check_int_all_bases<uint8_t>(input_i, base);
|
||||
check_int_all_bases<int8_t>(input_i, base);
|
||||
check_int_all_bases<uint16_t>(input_i, base);
|
||||
check_int_all_bases<int16_t>(input_i, base);
|
||||
check_int_all_bases<uint32_t>(input_i, base);
|
||||
check_int_all_bases<int32_t>(input_i, base);
|
||||
check_int_all_bases<uint64_t>(input_i, base);
|
||||
check_int_all_bases<int64_t>(input_i, base);
|
||||
|
||||
check_arbitrary_units<wchar_t>(fdp, format);
|
||||
check_arbitrary_units<char16_t>(fdp, format);
|
||||
check_arbitrary_units<char32_t>(fdp, format);
|
||||
return 0;
|
||||
}
|
||||
@ -622,10 +622,22 @@ parse_number_string(UC const *p, UC const *pend, parse_options_t<UC> options,
|
||||
// JSON and JavaScript are mutually exclusive, so only the non-JSON
|
||||
// instantiation has to look at the flag.
|
||||
FASTFLOAT_IF_CONSTEXPR17(!basic_json_fmt) {
|
||||
// We have to disable -Wc++20-extensions for the [[unlikely]] attribute
|
||||
// See comment for @jwakely at
|
||||
// https://github.com/fastfloat/fast_float/pull/387#discussion_r3366943539
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#if (!defined(__APPLE_CC__) && __clang_major__ >= 10) || (__clang_major__ >= 13)
|
||||
#pragma clang diagnostic ignored "-Wc++20-extensions"
|
||||
#endif
|
||||
#endif
|
||||
if fastfloat_unlikely (uint64_t(options.format &
|
||||
detail::basic_javascript_fmt)) {
|
||||
return parse_number_string_javascript<UC>(p, pend, options, store_spans);
|
||||
}
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
}
|
||||
return parse_number_string_impl<basic_json_fmt, false, UC>(p, pend, options,
|
||||
store_spans);
|
||||
|
||||
@ -61,9 +61,15 @@ namespace detail {
|
||||
* -ceil(p) + q
|
||||
* where
|
||||
* p = log(5**-q)/log(2) = -q * log(5)/log(2)
|
||||
*
|
||||
* The shift must floor towards negative infinity, as an arithmetic right
|
||||
* shift does, but right-shifting a negative value is implementation-defined
|
||||
* before C++20. Biasing the product by 2048 * 65536 keeps the shifted value
|
||||
* non-negative without changing the result: the bias is a multiple of 2^16
|
||||
* and is removed again after the shift.
|
||||
*/
|
||||
constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept {
|
||||
return (((152170 + 65536) * q) >> 16) + 63;
|
||||
return (((152170 + 65536) * q + (2048 * 65536)) >> 16) - 2048 + 63;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
|
||||
#define FASTFLOAT_VERSION_MAJOR 8
|
||||
#define FASTFLOAT_VERSION_MINOR 3
|
||||
#define FASTFLOAT_VERSION_PATCH 2
|
||||
#define FASTFLOAT_VERSION_PATCH 3
|
||||
|
||||
#define FASTFLOAT_STRINGIZE_IMPL(x) #x
|
||||
#define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x)
|
||||
|
||||
@ -74,12 +74,20 @@ from_chars_result_t<UC>
|
||||
* Returns true if the floating-pointing rounding mode is to 'nearest'.
|
||||
* It is the default on most system. This function is meant to be inexpensive.
|
||||
* Credit : @mwalcott3
|
||||
*
|
||||
* If FASTFLOAT_ROUNDS_TO_NEAREST is defined, we trust the user that the
|
||||
* rounding mode is always 'nearest' and we skip the check (see README).
|
||||
*/
|
||||
fastfloat_really_inline bool rounds_to_nearest() noexcept {
|
||||
// https://lemire.me/blog/2020/06/26/gcc-not-nearest/
|
||||
#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
|
||||
// The arithmetic may be carried out with excess precision (e.g., x87):
|
||||
// Clinger's fast path could then round twice. FASTFLOAT_ROUNDS_TO_NEAREST
|
||||
// does not change this.
|
||||
return false;
|
||||
#endif
|
||||
#elif defined(FASTFLOAT_ROUNDS_TO_NEAREST)
|
||||
return true;
|
||||
#else
|
||||
// See
|
||||
// A fast function to check your floating-point rounding mode
|
||||
// https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/
|
||||
@ -133,6 +141,7 @@ fastfloat_really_inline bool rounds_to_nearest() noexcept {
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
#endif // FASTFLOAT_ROUNDS_TO_NEAREST
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
@ -158,7 +167,10 @@ template <> struct from_chars_caller<std::float32_t> {
|
||||
// float32_t
|
||||
float val = 0.0f;
|
||||
auto ret = from_chars_advanced(first, last, val, options);
|
||||
// If no characters match the pattern, value must be left unmodified.
|
||||
if (ret.ec != std::errc::invalid_argument) {
|
||||
value = val;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
@ -175,7 +187,10 @@ template <> struct from_chars_caller<std::float64_t> {
|
||||
// float64_t
|
||||
double val = 0.0;
|
||||
auto ret = from_chars_advanced(first, last, val, options);
|
||||
// If no characters match the pattern, value must be left unmodified.
|
||||
if (ret.ec != std::errc::invalid_argument) {
|
||||
value = val;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
@ -385,9 +400,19 @@ from_chars_float_advanced(UC const *first, UC const *last, T &value,
|
||||
// Leave for the javascript conversion before anything else is live, so this
|
||||
// compiles to a tail call and the common path keeps its registers. The cold
|
||||
// function repeats the leading-whitespace and empty-input handling below.
|
||||
// See the comment on -Wc++20-extensions further down in this function.
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#if (!defined(__APPLE_CC__) && __clang_major__ >= 10) || (__clang_major__ >= 13)
|
||||
#pragma clang diagnostic ignored "-Wc++20-extensions"
|
||||
#endif
|
||||
#endif
|
||||
if fastfloat_unlikely (uint64_t(fmt & detail::basic_javascript_fmt)) {
|
||||
return from_chars_float_javascript<T, UC>(first, last, value, options);
|
||||
}
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
from_chars_result_t<UC> answer;
|
||||
if (uint64_t(fmt & chars_format::skip_white_space)) {
|
||||
@ -529,38 +554,42 @@ integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept {
|
||||
// the following overloads are here to avoid surprising ambiguity for int,
|
||||
// unsigned, etc.
|
||||
template <typename T, typename Int>
|
||||
FASTFLOAT_CONSTEXPR20
|
||||
typename std::enable_if<is_supported_float_type<T>::value &&
|
||||
std::is_integral<Int>::value &&
|
||||
!std::is_signed<Int>::value,
|
||||
FASTFLOAT_CONSTEXPR20 typename std::enable_if<
|
||||
is_supported_float_type<T>::value && std::is_integral<Int>::value &&
|
||||
sizeof(Int) <= sizeof(uint64_t) && !std::is_signed<Int>::value,
|
||||
T>::type
|
||||
integer_times_pow10(Int mantissa, int decimal_exponent) noexcept {
|
||||
integer_times_pow10(Int mantissa, int decimal_exponent) noexcept {
|
||||
return integer_times_pow10<T>(static_cast<uint64_t>(mantissa),
|
||||
decimal_exponent);
|
||||
}
|
||||
|
||||
template <typename T, typename Int>
|
||||
FASTFLOAT_CONSTEXPR20
|
||||
typename std::enable_if<is_supported_float_type<T>::value &&
|
||||
std::is_integral<Int>::value &&
|
||||
std::is_signed<Int>::value,
|
||||
FASTFLOAT_CONSTEXPR20 typename std::enable_if<
|
||||
is_supported_float_type<T>::value && std::is_integral<Int>::value &&
|
||||
sizeof(Int) <= sizeof(int64_t) && std::is_signed<Int>::value,
|
||||
T>::type
|
||||
integer_times_pow10(Int mantissa, int decimal_exponent) noexcept {
|
||||
integer_times_pow10(Int mantissa, int decimal_exponent) noexcept {
|
||||
return integer_times_pow10<T>(static_cast<int64_t>(mantissa),
|
||||
decimal_exponent);
|
||||
}
|
||||
|
||||
template <typename Int>
|
||||
FASTFLOAT_CONSTEXPR20 typename std::enable_if<
|
||||
std::is_integral<Int>::value && !std::is_signed<Int>::value, double>::type
|
||||
integer_times_pow10(Int mantissa, int decimal_exponent) noexcept {
|
||||
FASTFLOAT_CONSTEXPR20
|
||||
typename std::enable_if<std::is_integral<Int>::value &&
|
||||
sizeof(Int) <= sizeof(uint64_t) &&
|
||||
!std::is_signed<Int>::value,
|
||||
double>::type
|
||||
integer_times_pow10(Int mantissa, int decimal_exponent) noexcept {
|
||||
return integer_times_pow10(static_cast<uint64_t>(mantissa), decimal_exponent);
|
||||
}
|
||||
|
||||
template <typename Int>
|
||||
FASTFLOAT_CONSTEXPR20 typename std::enable_if<
|
||||
std::is_integral<Int>::value && std::is_signed<Int>::value, double>::type
|
||||
integer_times_pow10(Int mantissa, int decimal_exponent) noexcept {
|
||||
FASTFLOAT_CONSTEXPR20
|
||||
typename std::enable_if<std::is_integral<Int>::value &&
|
||||
sizeof(Int) <= sizeof(int64_t) &&
|
||||
std::is_signed<Int>::value,
|
||||
double>::type
|
||||
integer_times_pow10(Int mantissa, int decimal_exponent) noexcept {
|
||||
return integer_times_pow10(static_cast<int64_t>(mantissa), decimal_exponent);
|
||||
}
|
||||
|
||||
|
||||
@ -67,6 +67,7 @@ fast_float_add_cpp_test(supported_chars_test)
|
||||
fast_float_add_cpp_test(example_test)
|
||||
fast_float_add_cpp_test(example_comma_test)
|
||||
fast_float_add_cpp_test(example_integer_times_pow10)
|
||||
fast_float_add_cpp_test(rounds_to_nearest_test)
|
||||
fast_float_add_cpp_test(basictest)
|
||||
option(FASTFLOAT_CONSTEXPR_TESTS "Require constexpr tests (build will fail if the compiler won't support it)" OFF)
|
||||
if (FASTFLOAT_CONSTEXPR_TESTS)
|
||||
|
||||
@ -2283,6 +2283,38 @@ void verify_integer_times_pow10(Int mantissa, int decimal_exponent) {
|
||||
}
|
||||
} // namespace all_supported_types
|
||||
|
||||
#if defined(__SIZEOF_INT128__)
|
||||
template <typename Int, typename = void>
|
||||
struct has_integer_times_pow10 : std::false_type {};
|
||||
|
||||
template <typename Int>
|
||||
struct has_integer_times_pow10<
|
||||
Int, std::void_t<decltype(fast_float::integer_times_pow10(Int{}, 0))>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename Float, typename Int, typename = void>
|
||||
struct has_typed_integer_times_pow10 : std::false_type {};
|
||||
|
||||
template <typename Float, typename Int>
|
||||
struct has_typed_integer_times_pow10<
|
||||
Float, Int,
|
||||
std::void_t<decltype(fast_float::integer_times_pow10<Float>(Int{}, 0))>>
|
||||
: std::true_type {};
|
||||
|
||||
TEST_CASE("integer_times_pow10 rejects integers wider than 64 bits") {
|
||||
CHECK(has_integer_times_pow10<int64_t>::value);
|
||||
CHECK(has_integer_times_pow10<uint64_t>::value);
|
||||
CHECK(has_typed_integer_times_pow10<float, int64_t>::value);
|
||||
CHECK(has_typed_integer_times_pow10<double, uint64_t>::value);
|
||||
CHECK_FALSE(has_integer_times_pow10<__int128_t>::value);
|
||||
CHECK_FALSE(has_integer_times_pow10<__uint128_t>::value);
|
||||
CHECK_FALSE(has_typed_integer_times_pow10<float, __int128_t>::value);
|
||||
CHECK_FALSE(has_typed_integer_times_pow10<float, __uint128_t>::value);
|
||||
CHECK_FALSE(has_typed_integer_times_pow10<double, __int128_t>::value);
|
||||
CHECK_FALSE(has_typed_integer_times_pow10<double, __uint128_t>::value);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("integer_times_pow10") {
|
||||
/* explicitly verifying API with different types of integers */
|
||||
// double (the "default" overload)
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#include "fast_float/fast_float.h"
|
||||
#include <cstdint>
|
||||
|
||||
@ -9,6 +11,39 @@
|
||||
#include <stdfloat>
|
||||
#endif
|
||||
|
||||
// If no characters match the pattern, value must be left unmodified
|
||||
// (see https://github.com/fastfloat/fast_float/issues/424).
|
||||
template <typename T> bool check_invalid_input_preserves_value() {
|
||||
std::vector<std::string_view> const invalid_inputs{"x", "", "-", ".",
|
||||
"e1", "-x", "na", "+"};
|
||||
for (auto const &f : invalid_inputs) {
|
||||
T result = T(42);
|
||||
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
|
||||
if (answer.ec != std::errc::invalid_argument || answer.ptr != f.data()) {
|
||||
std::cerr << "Expected invalid_argument for input: \"" << f << "\""
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
if (result != T(42)) {
|
||||
std::cerr << "Value modified on invalid input: \"" << f << "\" got "
|
||||
<< result << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Out-of-range input must still store the documented value.
|
||||
std::string_view const too_large = "1e999";
|
||||
T result = T(42);
|
||||
auto answer = fast_float::from_chars(
|
||||
too_large.data(), too_large.data() + too_large.size(), result);
|
||||
if (answer.ec != std::errc::result_out_of_range ||
|
||||
result != std::numeric_limits<T>::infinity()) {
|
||||
std::cerr << "Expected infinity and result_out_of_range for \"1e999\""
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main() {
|
||||
// Write some testcases for the parsing of floating point numbers in the
|
||||
// float32_t type. We use the from_chars function defined in this library.
|
||||
@ -33,6 +68,9 @@ int main() {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
if (!check_invalid_input_preserves_value<std::float32_t>()) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
#else
|
||||
std::cout << "No std::float32_t type available." << std::endl;
|
||||
#endif
|
||||
@ -60,6 +98,9 @@ int main() {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
if (!check_invalid_input_preserves_value<std::float64_t>()) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
#else
|
||||
std::cout << "No std::float64_t type available." << std::endl;
|
||||
#endif
|
||||
|
||||
107
tests/rounds_to_nearest_test.cpp
Normal file
107
tests/rounds_to_nearest_test.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
// Checks that defining FASTFLOAT_ROUNDS_TO_NEAREST does not change the results
|
||||
// when the rounding mode is the default (round to nearest).
|
||||
#define FASTFLOAT_ROUNDS_TO_NEAREST
|
||||
#include "fast_float/fast_float.h"
|
||||
|
||||
#include <cfloat>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
|
||||
namespace {
|
||||
|
||||
bool check_double(char const *s) {
|
||||
double const expected = std::strtod(s, nullptr);
|
||||
double result = 0;
|
||||
auto const answer = fast_float::from_chars(s, s + std::strlen(s), result);
|
||||
if (answer.ec != std::errc() ||
|
||||
std::memcmp(&result, &expected, sizeof(double)) != 0) {
|
||||
std::cerr << "mismatch on " << s << "\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_float(char const *s) {
|
||||
float const expected = std::strtof(s, nullptr);
|
||||
float result = 0;
|
||||
auto const answer = fast_float::from_chars(s, s + std::strlen(s), result);
|
||||
if (answer.ec != std::errc() ||
|
||||
std::memcmp(&result, &expected, sizeof(float)) != 0) {
|
||||
std::cerr << "mismatch on " << s << " (float)\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
#if (FLT_EVAL_METHOD == 0) || (FLT_EVAL_METHOD == 1)
|
||||
if (!fast_float::detail::rounds_to_nearest()) {
|
||||
std::cerr << "FASTFLOAT_ROUNDS_TO_NEAREST is ignored\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
#endif
|
||||
char const *cases[] = {"0",
|
||||
"-0",
|
||||
"1",
|
||||
"-1",
|
||||
"0.1",
|
||||
"-0.1",
|
||||
"1e10",
|
||||
"-1e10",
|
||||
"1e-10",
|
||||
"16777216",
|
||||
"16777217",
|
||||
"9007199254740992",
|
||||
"9007199254740993",
|
||||
"123456.789",
|
||||
"-1234.56",
|
||||
"1e23"};
|
||||
for (char const *s : cases) {
|
||||
if (!check_double(s) || !check_float(s)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
char const *double_cases[] = {"1e22", "-1e22", "1e-22",
|
||||
"2.2250738585072014e-308",
|
||||
"1.7976931348623157e308"};
|
||||
for (char const *s : double_cases) {
|
||||
if (!check_double(s)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
std::mt19937_64 gen(1234);
|
||||
std::uniform_real_distribution<double> prices(-1e6, 1e6);
|
||||
std::uniform_int_distribution<uint64_t> bits;
|
||||
char buffer[64];
|
||||
for (size_t i = 0; i < 100000; i++) {
|
||||
// short decimals: they take Clinger's fast path
|
||||
std::snprintf(buffer, sizeof(buffer), "%.2f", prices(gen));
|
||||
if (!check_double(buffer) || !check_float(buffer)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
// arbitrary doubles: most take the other paths
|
||||
uint64_t const word = bits(gen);
|
||||
double d;
|
||||
std::memcpy(&d, &word, sizeof(d));
|
||||
if (d != d || d - d != 0) { // NaN or infinity
|
||||
continue;
|
||||
}
|
||||
std::snprintf(buffer, sizeof(buffer), "%.17g", d);
|
||||
if (!check_double(buffer)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
if (fast_float::integer_times_pow10(uint64_t(123456), -2) != 1234.56 ||
|
||||
fast_float::integer_times_pow10(int64_t(-123456), -2) != -1234.56) {
|
||||
std::cerr << "integer_times_pow10 mismatch\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
std::cout << "ok\n";
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user