From 7b8f04500a3db37ae451288d5df2dc833f6a1eb7 Mon Sep 17 00:00:00 2001 From: Pavel Novikov Date: Tue, 2 Sep 2025 13:20:36 +0300 Subject: [PATCH 01/10] implemented multiplication of integer by power of 10 --- include/fast_float/fast_float.h | 22 +++++ include/fast_float/parse_number.h | 132 +++++++++++++++++++++--------- 2 files changed, 116 insertions(+), 38 deletions(-) diff --git a/include/fast_float/fast_float.h b/include/fast_float/fast_float.h index af65c96b..453a0be4 100644 --- a/include/fast_float/fast_float.h +++ b/include/fast_float/fast_float.h @@ -45,6 +45,28 @@ FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_advanced(UC const *first, UC const *last, T &value, parse_options_t options) noexcept; +/** + * This function multiplies an integer number by a power of 10 and returns + * the result as a double precision floating-point value that is correctly + * rounded. The resulting floating-point value is the closest floating-point + * value, using the "round to nearest, tie to even" convention for values that + * would otherwise fall right in-between two values. That is, we provide exact + * conversion according to the IEEE standard. + * + * On overflow infinity is returned, on underflow 0 is returned. + * + * The implementation does not throw and does not allocate memory (e.g., with + * `new` or `malloc`). + */ +FASTFLOAT_CONSTEXPR20 +typename std::enable_if::value, double>::type +multiply_integer_and_power_of_10(uint64_t mantissa, + int decimal_exponent) noexcept; +FASTFLOAT_CONSTEXPR20 +typename std::enable_if::value, double>::type +multiply_integer_and_power_of_10(int64_t mantissa, + int decimal_exponent) noexcept; + /** * from_chars for integer types. */ diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index e74c478f..8a04a358 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -188,32 +188,17 @@ from_chars(UC const *first, UC const *last, T &value, parse_options_t(fmt)); } -/** - * This function overload takes parsed_number_string_t structure that is created - * and populated either by from_chars_advanced function taking chars range and - * parsing options or other parsing custom function implemented by user. - */ -template -FASTFLOAT_CONSTEXPR20 from_chars_result_t -from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { - - static_assert(is_supported_float_type::value, - "only some floating-point types are supported"); - static_assert(is_supported_char_type::value, - "only char, wchar_t, char16_t and char32_t are supported"); - - from_chars_result_t answer; - - answer.ec = std::errc(); // be optimistic - answer.ptr = pns.lastmatch; +template +FASTFLOAT_CONSTEXPR20 bool +clinger_fast_path_impl(uint64_t mantissa, int64_t exponent, bool is_negative, + T &value) noexcept { // The implementation of the Clinger's fast path is convoluted because // we want round-to-nearest in all cases, irrespective of the rounding mode // selected on the thread. // We proceed optimistically, assuming that detail::rounds_to_nearest() // returns true. - if (binary_format::min_exponent_fast_path() <= pns.exponent && - pns.exponent <= binary_format::max_exponent_fast_path() && - !pns.too_many_digits) { + if (binary_format::min_exponent_fast_path() <= exponent && + exponent <= binary_format::max_exponent_fast_path()) { // Unfortunately, the conventional Clinger's fast path is only possible // when the system rounds to the nearest float. // @@ -224,41 +209,64 @@ from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { // We have that fegetround() == FE_TONEAREST. // Next is Clinger's fast path. - if (pns.mantissa <= binary_format::max_mantissa_fast_path()) { - value = T(pns.mantissa); - if (pns.exponent < 0) { - value = value / binary_format::exact_power_of_ten(-pns.exponent); + if (mantissa <= binary_format::max_mantissa_fast_path()) { + value = T(mantissa); + if (exponent < 0) { + value = value / binary_format::exact_power_of_ten(-exponent); } else { - value = value * binary_format::exact_power_of_ten(pns.exponent); + value = value * binary_format::exact_power_of_ten(exponent); } - if (pns.negative) { + if (is_negative) { value = -value; } - return answer; + return true; } } else { // We do not have that fegetround() == FE_TONEAREST. // Next is a modified Clinger's fast path, inspired by Jakub JelĂ­nek's // proposal - if (pns.exponent >= 0 && - pns.mantissa <= - binary_format::max_mantissa_fast_path(pns.exponent)) { + if (exponent >= 0 && + mantissa <= binary_format::max_mantissa_fast_path(exponent)) { #if defined(__clang__) || defined(FASTFLOAT_32BIT) // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD - if (pns.mantissa == 0) { - value = pns.negative ? T(-0.) : T(0.); - return answer; + if (mantissa == 0) { + value = is_negative ? T(-0.) : T(0.); + return true; } #endif - value = T(pns.mantissa) * - binary_format::exact_power_of_ten(pns.exponent); - if (pns.negative) { + value = T(mantissa) * binary_format::exact_power_of_ten(exponent); + if (is_negative) { value = -value; } - return answer; + return true; } } } + return false; +} + +/** + * This function overload takes parsed_number_string_t structure that is created + * and populated either by from_chars_advanced function taking chars range and + * parsing options or other parsing custom function implemented by user. + */ +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { + static_assert(is_supported_float_type::value, + "only some floating-point types are supported"); + static_assert(is_supported_char_type::value, + "only char, wchar_t, char16_t and char32_t are supported"); + + from_chars_result_t answer; + + answer.ec = std::errc(); // be optimistic + answer.ptr = pns.lastmatch; + + if (!pns.too_many_digits && + clinger_fast_path_impl(pns.mantissa, pns.exponent, pns.negative, value)) + return answer; + adjusted_mantissa am = compute_float>(pns.exponent, pns.mantissa); if (pns.too_many_digits && am.power2 >= 0) { @@ -336,6 +344,54 @@ from_chars(UC const *first, UC const *last, T &value, int base) noexcept { return from_chars_advanced(first, last, value, options); } +FASTFLOAT_CONSTEXPR20 +typename std::enable_if::value, double>::type +multiply_integer_and_power_of_10(uint64_t mantissa, + int decimal_exponent) noexcept { + double value; + if (clinger_fast_path_impl(mantissa, decimal_exponent, false, value)) + return value; + + adjusted_mantissa am = + compute_float>(decimal_exponent, mantissa); + to_float(false, am, value); + return value; +} + +FASTFLOAT_CONSTEXPR20 +typename std::enable_if::value, double>::type +multiply_integer_and_power_of_10(int64_t mantissa, + int decimal_exponent) noexcept { + const bool is_negative = mantissa < 0; + const uint64_t m = static_cast(is_negative ? -mantissa : mantissa); + + double value; + if (clinger_fast_path_impl(m, decimal_exponent, is_negative, value)) + return value; + + adjusted_mantissa am = + compute_float>(decimal_exponent, m); + to_float(is_negative, am, value); + return value; +} + +// the following overloads are here to avoid surprising ambiguity for int, +// unsigned, etc. +FASTFLOAT_CONSTEXPR20 +typename std::enable_if::value, double>::type +multiply_integer_and_power_of_10(unsigned mantissa, + int decimal_exponent) noexcept { + return multiply_integer_and_power_of_10(static_cast(mantissa), + decimal_exponent); +} + +FASTFLOAT_CONSTEXPR20 +typename std::enable_if::value, double>::type +multiply_integer_and_power_of_10(int mantissa, int decimal_exponent) noexcept { + return multiply_integer_and_power_of_10(static_cast(mantissa), + decimal_exponent); +} + template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_int_advanced(UC const *first, UC const *last, T &value, From a134561e4b11a2c0dc708692591fb8297a4701b4 Mon Sep 17 00:00:00 2001 From: Pavel Novikov Date: Tue, 2 Sep 2025 13:30:40 +0300 Subject: [PATCH 02/10] added missing `inline` specifiers --- include/fast_float/fast_float.h | 18 +++++++++------- include/fast_float/parse_number.h | 35 ++++++++++++++++++------------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/include/fast_float/fast_float.h b/include/fast_float/fast_float.h index 453a0be4..5f7cc09e 100644 --- a/include/fast_float/fast_float.h +++ b/include/fast_float/fast_float.h @@ -58,14 +58,16 @@ from_chars_advanced(UC const *first, UC const *last, T &value, * The implementation does not throw and does not allocate memory (e.g., with * `new` or `malloc`). */ -FASTFLOAT_CONSTEXPR20 -typename std::enable_if::value, double>::type -multiply_integer_and_power_of_10(uint64_t mantissa, - int decimal_exponent) noexcept; -FASTFLOAT_CONSTEXPR20 -typename std::enable_if::value, double>::type -multiply_integer_and_power_of_10(int64_t mantissa, - int decimal_exponent) noexcept; +FASTFLOAT_CONSTEXPR20 inline + typename std::enable_if::value, + double>::type + multiply_integer_and_power_of_10(uint64_t mantissa, + int decimal_exponent) noexcept; +FASTFLOAT_CONSTEXPR20 inline + typename std::enable_if::value, + double>::type + multiply_integer_and_power_of_10(int64_t mantissa, + int decimal_exponent) noexcept; /** * from_chars for integer types. diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index 8a04a358..fb4ae62e 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -344,10 +344,11 @@ from_chars(UC const *first, UC const *last, T &value, int base) noexcept { return from_chars_advanced(first, last, value, options); } -FASTFLOAT_CONSTEXPR20 -typename std::enable_if::value, double>::type -multiply_integer_and_power_of_10(uint64_t mantissa, - int decimal_exponent) noexcept { +FASTFLOAT_CONSTEXPR20 inline + typename std::enable_if::value, + double>::type + multiply_integer_and_power_of_10(uint64_t mantissa, + int decimal_exponent) noexcept { double value; if (clinger_fast_path_impl(mantissa, decimal_exponent, false, value)) return value; @@ -358,10 +359,11 @@ multiply_integer_and_power_of_10(uint64_t mantissa, return value; } -FASTFLOAT_CONSTEXPR20 -typename std::enable_if::value, double>::type -multiply_integer_and_power_of_10(int64_t mantissa, - int decimal_exponent) noexcept { +FASTFLOAT_CONSTEXPR20 inline + typename std::enable_if::value, + double>::type + multiply_integer_and_power_of_10(int64_t mantissa, + int decimal_exponent) noexcept { const bool is_negative = mantissa < 0; const uint64_t m = static_cast(is_negative ? -mantissa : mantissa); @@ -377,17 +379,20 @@ multiply_integer_and_power_of_10(int64_t mantissa, // the following overloads are here to avoid surprising ambiguity for int, // unsigned, etc. -FASTFLOAT_CONSTEXPR20 -typename std::enable_if::value, double>::type -multiply_integer_and_power_of_10(unsigned mantissa, - int decimal_exponent) noexcept { +FASTFLOAT_CONSTEXPR20 inline + typename std::enable_if::value, + double>::type + multiply_integer_and_power_of_10(unsigned mantissa, + int decimal_exponent) noexcept { return multiply_integer_and_power_of_10(static_cast(mantissa), decimal_exponent); } -FASTFLOAT_CONSTEXPR20 -typename std::enable_if::value, double>::type -multiply_integer_and_power_of_10(int mantissa, int decimal_exponent) noexcept { +FASTFLOAT_CONSTEXPR20 inline + typename std::enable_if::value, + double>::type + multiply_integer_and_power_of_10(int mantissa, + int decimal_exponent) noexcept { return multiply_integer_and_power_of_10(static_cast(mantissa), decimal_exponent); } From cc90f240ee8db4de61aab81da76d50f791fe4b1c Mon Sep 17 00:00:00 2001 From: Pavel Novikov Date: Tue, 2 Sep 2025 23:02:07 +0300 Subject: [PATCH 03/10] added some tests --- tests/basictest.cpp | 126 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/tests/basictest.cpp b/tests/basictest.cpp index 552d6f15..67539f02 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -1134,6 +1134,14 @@ TEST_CASE("double.inf") { std::errc::result_out_of_range); verify("1.9e308", std::numeric_limits::infinity(), std::errc::result_out_of_range); + + // DBL_MAX + 0.00000000000000001e308 + verify("1.79769313486231581e308", std::numeric_limits::infinity(), + std::errc::result_out_of_range); + + // DBL_MAX + 0.0000000000000001e308 + verify("1.7976931348623159e308", std::numeric_limits::infinity(), + std::errc::result_out_of_range); } TEST_CASE("double.general") { @@ -1143,6 +1151,13 @@ TEST_CASE("double.general") { verify("-22250738585072012e-324", -0x1p-1022); /* limit between normal and subnormal*/ verify("-1e-999", -0.0, std::errc::result_out_of_range); + + // DBL_TRUE_MIN / 2 + verify("2.4703282292062327e-324", 0.0, std::errc::result_out_of_range); + + // DBL_TRUE_MIN / 2 + 0.0000000000000001e-324 + verify("2.4703282292062328e-324", 0x0.0000000000001p-1022); + verify("-2.2222222222223e-322", -0x1.68p-1069); verify("9007199254740993.0", 0x1p+53); verify("860228122.6654514319E+90", 0x1.92bb20990715fp+328); @@ -2070,3 +2085,114 @@ TEST_CASE("bfloat16.general") { // 0.00000000000000000000000000000000000001175494210692441075487029444849287348827052428745893333857174530571588870475618904265502351336181163787841796875bf16); } #endif + +template +void integer_multiplication_by_power_of_10_test(Int mantissa, + int decimal_exponent, + double expected) { + const double actual = + fast_float::multiply_integer_and_power_of_10(mantissa, decimal_exponent); + + INFO("m * 10^e=" << mantissa << " * 10^" << decimal_exponent + << "\n" + " expected=" + << fHexAndDec(expected) << "\n" + << " ..actual=" << fHexAndDec(actual) << "\n" + << " expected mantissa=" + << iHexAndDec(get_mantissa(expected)) << "\n" + << " ..actual mantissa=" << iHexAndDec(get_mantissa(actual)) + << "\n"); + CHECK_EQ(actual, expected); +} + +#define verify_integer_multiplication_by_power_of_10(mantissa, \ + decimal_exponent) \ + do { \ + integer_multiplication_by_power_of_10_test(mantissa, decimal_exponent, \ + mantissa##e##decimal_exponent); \ + } while (false) + +TEST_CASE("multiply_integer_and_power_of_10") { + // explicitly verifying API with different types of integers + integer_multiplication_by_power_of_10_test(31, -1, 3.1); + integer_multiplication_by_power_of_10_test(31, -1, 3.1); + integer_multiplication_by_power_of_10_test(31, -1, 3.1); + integer_multiplication_by_power_of_10_test(-31, -1, -3.1); + integer_multiplication_by_power_of_10_test(31415, -4, 3.1415); + integer_multiplication_by_power_of_10_test(-31415, -4, -3.1415); + integer_multiplication_by_power_of_10_test(31415, -4, 3.1415); + integer_multiplication_by_power_of_10_test(314159265, -8, + 3.14159265); + integer_multiplication_by_power_of_10_test(-314159265, -8, + -3.14159265); + integer_multiplication_by_power_of_10_test(3141592653, -9, + 3.141592653); + integer_multiplication_by_power_of_10_test(3141592653589793238, -18, + 3.141592653589793238); + integer_multiplication_by_power_of_10_test(-3141592653589793238, -18, + -3.141592653589793238); + integer_multiplication_by_power_of_10_test(3141592653589793238, -18, + 3.141592653589793238); + + for (int mode : {FE_UPWARD, FE_DOWNWARD, FE_TOWARDZERO, FE_TONEAREST}) { + fesetround(mode); + INFO("fesetround(): " << std::string{round_name(mode)}); + + struct Guard { + ~Guard() { fesetround(FE_TONEAREST); } + } guard; + + verify_integer_multiplication_by_power_of_10(0, 0); + verify_integer_multiplication_by_power_of_10(1, 0); + verify_integer_multiplication_by_power_of_10(0, 1); + verify_integer_multiplication_by_power_of_10(1, 1); + verify_integer_multiplication_by_power_of_10(-1, 0); + verify_integer_multiplication_by_power_of_10(0, -1); + verify_integer_multiplication_by_power_of_10(-1, -1); + verify_integer_multiplication_by_power_of_10(-1, 1); + verify_integer_multiplication_by_power_of_10(1, -1); + verify_integer_multiplication_by_power_of_10(-1, -1); + + integer_multiplication_by_power_of_10_test(49406564584124654, -340, + DBL_TRUE_MIN); + integer_multiplication_by_power_of_10_test(22250738585072014, -324, + DBL_MIN); + integer_multiplication_by_power_of_10_test(17976931348623158, 292, DBL_MAX); + + // DBL_TRUE_MIN / 2 underflows to 0 + integer_multiplication_by_power_of_10_test(49406564584124654 / 2, -340, 0.); + + // DBL_TRUE_MIN / 2 + 0.0000000000000001e-324 rounds to DBL_TRUE_MIN + integer_multiplication_by_power_of_10_test(49406564584124654 / 2 + 1, -340, + DBL_TRUE_MIN); + + // DBL_MAX + 0.0000000000000001e308 overflows to infinity + integer_multiplication_by_power_of_10_test( + 17976931348623158 + 1, 292, std::numeric_limits::infinity()); + + // loosely verifying correct rounding of 1 to 64 bits + // worth of significant digits + verify_integer_multiplication_by_power_of_10(1, 42); + verify_integer_multiplication_by_power_of_10(12, 42); + verify_integer_multiplication_by_power_of_10(123, 42); + verify_integer_multiplication_by_power_of_10(1234, 42); + verify_integer_multiplication_by_power_of_10(12345, 42); + verify_integer_multiplication_by_power_of_10(123456, 42); + verify_integer_multiplication_by_power_of_10(1234567, 42); + verify_integer_multiplication_by_power_of_10(12345678, 42); + verify_integer_multiplication_by_power_of_10(123456789, 42); + verify_integer_multiplication_by_power_of_10(1234567890, 42); + verify_integer_multiplication_by_power_of_10(12345678901, 42); + verify_integer_multiplication_by_power_of_10(123456789012, 42); + verify_integer_multiplication_by_power_of_10(1234567890123, 42); + verify_integer_multiplication_by_power_of_10(12345678901234, 42); + verify_integer_multiplication_by_power_of_10(123456789012345, 42); + verify_integer_multiplication_by_power_of_10(1234567890123456, 42); + verify_integer_multiplication_by_power_of_10(12345678901234567, 42); + verify_integer_multiplication_by_power_of_10(123456789012345678, 42); + verify_integer_multiplication_by_power_of_10(1234567890123456789, 42); + verify_integer_multiplication_by_power_of_10(12345678901234567890, 42); + // ULLONG_MAX + verify_integer_multiplication_by_power_of_10(18446744073709551615, 42); + } +} \ No newline at end of file From 6be07d66a83bf6186d427bb21344dcdd28df09d2 Mon Sep 17 00:00:00 2001 From: Pavel Novikov Date: Wed, 3 Sep 2025 16:39:56 +0300 Subject: [PATCH 04/10] inlining Clinger's fast path because why not, and it seems to bring performance to the level before the changes somewhat --- include/fast_float/parse_number.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index fb4ae62e..a5a236fa 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -189,7 +189,7 @@ from_chars(UC const *first, UC const *last, T &value, } template -FASTFLOAT_CONSTEXPR20 bool +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool clinger_fast_path_impl(uint64_t mantissa, int64_t exponent, bool is_negative, T &value) noexcept { // The implementation of the Clinger's fast path is convoluted because From 763558b9acc175ab2c81a6fac39f195d7bc415b9 Mon Sep 17 00:00:00 2001 From: Pavel Novikov Date: Wed, 3 Sep 2025 13:34:57 +0300 Subject: [PATCH 05/10] cleaned up tests --- tests/basictest.cpp | 119 +++++++++++++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 41 deletions(-) diff --git a/tests/basictest.cpp b/tests/basictest.cpp index 67539f02..a8e21216 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -2087,9 +2087,9 @@ TEST_CASE("bfloat16.general") { #endif template -void integer_multiplication_by_power_of_10_test(Int mantissa, - int decimal_exponent, - double expected) { +void verify_integer_multiplication_by_power_of_10(Int mantissa, + int decimal_exponent, + double expected) { const double actual = fast_float::multiply_integer_and_power_of_10(mantissa, decimal_exponent); @@ -2105,34 +2105,41 @@ void integer_multiplication_by_power_of_10_test(Int mantissa, CHECK_EQ(actual, expected); } -#define verify_integer_multiplication_by_power_of_10(mantissa, \ - decimal_exponent) \ - do { \ - integer_multiplication_by_power_of_10_test(mantissa, decimal_exponent, \ - mantissa##e##decimal_exponent); \ - } while (false) +template +void verify_integer_multiplication_by_power_of_10(Int mantissa, + int decimal_exponent) { + std::string constructed_string = + std::to_string(mantissa) + "e" + std::to_string(decimal_exponent); + double expected_result; + const auto result = fast_float::from_chars( + constructed_string.data(), + constructed_string.data() + constructed_string.size(), expected_result); + if (result.ec != std::errc()) + INFO("Failed to parse: " << constructed_string); + verify_integer_multiplication_by_power_of_10(mantissa, decimal_exponent, + expected_result); +} TEST_CASE("multiply_integer_and_power_of_10") { // explicitly verifying API with different types of integers - integer_multiplication_by_power_of_10_test(31, -1, 3.1); - integer_multiplication_by_power_of_10_test(31, -1, 3.1); - integer_multiplication_by_power_of_10_test(31, -1, 3.1); - integer_multiplication_by_power_of_10_test(-31, -1, -3.1); - integer_multiplication_by_power_of_10_test(31415, -4, 3.1415); - integer_multiplication_by_power_of_10_test(-31415, -4, -3.1415); - integer_multiplication_by_power_of_10_test(31415, -4, 3.1415); - integer_multiplication_by_power_of_10_test(314159265, -8, - 3.14159265); - integer_multiplication_by_power_of_10_test(-314159265, -8, - -3.14159265); - integer_multiplication_by_power_of_10_test(3141592653, -9, - 3.141592653); - integer_multiplication_by_power_of_10_test(3141592653589793238, -18, - 3.141592653589793238); - integer_multiplication_by_power_of_10_test(-3141592653589793238, -18, - -3.141592653589793238); - integer_multiplication_by_power_of_10_test(3141592653589793238, -18, - 3.141592653589793238); + verify_integer_multiplication_by_power_of_10(31, -1, 3.1); + verify_integer_multiplication_by_power_of_10(-31, -1, -3.1); + verify_integer_multiplication_by_power_of_10(31, -1, 3.1); + verify_integer_multiplication_by_power_of_10(31415, -4, 3.1415); + verify_integer_multiplication_by_power_of_10(-31415, -4, -3.1415); + verify_integer_multiplication_by_power_of_10(31415, -4, 3.1415); + verify_integer_multiplication_by_power_of_10(314159265, -8, + 3.14159265); + verify_integer_multiplication_by_power_of_10(-314159265, -8, + -3.14159265); + verify_integer_multiplication_by_power_of_10(3141592653, -9, + 3.141592653); + verify_integer_multiplication_by_power_of_10( + 3141592653589793238, -18, 3.141592653589793238); + verify_integer_multiplication_by_power_of_10( + -3141592653589793238, -18, -3.141592653589793238); + verify_integer_multiplication_by_power_of_10( + 3141592653589793238, -18, 3.141592653589793238); for (int mode : {FE_UPWARD, FE_DOWNWARD, FE_TOWARDZERO, FE_TONEAREST}) { fesetround(mode); @@ -2151,48 +2158,78 @@ TEST_CASE("multiply_integer_and_power_of_10") { verify_integer_multiplication_by_power_of_10(-1, -1); verify_integer_multiplication_by_power_of_10(-1, 1); verify_integer_multiplication_by_power_of_10(1, -1); - verify_integer_multiplication_by_power_of_10(-1, -1); - integer_multiplication_by_power_of_10_test(49406564584124654, -340, - DBL_TRUE_MIN); - integer_multiplication_by_power_of_10_test(22250738585072014, -324, - DBL_MIN); - integer_multiplication_by_power_of_10_test(17976931348623158, 292, DBL_MAX); + verify_integer_multiplication_by_power_of_10( + 49406564584124654, -340, std::numeric_limits::denorm_min()); + verify_integer_multiplication_by_power_of_10( + 22250738585072014, -324, std::numeric_limits::min()); + verify_integer_multiplication_by_power_of_10( + 17976931348623158, 292, std::numeric_limits::max()); // DBL_TRUE_MIN / 2 underflows to 0 - integer_multiplication_by_power_of_10_test(49406564584124654 / 2, -340, 0.); + verify_integer_multiplication_by_power_of_10(49406564584124654 / 2, -340, + 0.); // DBL_TRUE_MIN / 2 + 0.0000000000000001e-324 rounds to DBL_TRUE_MIN - integer_multiplication_by_power_of_10_test(49406564584124654 / 2 + 1, -340, - DBL_TRUE_MIN); + verify_integer_multiplication_by_power_of_10( + 49406564584124654 / 2 + 1, -340, + std::numeric_limits::denorm_min()); // DBL_MAX + 0.0000000000000001e308 overflows to infinity - integer_multiplication_by_power_of_10_test( + verify_integer_multiplication_by_power_of_10( 17976931348623158 + 1, 292, std::numeric_limits::infinity()); + // DBL_MAX + 0.00000000000000001e308 overflows to infinity + verify_integer_multiplication_by_power_of_10( + 179769313486231580 + 1, 291, std::numeric_limits::infinity()); // loosely verifying correct rounding of 1 to 64 bits // worth of significant digits verify_integer_multiplication_by_power_of_10(1, 42); + verify_integer_multiplication_by_power_of_10(1, -42); verify_integer_multiplication_by_power_of_10(12, 42); + verify_integer_multiplication_by_power_of_10(12, -42); verify_integer_multiplication_by_power_of_10(123, 42); + verify_integer_multiplication_by_power_of_10(123, -42); verify_integer_multiplication_by_power_of_10(1234, 42); + verify_integer_multiplication_by_power_of_10(1234, -42); verify_integer_multiplication_by_power_of_10(12345, 42); + verify_integer_multiplication_by_power_of_10(12345, -42); verify_integer_multiplication_by_power_of_10(123456, 42); + verify_integer_multiplication_by_power_of_10(123456, -42); verify_integer_multiplication_by_power_of_10(1234567, 42); + verify_integer_multiplication_by_power_of_10(1234567, -42); verify_integer_multiplication_by_power_of_10(12345678, 42); + verify_integer_multiplication_by_power_of_10(12345678, -42); verify_integer_multiplication_by_power_of_10(123456789, 42); verify_integer_multiplication_by_power_of_10(1234567890, 42); + verify_integer_multiplication_by_power_of_10(1234567890, -42); verify_integer_multiplication_by_power_of_10(12345678901, 42); + verify_integer_multiplication_by_power_of_10(12345678901, -42); verify_integer_multiplication_by_power_of_10(123456789012, 42); + verify_integer_multiplication_by_power_of_10(123456789012, -42); verify_integer_multiplication_by_power_of_10(1234567890123, 42); + verify_integer_multiplication_by_power_of_10(1234567890123, -42); verify_integer_multiplication_by_power_of_10(12345678901234, 42); + verify_integer_multiplication_by_power_of_10(12345678901234, -42); verify_integer_multiplication_by_power_of_10(123456789012345, 42); + verify_integer_multiplication_by_power_of_10(123456789012345, -42); verify_integer_multiplication_by_power_of_10(1234567890123456, 42); + verify_integer_multiplication_by_power_of_10(1234567890123456, -42); verify_integer_multiplication_by_power_of_10(12345678901234567, 42); + verify_integer_multiplication_by_power_of_10(12345678901234567, -42); verify_integer_multiplication_by_power_of_10(123456789012345678, 42); + verify_integer_multiplication_by_power_of_10(123456789012345678, -42); verify_integer_multiplication_by_power_of_10(1234567890123456789, 42); - verify_integer_multiplication_by_power_of_10(12345678901234567890, 42); - // ULLONG_MAX - verify_integer_multiplication_by_power_of_10(18446744073709551615, 42); + verify_integer_multiplication_by_power_of_10(1234567890123456789, -42); + verify_integer_multiplication_by_power_of_10(12345678901234567890ull, 42); + verify_integer_multiplication_by_power_of_10(12345678901234567890ull, -42); + verify_integer_multiplication_by_power_of_10( + std::numeric_limits::max(), 42); + verify_integer_multiplication_by_power_of_10( + std::numeric_limits::max(), -42); + verify_integer_multiplication_by_power_of_10( + std::numeric_limits::max(), 42); + verify_integer_multiplication_by_power_of_10( + std::numeric_limits::max(), -42); } } \ No newline at end of file From 20a73834425b0f62f95b5961a1054b7692769a44 Mon Sep 17 00:00:00 2001 From: Pavel Novikov Date: Fri, 5 Sep 2025 13:27:15 +0300 Subject: [PATCH 06/10] renamed the function, cleaned up return type --- include/fast_float/fast_float.h | 10 +++------ include/fast_float/parse_number.h | 34 +++++++++---------------------- tests/basictest.cpp | 4 ++-- 3 files changed, 15 insertions(+), 33 deletions(-) diff --git a/include/fast_float/fast_float.h b/include/fast_float/fast_float.h index 5f7cc09e..e316f701 100644 --- a/include/fast_float/fast_float.h +++ b/include/fast_float/fast_float.h @@ -58,16 +58,12 @@ from_chars_advanced(UC const *first, UC const *last, T &value, * The implementation does not throw and does not allocate memory (e.g., with * `new` or `malloc`). */ +FASTFLOAT_CONSTEXPR20 inline double +integer_times_pow10(uint64_t mantissa, int decimal_exponent) noexcept; FASTFLOAT_CONSTEXPR20 inline typename std::enable_if::value, double>::type - multiply_integer_and_power_of_10(uint64_t mantissa, - int decimal_exponent) noexcept; -FASTFLOAT_CONSTEXPR20 inline - typename std::enable_if::value, - double>::type - multiply_integer_and_power_of_10(int64_t mantissa, - int decimal_exponent) noexcept; + integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept; /** * from_chars for integer types. diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index a5a236fa..416f5f76 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -344,11 +344,8 @@ from_chars(UC const *first, UC const *last, T &value, int base) noexcept { return from_chars_advanced(first, last, value, options); } -FASTFLOAT_CONSTEXPR20 inline - typename std::enable_if::value, - double>::type - multiply_integer_and_power_of_10(uint64_t mantissa, - int decimal_exponent) noexcept { +FASTFLOAT_CONSTEXPR20 inline double +integer_times_pow10(uint64_t mantissa, int decimal_exponent) noexcept { double value; if (clinger_fast_path_impl(mantissa, decimal_exponent, false, value)) return value; @@ -359,11 +356,8 @@ FASTFLOAT_CONSTEXPR20 inline return value; } -FASTFLOAT_CONSTEXPR20 inline - typename std::enable_if::value, - double>::type - multiply_integer_and_power_of_10(int64_t mantissa, - int decimal_exponent) noexcept { +FASTFLOAT_CONSTEXPR20 inline double +integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept { const bool is_negative = mantissa < 0; const uint64_t m = static_cast(is_negative ? -mantissa : mantissa); @@ -379,22 +373,14 @@ FASTFLOAT_CONSTEXPR20 inline // the following overloads are here to avoid surprising ambiguity for int, // unsigned, etc. -FASTFLOAT_CONSTEXPR20 inline - typename std::enable_if::value, - double>::type - multiply_integer_and_power_of_10(unsigned mantissa, - int decimal_exponent) noexcept { - return multiply_integer_and_power_of_10(static_cast(mantissa), - decimal_exponent); +FASTFLOAT_CONSTEXPR20 inline double +integer_times_pow10(unsigned mantissa, int decimal_exponent) noexcept { + return integer_times_pow10(static_cast(mantissa), decimal_exponent); } -FASTFLOAT_CONSTEXPR20 inline - typename std::enable_if::value, - double>::type - multiply_integer_and_power_of_10(int mantissa, - int decimal_exponent) noexcept { - return multiply_integer_and_power_of_10(static_cast(mantissa), - decimal_exponent); +FASTFLOAT_CONSTEXPR20 inline double +integer_times_pow10(int mantissa, int decimal_exponent) noexcept { + return integer_times_pow10(static_cast(mantissa), decimal_exponent); } template diff --git a/tests/basictest.cpp b/tests/basictest.cpp index a8e21216..4899b40c 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -2091,7 +2091,7 @@ void verify_integer_multiplication_by_power_of_10(Int mantissa, int decimal_exponent, double expected) { const double actual = - fast_float::multiply_integer_and_power_of_10(mantissa, decimal_exponent); + fast_float::integer_times_pow10(mantissa, decimal_exponent); INFO("m * 10^e=" << mantissa << " * 10^" << decimal_exponent << "\n" @@ -2120,7 +2120,7 @@ void verify_integer_multiplication_by_power_of_10(Int mantissa, expected_result); } -TEST_CASE("multiply_integer_and_power_of_10") { +TEST_CASE("integer_times_pow10") { // explicitly verifying API with different types of integers verify_integer_multiplication_by_power_of_10(31, -1, 3.1); verify_integer_multiplication_by_power_of_10(-31, -1, -3.1); From 6702cd424436c7dc6c53ad4430f41173a6d5c2a7 Mon Sep 17 00:00:00 2001 From: Pavel Novikov Date: Fri, 5 Sep 2025 13:28:27 +0300 Subject: [PATCH 07/10] added doc section in the README, added example code test executable --- README.md | 27 +++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/example_integer_times_pow10.cpp | 12 ++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 tests/example_integer_times_pow10.cpp diff --git a/README.md b/README.md index 8770ac44..7ebb163e 100644 --- a/README.md +++ b/README.md @@ -357,6 +357,33 @@ int main() { } ``` +## Multiplication of an integer by a power of 10 +An integer `W` can be multiplied by a power of ten `10^Q` and +converted to `double` with correctly rounded value +(in "round to nearest, tie to even" fashion) using +`fast_float::integer_times_pow10()`, e.g.: +```C++ +const uint64_t W = 12345678901234567; +const int Q = 23; +const double result = fast_float::integer_times_pow10(W, Q); +std::cout.precision(17); +std::cout << W << " * 10^" << Q << " = " << result << " (" + << (result == 12345678901234567e23 ? "==" : "!=") << "expected)\n"; +``` +outputs +``` +12345678901234567 * 10^23 = 1.2345678901234567e+39 (==expected) +``` +`fast_float::integer_times_pow10()` gives the same result as +using `fast_float::from_chars()` when parsing the string `"WeQ"` +(in this example `"12345678901234567e23"`), +except `fast_float::integer_times_pow10()` does not report out-of-range errors, and +underflows to zero or overflows to infinity when the resulting value is +out of range. + +Overloads of `fast_float::integer_times_pow10()` are provided for +signed and unsigned integer types: `int64_t`, `uint64_t`, etc. + ## Users and Related Work diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c4e43b21..817c4e85 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -71,6 +71,7 @@ fast_float_add_cpp_test(wide_char_test) 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(basictest) option(FASTFLOAT_CONSTEXPR_TESTS "Require constexpr tests (build will fail if the compiler won't support it)" OFF) if (FASTFLOAT_CONSTEXPR_TESTS) diff --git a/tests/example_integer_times_pow10.cpp b/tests/example_integer_times_pow10.cpp new file mode 100644 index 00000000..3e86826c --- /dev/null +++ b/tests/example_integer_times_pow10.cpp @@ -0,0 +1,12 @@ +#include "fast_float/fast_float.h" + +#include + +int main() { + const uint64_t W = 12345678901234567; + const int Q = 23; + const double result = fast_float::integer_times_pow10(W, Q); + std::cout.precision(17); + std::cout << W << " * 10^" << Q << " = " << result << " (" + << (result == 12345678901234567e23 ? "==" : "!=") << "expected)\n"; +} From e12463583fc4b8f193667cf5c411f245d3a3673f Mon Sep 17 00:00:00 2001 From: Pavel Novikov Date: Sat, 6 Sep 2025 00:12:37 +0300 Subject: [PATCH 08/10] added lacking overloads to avoid potential ambiguity --- include/fast_float/parse_number.h | 12 ++++++++++++ tests/basictest.cpp | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index 416f5f76..71989f61 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -373,6 +373,18 @@ integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept { // the following overloads are here to avoid surprising ambiguity for int, // unsigned, etc. +#if !defined(_MSC_VER) +FASTFLOAT_CONSTEXPR20 inline double +integer_times_pow10(unsigned long long mantissa, + int decimal_exponent) noexcept { + return integer_times_pow10(static_cast(mantissa), decimal_exponent); +} + +FASTFLOAT_CONSTEXPR20 inline double +integer_times_pow10(long long mantissa, int decimal_exponent) noexcept { + return integer_times_pow10(static_cast(mantissa), decimal_exponent); +} +#endif FASTFLOAT_CONSTEXPR20 inline double integer_times_pow10(unsigned mantissa, int decimal_exponent) noexcept { return integer_times_pow10(static_cast(mantissa), decimal_exponent); diff --git a/tests/basictest.cpp b/tests/basictest.cpp index 4899b40c..dc117526 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -2140,6 +2140,10 @@ TEST_CASE("integer_times_pow10") { -3141592653589793238, -18, -3.141592653589793238); verify_integer_multiplication_by_power_of_10( 3141592653589793238, -18, 3.141592653589793238); + verify_integer_multiplication_by_power_of_10( + -3141592653589793238, -18, -3.141592653589793238); + verify_integer_multiplication_by_power_of_10( + 3141592653589793238, -18, 3.141592653589793238); for (int mode : {FE_UPWARD, FE_DOWNWARD, FE_TOWARDZERO, FE_TONEAREST}) { fesetround(mode); From 7ae62ee0d52675e9c853aeded4ede8a32f866615 Mon Sep 17 00:00:00 2001 From: Pavel Novikov Date: Sat, 6 Sep 2025 02:10:52 +0300 Subject: [PATCH 09/10] finally got the anti-ambiguity overloads right? --- include/fast_float/parse_number.h | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index 71989f61..d2b2aaf9 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -373,25 +373,17 @@ integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept { // the following overloads are here to avoid surprising ambiguity for int, // unsigned, etc. -#if !defined(_MSC_VER) -FASTFLOAT_CONSTEXPR20 inline double -integer_times_pow10(unsigned long long mantissa, - int decimal_exponent) noexcept { - return integer_times_pow10(static_cast(mantissa), decimal_exponent); -} - -FASTFLOAT_CONSTEXPR20 inline double -integer_times_pow10(long long mantissa, int decimal_exponent) noexcept { - return integer_times_pow10(static_cast(mantissa), decimal_exponent); -} -#endif -FASTFLOAT_CONSTEXPR20 inline double -integer_times_pow10(unsigned mantissa, int decimal_exponent) noexcept { +template +FASTFLOAT_CONSTEXPR20 inline std::enable_if_t< + std::is_integral::value && !std::is_signed::value, double> +integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { return integer_times_pow10(static_cast(mantissa), decimal_exponent); } -FASTFLOAT_CONSTEXPR20 inline double -integer_times_pow10(int mantissa, int decimal_exponent) noexcept { +template +FASTFLOAT_CONSTEXPR20 inline std::enable_if_t< + std::is_integral::value && std::is_signed::value, double> +integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { return integer_times_pow10(static_cast(mantissa), decimal_exponent); } From 0a230326ab946e95c4a65dd9539be8638f3ffe88 Mon Sep 17 00:00:00 2001 From: Pavel Novikov Date: Sat, 6 Sep 2025 02:22:41 +0300 Subject: [PATCH 10/10] now finally got the anti-ambiguity overloads right, right? --- include/fast_float/parse_number.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index d2b2aaf9..a44fef0b 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -374,15 +374,15 @@ integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept { // the following overloads are here to avoid surprising ambiguity for int, // unsigned, etc. template -FASTFLOAT_CONSTEXPR20 inline std::enable_if_t< - std::is_integral::value && !std::is_signed::value, double> +FASTFLOAT_CONSTEXPR20 inline typename std::enable_if< + std::is_integral::value && !std::is_signed::value, double>::type integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { return integer_times_pow10(static_cast(mantissa), decimal_exponent); } template -FASTFLOAT_CONSTEXPR20 inline std::enable_if_t< - std::is_integral::value && std::is_signed::value, double> +FASTFLOAT_CONSTEXPR20 inline typename std::enable_if< + std::is_integral::value && std::is_signed::value, double>::type integer_times_pow10(Int mantissa, int decimal_exponent) noexcept { return integer_times_pow10(static_cast(mantissa), decimal_exponent); }