mirror of
https://github.com/fastfloat/fast_float.git
synced 2026-11-10 07:09:53 +08:00
Merge pull request #432 from fastfloat/rounds-to-nearest-macro
Add FASTFLOAT_ROUNDS_TO_NEAREST to skip the rounding-mode check
This commit is contained in:
commit
0b21c2eadb
55
README.md
55
README.md
@ -155,8 +155,9 @@ Furthermore, we have the following restrictions:
|
|||||||
We support Visual Studio, macOS, Linux, freeBSD. We support big and little
|
We support Visual Studio, macOS, Linux, freeBSD. We support big and little
|
||||||
endian. We support 32-bit and 64-bit systems.
|
endian. We support 32-bit and 64-bit systems.
|
||||||
|
|
||||||
We assume that the rounding mode is set to nearest (`std::fegetround() ==
|
The results are rounded to the nearest floating-point value, whatever the
|
||||||
FE_TONEAREST`).
|
rounding mode of the thread (`std::fegetround()`). See
|
||||||
|
[Assuming the default rounding mode](#assuming-the-default-rounding-mode).
|
||||||
|
|
||||||
## Integer types
|
## Integer types
|
||||||
|
|
||||||
@ -485,6 +486,56 @@ Overloads of `fast_float::integer_times_pow10()` are provided for
|
|||||||
signed and unsigned integer types up to 64 bits: `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
|
## Users and Related Work
|
||||||
|
|
||||||
The fast_float library is part of:
|
The fast_float library is part of:
|
||||||
|
|||||||
@ -74,12 +74,20 @@ from_chars_result_t<UC>
|
|||||||
* Returns true if the floating-pointing rounding mode is to 'nearest'.
|
* 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.
|
* It is the default on most system. This function is meant to be inexpensive.
|
||||||
* Credit : @mwalcott3
|
* 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 {
|
fastfloat_really_inline bool rounds_to_nearest() noexcept {
|
||||||
// https://lemire.me/blog/2020/06/26/gcc-not-nearest/
|
// https://lemire.me/blog/2020/06/26/gcc-not-nearest/
|
||||||
#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
|
#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;
|
return false;
|
||||||
#endif
|
#elif defined(FASTFLOAT_ROUNDS_TO_NEAREST)
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
// See
|
// See
|
||||||
// A fast function to check your floating-point rounding mode
|
// 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/
|
// 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__)
|
#elif defined(__GNUC__)
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
#endif
|
#endif
|
||||||
|
#endif // FASTFLOAT_ROUNDS_TO_NEAREST
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
@ -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_test)
|
||||||
fast_float_add_cpp_test(example_comma_test)
|
fast_float_add_cpp_test(example_comma_test)
|
||||||
fast_float_add_cpp_test(example_integer_times_pow10)
|
fast_float_add_cpp_test(example_integer_times_pow10)
|
||||||
|
fast_float_add_cpp_test(rounds_to_nearest_test)
|
||||||
fast_float_add_cpp_test(basictest)
|
fast_float_add_cpp_test(basictest)
|
||||||
option(FASTFLOAT_CONSTEXPR_TESTS "Require constexpr tests (build will fail if the compiler won't support it)" OFF)
|
option(FASTFLOAT_CONSTEXPR_TESTS "Require constexpr tests (build will fail if the compiler won't support it)" OFF)
|
||||||
if (FASTFLOAT_CONSTEXPR_TESTS)
|
if (FASTFLOAT_CONSTEXPR_TESTS)
|
||||||
|
|||||||
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