Compare commits

...

16 Commits
v8.3.2 ... main

Author SHA1 Message Date
Daniel Lemire
8ec59686a1
Merge pull request #433 from fastfloat/benchmark-rounds-to-nearest
Benchmark fast_float with and without FASTFLOAT_ROUNDS_TO_NEAREST
2026-11-09 12:35:25 -05:00
Daniel Lemire
9ed4b90638 Benchmark fast_float with and without FASTFLOAT_ROUNDS_TO_NEAREST
realbenchmark now also times the parsing loops compiled with
FASTFLOAT_ROUNDS_TO_NEAREST (benchmark_rtn.cpp). That file renames the
fast_float namespace so that both configurations coexist in one binary
without an ODR violation.
2026-11-09 12:06:33 -05:00
Daniel Lemire
0b21c2eadb
Merge pull request #432 from fastfloat/rounds-to-nearest-macro
Add FASTFLOAT_ROUNDS_TO_NEAREST to skip the rounding-mode check
2026-11-09 11:58:27 -05:00
Daniel Lemire
8cd1810adb Add FASTFLOAT_ROUNDS_TO_NEAREST to skip the rounding-mode check
When FASTFLOAT_ROUNDS_TO_NEAREST is defined, fast_float assumes that the
rounding mode is the default (round to nearest) and skips the volatile
probe before Clinger's fast path. The FLT_EVAL_METHOD check is kept.
The README documents the tradeoff.
2026-11-09 11:55:36 -05:00
github-actions[bot]
dbeabeeb3e Version 8.3.3 2026-10-31 13:45:33 +00:00
Daniel Lemire
9054aa02be
Merge pull request #430 from CODER-7777/fix-427-unlikely-cxx20-ext-warning
Silence -Wc++20-extensions at the remaining [[unlikely]] sites
2026-10-31 09:42:54 -04:00
Daniel Lemire
33785d5d61
Merge pull request #431 from aikhanjum/codex/fastfloat-correctness
Reject integer_times_pow10 arguments wider than 64 bits
2026-10-29 13:54:48 -04:00
Aikhan Jumashukurov
4b37bda237 Reject integer_times_pow10 arguments wider than 64 bits 2026-10-29 13:00:34 -04:00
CODER-7777
78b6a96f28 Silence -Wc++20-extensions at the remaining [[unlikely]] sites (fixes #427)
The javascript early-exit checks added in b2e41e8 use fastfloat_unlikely
without the point-of-use clang diagnostic guard that the other call sites
have (see #387), so clang with -pedantic in C++11/14/17 mode warns
"use of the 'unlikely' attribute is a C++20 extension".

Wrap both sites with the same guarded pragma push/pop already used in
parse_number.h and decimal_to_binary.h. No change to code generation.

Add a CI step compiling fast_float.h with clang-14 and
-pedantic-errors -Werror in C++11/14/17/20 to catch regressions.
2026-10-29 05:53:02 +05:30
Daniel Lemire
0de7a21da5 Run clang-format 2026-10-17 08:55:50 -04:00
Daniel Lemire
638a8acd6d
Merge pull request #426 from sahvx655-wq/power-negative-shift
Avoid right-shifting a negative value in detail::power()
2026-10-17 08:54:58 -04:00
Sahana Bogar
90513adca1 Avoid right-shifting a negative value in detail::power() 2026-10-15 11:53:39 +05:30
Daniel Lemire
166d2b3df2
Merge pull request #425 from fastfloat/fix-424-fixed-width-invalid
Leave std::float32_t/std::float64_t unmodified on invalid input (fixes #424)
2026-10-12 10:11:38 -04:00
Daniel Lemire
9cd6dfc8e3 Leave std::float32_t/std::float64_t unmodified on invalid input (fixes #424)
The fixed-width wrappers parsed into a zero-initialized temporary and
copied it back unconditionally, so invalid input overwrote the
destination with zero. Copy back only when the result is not
invalid_argument, as required by [charconv.from.chars]/1.
2026-10-12 10:10:54 -04:00
Daniel Lemire
033f64f608
Merge pull request #423 from sahvx655-wq/fuzz-coverage
extend fuzzer to wide char types, integer parsing and all formats
2026-10-11 17:35:37 -04:00
Sahana Bogar
e7e0cfcd4f extend fuzzer to wide char types, integer parsing and all formats 2026-10-12 01:55:39 +05:30
16 changed files with 568 additions and 47 deletions

View File

@ -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 &&

View File

@ -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)

View File

@ -2,7 +2,7 @@
module(
name = "fast_float",
version = "8.3.2",
version = "8.3.3",
compatibility_level = 8,
)

View File

@ -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

View File

@ -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)

View File

@ -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) {

View 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> &);

View File

@ -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;
}

View File

@ -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);

View File

@ -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

View File

@ -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)

View File

@ -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);
}

View File

@ -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)

View File

@ -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)

View File

@ -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

View 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;
}