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
|
||||
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
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
## 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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
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