From 1afba556e344360c403ea060b041fa27032fa4b4 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Tue, 17 Nov 2020 21:55:01 -0500 Subject: [PATCH 1/7] Extending the fast path. --- include/fast_float/decimal_to_binary.h | 43 +++++++++++- tests/CMakeLists.txt | 1 + tests/powersoffive_hardround.cpp | 94 ++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 tests/powersoffive_hardround.cpp diff --git a/include/fast_float/decimal_to_binary.h b/include/fast_float/decimal_to_binary.h index 68ab079..0b2741e 100644 --- a/include/fast_float/decimal_to_binary.h +++ b/include/fast_float/decimal_to_binary.h @@ -60,8 +60,40 @@ namespace { fastfloat_really_inline int power(int q) noexcept { return (((152170 + 65536) * q) >> 16) + 63; } + // Checks whether w is divisible by 5**-q. If it returns true, then + // w is definitively divisible by 5**-q. + inline bool is_divisible(int64_t q, uint64_t w) noexcept { + if((q>=-18) || (q<-27)) { return false; } + int64_t pos_q = -q; + // For each pair, first entry is the multiplicative inverse of 5**-q + // and the second one is the largest quotient. + // + // This could be more efficient by using... + // Faster remainder by direct computation: Applications to compilers and software libraries + // Software: Practice and Experience 49 (6), 2019. + // but the following is simple enough. + constexpr static uint64_t table[10][2] = { + {0xc1773b91fac10669,0x49c977}, // inverse of 5**18 + {0x26b172506559ce15,0xec1e4}, // inverse of 5**19 + {0xd489e3a9addec2d1,0x2f394}, // inverse of 5**20 + {0x90e860bb892c8d5d,0x971d}, // inverse of 5**21 + {0x502e79bf1b6f4f79,0x1e39}, // inverse of 5**22 + {0xdcd618596be30fe5,0x60b}, // inverse of 5**23 + {0x2c2ad1ab7bfa3661,0x135}, // inverse of 5**24 + {0x8d55d224bfed7ad,0x3d}, // inverse of 5**25 + {0x1c445d3a8cc9189,0xc}, // inverse of 5**26 + {0xcd27412a54f5b6b5,0x2}, // inverse of 5**27 + }; + uint64_t inverse = table[pos_q-18][0]; + uint64_t threshold = table[pos_q-18][1]; + uint64_t product = w * inverse; + if(product > threshold) { return false; } + return true; + } } // namespace + + // w * 10 ** q // The returned value should be a valid ieee64 number that simply need to be packed. // However, in some very rare cases, the computation will fail. In such cases, we @@ -93,12 +125,19 @@ adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { // 1. We need the implicit bit // 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) + value128 product = compute_product_approximation(q, w); if(product.low == 0xFFFFFFFFFFFFFFFF) { // could guard it further // In some very rare cases, this could happen, in which case we might need a more accurate // computation that what we can provide cheaply. This is very, very unlikely. - answer.power2 = -1; // This (a negative value) indicates an error condition. - return answer; + // + // There is still a chance to recover. If w is divisible by 5**-q, + if(!is_divisible(q,w)) { + answer.power2 = -1; // This (a negative value) indicates an error condition. + return answer; + } + product.low += 1; + product.high += 1; } // The "compute_product_approximation" function can be slightly slower than a branchless approach: // value128 product = compute_product(q, w); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0b32b52..74cfbad 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,6 +8,7 @@ function(fast_float_add_cpp_test TEST_NAME) endif() target_link_libraries(${TEST_NAME} PUBLIC fast_float) endfunction(fast_float_add_cpp_test) +fast_float_add_cpp_test(powersoffive_hardround) fast_float_add_cpp_test(short_random_string) fast_float_add_cpp_test(exhaustive32_midpoint) fast_float_add_cpp_test(random_string) diff --git a/tests/powersoffive_hardround.cpp b/tests/powersoffive_hardround.cpp new file mode 100644 index 0000000..eea82bf --- /dev/null +++ b/tests/powersoffive_hardround.cpp @@ -0,0 +1,94 @@ +#include "fast_float/fast_float.h" + +#include +#include +#include +#include + +std::pair strtod_from_string(const char *st) { + double d; + char *pr; +#ifdef _WIN32 + static _locale_t c_locale = _create_locale(LC_ALL, "C"); + d = _strtod_l(st, &pr, c_locale); +#else + static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL); + d = strtod_l(st, &pr, c_locale); +#endif + if (st == pr) { + std::cerr << "strtod_l could not parse '" << st << std::endl; + return std::make_pair(0, false); + } + return std::make_pair(d, true); +} + +std::pair strtof_from_string(char *st) { + float d; + char *pr; +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) + d = cygwin_strtod_l(st, &pr); +#elif defined(_WIN32) + static _locale_t c_locale = _create_locale(LC_ALL, "C"); + d = _strtof_l(st, &pr, c_locale); +#else + static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL); + d = strtof_l(st, &pr, c_locale); +#endif + if (st == pr) { + std::cerr << "strtof_l could not parse '" << st << std::endl; + return std::make_pair(0.0f, false); + } + return std::make_pair(d, true); +} + +bool tester() { + std::random_device rd; + std::mt19937 gen(rd()); + for (int q = 18; q <= 27; q++) { + std::cout << "q = " << -q << std::endl; + uint64_t power5 = 1; + for (int k = 0; k < q; k++) { + power5 *= 5; + } + uint64_t low_threshold = 0x20000000000000 / power5 + 1; + uint64_t threshold = 0xFFFFFFFFFFFFFFFF / power5; + std::uniform_int_distribution dis(low_threshold, threshold); + for (size_t i = 0; i < 10000; i++) { + uint64_t mantissa = dis(gen) * power5; + std::stringstream ss; + ss << mantissa; + ss << "e"; + ss << -q; + std::string to_be_parsed = ss.str(); + std::pair expected_double = + strtod_from_string(to_be_parsed.c_str()); + double result_value; + auto result = + fast_float::from_chars(to_be_parsed.data(), to_be_parsed.data() + to_be_parsed.size(), result_value); + if (result.ec != std::errc()) { + std::cout << to_be_parsed << std::endl; + std::cerr << " I could not parse " << std::endl; + return false; + } + if (result_value != expected_double.first) { + std::cout << to_be_parsed << std::endl; + std::cerr << std::hexfloat << result_value << std::endl; + std::cerr << std::hexfloat << expected_double.first << std::endl; + std::cerr << " Mismatch " << std::endl; + return false; + } + } + } + return true; +} + +int main() { + if (tester()) { + std::cout << std::endl; + std::cout << "all ok" << std::endl; + return EXIT_SUCCESS; + } + std::cerr << std::endl; + std::cerr << "errors were encountered" << std::endl; + return EXIT_FAILURE; +} From d521ddf7f7fca0b0004396397cfb7a6c9b78b2ad Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Thu, 19 Nov 2020 18:15:42 -0500 Subject: [PATCH 2/7] Let us adjust the powers instead. --- include/fast_float/decimal_to_binary.h | 39 ++------------------------ include/fast_float/fast_table.h | 20 ++++++------- script/table_generation.py | 2 +- 3 files changed, 13 insertions(+), 48 deletions(-) diff --git a/include/fast_float/decimal_to_binary.h b/include/fast_float/decimal_to_binary.h index 0b2741e..d5d1311 100644 --- a/include/fast_float/decimal_to_binary.h +++ b/include/fast_float/decimal_to_binary.h @@ -60,36 +60,6 @@ namespace { fastfloat_really_inline int power(int q) noexcept { return (((152170 + 65536) * q) >> 16) + 63; } - // Checks whether w is divisible by 5**-q. If it returns true, then - // w is definitively divisible by 5**-q. - inline bool is_divisible(int64_t q, uint64_t w) noexcept { - if((q>=-18) || (q<-27)) { return false; } - int64_t pos_q = -q; - // For each pair, first entry is the multiplicative inverse of 5**-q - // and the second one is the largest quotient. - // - // This could be more efficient by using... - // Faster remainder by direct computation: Applications to compilers and software libraries - // Software: Practice and Experience 49 (6), 2019. - // but the following is simple enough. - constexpr static uint64_t table[10][2] = { - {0xc1773b91fac10669,0x49c977}, // inverse of 5**18 - {0x26b172506559ce15,0xec1e4}, // inverse of 5**19 - {0xd489e3a9addec2d1,0x2f394}, // inverse of 5**20 - {0x90e860bb892c8d5d,0x971d}, // inverse of 5**21 - {0x502e79bf1b6f4f79,0x1e39}, // inverse of 5**22 - {0xdcd618596be30fe5,0x60b}, // inverse of 5**23 - {0x2c2ad1ab7bfa3661,0x135}, // inverse of 5**24 - {0x8d55d224bfed7ad,0x3d}, // inverse of 5**25 - {0x1c445d3a8cc9189,0xc}, // inverse of 5**26 - {0xcd27412a54f5b6b5,0x2}, // inverse of 5**27 - }; - uint64_t inverse = table[pos_q-18][0]; - uint64_t threshold = table[pos_q-18][1]; - uint64_t product = w * inverse; - if(product > threshold) { return false; } - return true; - } } // namespace @@ -131,13 +101,8 @@ adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { // In some very rare cases, this could happen, in which case we might need a more accurate // computation that what we can provide cheaply. This is very, very unlikely. // - // There is still a chance to recover. If w is divisible by 5**-q, - if(!is_divisible(q,w)) { - answer.power2 = -1; // This (a negative value) indicates an error condition. - return answer; - } - product.low += 1; - product.high += 1; + answer.power2 = -1; // This (a negative value) indicates an error condition. + return answer; } // The "compute_product_approximation" function can be slightly slower than a branchless approach: // value128 product = compute_product(q, w); diff --git a/include/fast_float/fast_table.h b/include/fast_float/fast_table.h index f92d6f6..b68fcac 100644 --- a/include/fast_float/fast_table.h +++ b/include/fast_float/fast_table.h @@ -347,16 +347,16 @@ const uint64_t power_of_five_128[]= { 0xa2425ff75e14fc31,0xa1258379a94d028d, 0xcad2f7f5359a3b3e,0x96ee45813a04330, 0xfd87b5f28300ca0d,0x8bca9d6e188853fc, - 0x9e74d1b791e07e48,0x775ea264cf55347d, - 0xc612062576589dda,0x95364afe032a819d, - 0xf79687aed3eec551,0x3a83ddbd83f52204, - 0x9abe14cd44753b52,0xc4926a9672793542, - 0xc16d9a0095928a27,0x75b7053c0f178293, - 0xf1c90080baf72cb1,0x5324c68b12dd6338, - 0x971da05074da7bee,0xd3f6fc16ebca5e03, - 0xbce5086492111aea,0x88f4bb1ca6bcf584, - 0xec1e4a7db69561a5,0x2b31e9e3d06c32e5, - 0x9392ee8e921d5d07,0x3aff322e62439fcf, + 0x9e74d1b791e07e48,0x775ea264cf55347e, + 0xc612062576589dda,0x95364afe032a819e, + 0xf79687aed3eec551,0x3a83ddbd83f52205, + 0x9abe14cd44753b52,0xc4926a9672793543, + 0xc16d9a0095928a27,0x75b7053c0f178294, + 0xf1c90080baf72cb1,0x5324c68b12dd6339, + 0x971da05074da7bee,0xd3f6fc16ebca5e04, + 0xbce5086492111aea,0x88f4bb1ca6bcf585, + 0xec1e4a7db69561a5,0x2b31e9e3d06c32e6, + 0x9392ee8e921d5d07,0x3aff322e62439fd0, 0xb877aa3236a4b449,0x9befeb9fad487c3, 0xe69594bec44de15b,0x4c2ebe687989a9b4, 0x901d7cf73ab0acd9,0xf9d37014bf60a11, diff --git a/script/table_generation.py b/script/table_generation.py index 0dfada7..a85dc47 100644 --- a/script/table_generation.py +++ b/script/table_generation.py @@ -8,7 +8,7 @@ for q in range(-342,0): z = 0 while( (1<= -17): + if(q >= -27): b = z + 127 c = 2 ** b // power5 + 1 format(c) From ad22e20e4ccfde1ad40f0c25085158b244dfaf3c Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Thu, 19 Nov 2020 21:37:10 -0500 Subject: [PATCH 3/7] Completing. --- tests/exhaustive32_midpoint.cpp | 6 +++--- tests/powersoffive_hardround.cpp | 23 ++++++++++++++++++++++- tests/random_string.cpp | 8 ++++---- tests/short_random_string.cpp | 6 +++--- tests/string_test.cpp | 6 +++--- 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/tests/exhaustive32_midpoint.cpp b/tests/exhaustive32_midpoint.cpp index 7c2080f..72469cd 100644 --- a/tests/exhaustive32_midpoint.cpp +++ b/tests/exhaustive32_midpoint.cpp @@ -31,7 +31,7 @@ template char *to_string(T d, char *buffer) { void strtod_from_string(const char * st, float& d) { char *pr = (char *)st; -#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun) d = cygwin_strtod_l(st, &pr); #elif defined(_WIN32) static _locale_t c_locale = _create_locale(LC_ALL, "C"); @@ -112,8 +112,8 @@ void allvalues() { } int main() { -#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) - std::cout << "Warning: msys/cygwin detected. This particular test is likely to generate false failures due to our reliance on the underlying runtime library." << std::endl; +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun) + std::cout << "Warning: msys/cygwin or solaris detected. This particular test is likely to generate false failures due to our reliance on the underlying runtime library." << std::endl; #endif allvalues(); std::cout << std::endl; diff --git a/tests/powersoffive_hardround.cpp b/tests/powersoffive_hardround.cpp index eea82bf..9288beb 100644 --- a/tests/powersoffive_hardround.cpp +++ b/tests/powersoffive_hardround.cpp @@ -5,6 +5,27 @@ #include #include + +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun) +// Anything at all that is related to cygwin, msys and so forth will +// always use this fallback because we cannot rely on it behaving as normal +// gcc. +#include +#include +// workaround for CYGWIN +double cygwin_strtod_l(const char* start, char** end) { + double d; + std::stringstream ss; + ss.imbue(std::locale::classic()); + ss << start; + ss >> d; + size_t nread = ss.tellg(); + *end = const_cast(start) + nread; + return d; +} +#endif + + std::pair strtod_from_string(const char *st) { double d; char *pr; @@ -25,7 +46,7 @@ std::pair strtod_from_string(const char *st) { std::pair strtof_from_string(char *st) { float d; char *pr; -#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun) d = cygwin_strtod_l(st, &pr); #elif defined(_WIN32) static _locale_t c_locale = _create_locale(LC_ALL, "C"); diff --git a/tests/random_string.cpp b/tests/random_string.cpp index 8663691..a58a092 100644 --- a/tests/random_string.cpp +++ b/tests/random_string.cpp @@ -2,7 +2,7 @@ #include #include -#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun) // Anything at all that is related to cygwin, msys and so forth will // always use this fallback because we cannot rely on it behaving as normal // gcc. @@ -126,7 +126,7 @@ std::pair strtod_from_string(char *st) { std::pair strtof_from_string(char *st) { float d; char *pr; -#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun) d = cygwin_strtod_l(st, &pr); #elif defined(_WIN32) static _locale_t c_locale = _create_locale(LC_ALL, "C"); @@ -203,8 +203,8 @@ bool tester(uint64_t seed, size_t volume) { } int main() { -#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) - std::cout << "Warning: msys/cygwin detected. This particular test is likely to generate false failures due to our reliance on the underlying runtime library." << std::endl; +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun) + std::cout << "Warning: msys/cygwin or solaris detected. This particular test is likely to generate false failures due to our reliance on the underlying runtime library." << std::endl; #endif if (tester(1234344, 100000000)) { std::cout << "All tests ok." << std::endl; diff --git a/tests/short_random_string.cpp b/tests/short_random_string.cpp index 50503cc..0d317b6 100644 --- a/tests/short_random_string.cpp +++ b/tests/short_random_string.cpp @@ -2,7 +2,7 @@ #include #include -#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun) // Anything at all that is related to cygwin, msys and so forth will // always use this fallback because we cannot rely on it behaving as normal // gcc. @@ -122,7 +122,7 @@ std::pair strtod_from_string(char *st) { std::pair strtof_from_string(char *st) { float d; char *pr; -#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun) d = cygwin_strtod_l(st, &pr); #elif defined(_WIN32) static _locale_t c_locale = _create_locale(LC_ALL, "C"); @@ -199,7 +199,7 @@ bool tester(uint64_t seed, size_t volume) { } int main() { -#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun) std::cout << "Warning: msys/cygwin detected. This particular test is likely to generate false failures due to our reliance on the underlying runtime library." << std::endl; #endif if (tester(1234344, 100000000)) { diff --git a/tests/string_test.cpp b/tests/string_test.cpp index 7da2f7b..a6f47e0 100644 --- a/tests/string_test.cpp +++ b/tests/string_test.cpp @@ -2,7 +2,7 @@ #include -#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun) // Anything at all that is related to cygwin, msys and so forth will // always use this fallback because we cannot rely on it behaving as normal // gcc. @@ -85,7 +85,7 @@ void strtod_from_string(const std::string &st, double& d) { template <> void strtod_from_string(const std::string &st, float& d) { char *pr = (char *)st.c_str(); -#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun) d = cygwin_strtod_l(st.c_str(), &pr); #elif defined(_WIN32) static _locale_t c_locale = _create_locale(LC_ALL, "C"); @@ -236,7 +236,7 @@ bool partow_test() { int main() { -#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) +#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun) std::cout << "Warning: msys/cygwin detected. This particular test is likely to generate false failures due to our reliance on the underlying runtime library." << std::endl; #endif std::cout << "32 bits checks" << std::endl; From dad8c84c388702089a68f6aec463f5012f8a30ee Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Thu, 19 Nov 2020 21:41:23 -0500 Subject: [PATCH 4/7] Upgrading. --- .github/workflows/ubuntu20.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu20.yml b/.github/workflows/ubuntu20.yml index 68aa1ee..33c38db 100644 --- a/.github/workflows/ubuntu20.yml +++ b/.github/workflows/ubuntu20.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.0 + uses: jwlawson/actions-setup-cmake@v1.4 with: cmake-version: '3.9.x' - name: install older compilers From 1283ea199be3dd5b14612f20a9f1ddd86b456e89 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Thu, 19 Nov 2020 21:45:55 -0500 Subject: [PATCH 5/7] Forgot this. --- .github/workflows/ubuntu18.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu18.yml b/.github/workflows/ubuntu18.yml index 23850cc..5ee88ff 100644 --- a/.github/workflows/ubuntu18.yml +++ b/.github/workflows/ubuntu18.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.0 + uses: jwlawson/actions-setup-cmake@v1.4 with: cmake-version: '3.9.x' - name: Install older compilers From 8fde4bad4e9722905b5eb0f6d80d26c97de15021 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Fri, 20 Nov 2020 16:09:53 -0500 Subject: [PATCH 6/7] Adding a guard. --- include/fast_float/decimal_to_binary.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/fast_float/decimal_to_binary.h b/include/fast_float/decimal_to_binary.h index d5d1311..cdeb44a 100644 --- a/include/fast_float/decimal_to_binary.h +++ b/include/fast_float/decimal_to_binary.h @@ -101,8 +101,11 @@ adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { // In some very rare cases, this could happen, in which case we might need a more accurate // computation that what we can provide cheaply. This is very, very unlikely. // - answer.power2 = -1; // This (a negative value) indicates an error condition. - return answer; + const bool inside_safe_exponent = (q >= 0) && (q <= 55); // always good because 5**q <2**128. + if(!inside_safe_exponent) { + answer.power2 = -1; // This (a negative value) indicates an error condition. + return answer; + } } // The "compute_product_approximation" function can be slightly slower than a branchless approach: // value128 product = compute_product(q, w); From 7bf5db7216c31e10cbddd5ab3a80c718b50fb03b Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Fri, 20 Nov 2020 17:05:06 -0500 Subject: [PATCH 7/7] Tuning. --- include/fast_float/decimal_to_binary.h | 8 ++++---- include/fast_float/float_common.h | 23 ++++++++++++++++++++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/include/fast_float/decimal_to_binary.h b/include/fast_float/decimal_to_binary.h index cdeb44a..e85f550 100644 --- a/include/fast_float/decimal_to_binary.h +++ b/include/fast_float/decimal_to_binary.h @@ -63,7 +63,6 @@ namespace { } // namespace - // w * 10 ** q // The returned value should be a valid ieee64 number that simply need to be packed. // However, in some very rare cases, the computation will fail. In such cases, we @@ -73,13 +72,13 @@ template fastfloat_really_inline adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { adjusted_mantissa answer; - if ((w == 0) || (q < smallest_power_of_five)) { + if ((w == 0) || (q < binary::smallest_power_of_ten())) { answer.power2 = 0; answer.mantissa = 0; // result should be zero return answer; } - if (q > largest_power_of_five) { + if (q > binary::largest_power_of_ten()) { // we want to get infinity: answer.power2 = binary::infinite_power(); answer.mantissa = 0; @@ -101,7 +100,8 @@ adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { // In some very rare cases, this could happen, in which case we might need a more accurate // computation that what we can provide cheaply. This is very, very unlikely. // - const bool inside_safe_exponent = (q >= 0) && (q <= 55); // always good because 5**q <2**128. + const bool inside_safe_exponent = (q >= -27) && (q <= 55); // always good because 5**q <2**128 when q>=0, + // and otherwise, for q<0, we have 5**-q<2**64 and the 128-bit reciprocal allows for exact computation. if(!inside_safe_exponent) { answer.power2 = -1; // This (a negative value) indicates an error condition. return answer; diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 60e5f6e..299fb5a 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -233,6 +233,8 @@ template struct binary_format { static constexpr int max_exponent_round_to_even(); static constexpr int min_exponent_round_to_even(); static constexpr uint64_t max_mantissa_fast_path(); + static constexpr int largest_power_of_ten(); + static constexpr int smallest_power_of_ten(); static constexpr T exact_power_of_ten(int64_t power); }; @@ -315,6 +317,25 @@ constexpr float binary_format::exact_power_of_ten(int64_t power) { return powers_of_ten_float[power]; } + +template <> +constexpr int binary_format::largest_power_of_ten() { + return 308; +} +template <> +constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +constexpr int binary_format::smallest_power_of_ten() { + return -342; +} +template <> +constexpr int binary_format::smallest_power_of_ten() { + return -65; +} + } // namespace fast_float // for convenience: @@ -328,4 +349,4 @@ inline std::ostream &operator<<(std::ostream &out, const fast_float::decimal &d) return out; } -#endif +#endif \ No newline at end of file