From 7b8f04500a3db37ae451288d5df2dc833f6a1eb7 Mon Sep 17 00:00:00 2001 From: Pavel Novikov Date: Tue, 2 Sep 2025 13:20:36 +0300 Subject: [PATCH 01/17] 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 af65c96..453a0be 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 e74c478..8a04a35 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/17] 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 453a0be..5f7cc09 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 8a04a35..fb4ae62 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/17] added some tests --- tests/basictest.cpp | 126 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/tests/basictest.cpp b/tests/basictest.cpp index 552d6f1..67539f0 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/17] 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 fb4ae62..a5a236f 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/17] 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 67539f0..a8e2121 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/17] 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 5f7cc09..e316f70 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 a5a236f..416f5f7 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 a8e2121..4899b40 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/17] 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 8770ac4..7ebb163 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 c4e43b2..817c4e8 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 0000000..3e86826 --- /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/17] 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 416f5f7..71989f6 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 4899b40..dc11752 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/17] 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 71989f6..d2b2aaf 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 { +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(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); -} - -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/17] 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 d2b2aaf..a44fef0 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); } From 9d81c71aef25a2b9aab22a500e956c18e00e30a2 Mon Sep 17 00:00:00 2001 From: InvalidUsernameException Date: Mon, 15 Sep 2025 23:05:25 +0200 Subject: [PATCH 11/17] Do not mis-parse certain wide-character emojis as integer When calling ch_to_digit() with a UTF-16 or UTF-32 code unit, it simply truncates away any data stored in the non-low byte(s) of the code unit. It then uses a lookup table to determine whether the low byte corresponds to an ASCII digit. This is incorrect because as soon as any bit outside the low byte is set, the number will never correspond to a ASCII digit anymore. To fix this, we produce a mask that is all zeroes if any bit outside the low byte is set in the code unit, all ones otherwise. Anding this mask with the original code unit forces the table lookup to return the sentinel value from the zero-index if any high bit was set and causes the code unit not to be parsed as integer. This bug was discovered when loading Mastodon posts inside the Ladybird browser where some of Mastodon's JavaScript would trigger the code path that erroneously parsed the emoji as integer. It had the visible effect that some digits inside the posts would get rendered as one of the emojis that parsed to that digit. For more details see this issue: https://github.com/LadybirdBrowser/ladybird/issues/6205 The emojis in the test case are simply all the emojis used on Mastodon that caused the bug. They can be found here: https://github.com/mastodon/mastodon/blob/06803422da3794538cd9cd5c7ccd61a0694ef921/app/javascript/mastodon/features/emoji/emoji_map.json --- include/fast_float/float_common.h | 8 +- tests/fast_int.cpp | 271 +++++++++++++++++++++++++++++- 2 files changed, 277 insertions(+), 2 deletions(-) diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 4a13e3b..2b8a528 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -1132,7 +1132,13 @@ template constexpr uint64_t int_luts::min_safe_u64[]; template fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) { - return int_luts<>::chdigit[static_cast(c)]; + using UnsignedUC = typename std::make_unsigned::type; + auto uc = static_cast(c); + // For types larger than one byte, we need to force an index with sentinel + // value (using index zero because that is easiest) if any byte other than + // the low byte is non-zero. + auto mask = static_cast(-((uc & ~0xFFull) == 0)); + return int_luts<>::chdigit[static_cast(uc & mask)]; } fastfloat_really_inline constexpr size_t max_digits_u64(int base) { diff --git a/tests/fast_int.cpp b/tests/fast_int.cpp index 9b107c8..49044d3 100644 --- a/tests/fast_int.cpp +++ b/tests/fast_int.cpp @@ -831,6 +831,275 @@ int main() { return EXIT_FAILURE; } } + // dont parse UTF-16 code units of emojis as int if low byte is ascii digit + { + const std::u16string emojis[] = { + u"ℹ", u"ℹ️", u"☸", u"☸️", u"☹", u"☹️", u"✳", u"✳️", + u"✴", u"✴️", u"⤴", u"⤴️", u"⤵", u"⤵️", u"〰", u"〰️", + }; + bool failed = false; + auto array_size = sizeof(emojis) / sizeof(emojis[0]); + for (size_t i = 0; i < array_size; i++) { + auto e = emojis[i]; + int foo; + auto answer = fast_float::from_chars(e.data(), e.data() + e.size(), foo); + if (answer.ec == std::errc()) { + failed = true; + std::cerr << "Incorrectly parsed emoji #" << i << " as integer " << foo + << "." << std::endl; + } + } + + if (failed) { + return EXIT_FAILURE; + } + } + // dont parse UTF-32 code points of emojis as int if low byte is ascii digit + { + const std::u32string emojis[] = { + U"ℹ", + U"ℹ️", + U"☸", + U"☸️", + U"☹", + U"☹️", + U"✳", + U"✳️", + U"✴", + U"✴️", + U"⤴", + U"⤴️", + U"⤵", + U"⤵️", + U"〰", + U"〰️", + U"🈲", + U"🈳", + U"🈴", + U"🈵", + U"🈶", + U"🈷", + U"🈷️", + U"🈸", + U"🈹", + U"🌰", + U"🌱", + U"🌲", + U"🌳", + U"🌴", + U"🌵", + U"🌶", + U"🌶️", + U"🌷", + U"🌸", + U"🌹", + U"🐰", + U"🐱", + U"🐲", + U"🐳", + U"🐴", + U"🐵", + U"🐶", + U"🐷", + U"🐸", + U"🐹", + U"🔰", + U"🔱", + U"🔲", + U"🔳", + U"🔴", + U"🔵", + U"🔶", + U"🔷", + U"🔸", + U"🔹", + U"😰", + U"😱", + U"😲", + U"😳", + U"😴", + U"😵", + U"😵‍💫", + U"😶", + U"😶‍🌫", + U"😶‍🌫️", + U"😷", + U"😸", + U"😹", + U"🤰", + U"🤰🏻", + U"🤰🏼", + U"🤰🏽", + U"🤰🏾", + U"🤰🏿", + U"🤱", + U"🤱🏻", + U"🤱🏼", + U"🤱🏽", + U"🤱🏾", + U"🤱🏿", + U"🤲", + U"🤲🏻", + U"🤲🏼", + U"🤲🏽", + U"🤲🏾", + U"🤲🏿", + U"🤳", + U"🤳🏻", + U"🤳🏼", + U"🤳🏽", + U"🤳🏾", + U"🤳🏿", + U"🤴", + U"🤴🏻", + U"🤴🏼", + U"🤴🏽", + U"🤴🏾", + U"🤴🏿", + U"🤵", + U"🤵‍♀", + U"🤵‍♀️", + U"🤵‍♂", + U"🤵‍♂️", + U"🤵🏻", + U"🤵🏻‍♀", + U"🤵🏻‍♀️", + U"🤵🏻‍♂", + U"🤵🏻‍♂️", + U"🤵🏼", + U"🤵🏼‍♀", + U"🤵🏼‍♀️", + U"🤵🏼‍♂", + U"🤵🏼‍♂️", + U"🤵🏽", + U"🤵🏽‍♀", + U"🤵🏽‍♀️", + U"🤵🏽‍♂", + U"🤵🏽‍♂️", + U"🤵🏾", + U"🤵🏾‍♀", + U"🤵🏾‍♀️", + U"🤵🏾‍♂", + U"🤵🏾‍♂️", + U"🤵🏿", + U"🤵🏿‍♀", + U"🤵🏿‍♀️", + U"🤵🏿‍♂", + U"🤵🏿‍♂️", + U"🤶", + U"🤶🏻", + U"🤶🏼", + U"🤶🏽", + U"🤶🏾", + U"🤶🏿", + U"🤷", + U"🤷‍♀", + U"🤷‍♀️", + U"🤷‍♂", + U"🤷‍♂️", + U"🤷🏻", + U"🤷🏻‍♀", + U"🤷🏻‍♀️", + U"🤷🏻‍♂", + U"🤷🏻‍♂️", + U"🤷🏼", + U"🤷🏼‍♀", + U"🤷🏼‍♀️", + U"🤷🏼‍♂", + U"🤷🏼‍♂️", + U"🤷🏽", + U"🤷🏽‍♀", + U"🤷🏽‍♀️", + U"🤷🏽‍♂", + U"🤷🏽‍♂️", + U"🤷🏾", + U"🤷🏾‍♀", + U"🤷🏾‍♀️", + U"🤷🏾‍♂", + U"🤷🏾‍♂️", + U"🤷🏿", + U"🤷🏿‍♀", + U"🤷🏿‍♀️", + U"🤷🏿‍♂", + U"🤷🏿‍♂️", + U"🤸", + U"🤸‍♀", + U"🤸‍♀️", + U"🤸‍♂", + U"🤸‍♂️", + U"🤸🏻", + U"🤸🏻‍♀", + U"🤸🏻‍♀️", + U"🤸🏻‍♂", + U"🤸🏻‍♂️", + U"🤸🏼", + U"🤸🏼‍♀", + U"🤸🏼‍♀️", + U"🤸🏼‍♂", + U"🤸🏼‍♂️", + U"🤸🏽", + U"🤸🏽‍♀", + U"🤸🏽‍♀️", + U"🤸🏽‍♂", + U"🤸🏽‍♂️", + U"🤸🏾", + U"🤸🏾‍♀", + U"🤸🏾‍♀️", + U"🤸🏾‍♂", + U"🤸🏾‍♂️", + U"🤸🏿", + U"🤸🏿‍♀", + U"🤸🏿‍♀️", + U"🤸🏿‍♂", + U"🤸🏿‍♂️", + U"🤹", + U"🤹‍♀", + U"🤹‍♀️", + U"🤹‍♂", + U"🤹‍♂️", + U"🤹🏻", + U"🤹🏻‍♀", + U"🤹🏻‍♀️", + U"🤹🏻‍♂", + U"🤹🏻‍♂️", + U"🤹🏼", + U"🤹🏼‍♀", + U"🤹🏼‍♀️", + U"🤹🏼‍♂", + U"🤹🏼‍♂️", + U"🤹🏽", + U"🤹🏽‍♀", + U"🤹🏽‍♀️", + U"🤹🏽‍♂", + U"🤹🏽‍♂️", + U"🤹🏾", + U"🤹🏾‍♀", + U"🤹🏾‍♀️", + U"🤹🏾‍♂", + U"🤹🏾‍♂️", + U"🤹🏿", + U"🤹🏿‍♀", + U"🤹🏿‍♀️", + U"🤹🏿‍♂", + U"🤹🏿‍♂️", + }; + bool failed = false; + auto array_size = sizeof(emojis) / sizeof(emojis[0]); + for (size_t i = 0; i < array_size; i++) { + auto e = emojis[i]; + int foo; + auto answer = fast_float::from_chars(e.data(), e.data() + e.size(), foo); + if (answer.ec == std::errc()) { + failed = true; + std::cerr << "Incorrectly parsed emoji #" << i << " as integer " << foo + << "." << std::endl; + } + } + + if (failed) { + return EXIT_FAILURE; + } + } return EXIT_SUCCESS; } @@ -842,4 +1111,4 @@ int main() { std::cerr << "The test requires C++17." << std::endl; return EXIT_SUCCESS; } -#endif \ No newline at end of file +#endif From fb384a4205d3d7d18b5bbf58b0835c5d7531aac9 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Wed, 17 Sep 2025 23:09:25 -0600 Subject: [PATCH 12/17] Add Blender to the list of projects using fast_float --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cf0d9f3..f828673 100644 --- a/README.md +++ b/README.md @@ -385,6 +385,8 @@ The fast_float library is part of: * GCC (as of version 12): the `from_chars` function in GCC relies on fast_float, * [Chromium](https://github.com/Chromium/Chromium), the engine behind Google Chrome, Microsoft Edge, and Opera, +* Boost JSON, MySQL, etc. +* Blender * [WebKit](https://github.com/WebKit/WebKit), the engine behind Safari (Apple's web browser), * [DuckDB](https://duckdb.org), From 48fc5404d4184111c0c771f4c1e841f652a532fd Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Thu, 18 Sep 2025 07:44:05 -0600 Subject: [PATCH 13/17] compatibility fix --- include/fast_float/float_common.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 2b8a528..e14c7dc 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -1132,13 +1132,10 @@ template constexpr uint64_t int_luts::min_safe_u64[]; template fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) { + // wchar_t and char can be signed, so we need to be careful. using UnsignedUC = typename std::make_unsigned::type; - auto uc = static_cast(c); - // For types larger than one byte, we need to force an index with sentinel - // value (using index zero because that is easiest) if any byte other than - // the low byte is non-zero. - auto mask = static_cast(-((uc & ~0xFFull) == 0)); - return int_luts<>::chdigit[static_cast(uc & mask)]; + return int_luts<>::chdigit[static_cast(static_cast(c) + & static_cast(-((static_cast(c) & ~0xFFull) == 0)))]; } fastfloat_really_inline constexpr size_t max_digits_u64(int base) { From bb956b29db5a0fe407a5c9c1fe2f8013be673d69 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Thu, 18 Sep 2025 07:44:53 -0600 Subject: [PATCH 14/17] release candidate 8.0.3 --- CMakeLists.txt | 2 +- README.md | 6 +++--- include/fast_float/float_common.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a86dfc2..2b979a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14) -project(fast_float VERSION 8.0.2 LANGUAGES CXX) +project(fast_float VERSION 8.0.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) diff --git a/README.md b/README.md index f828673..b9ee814 100644 --- a/README.md +++ b/README.md @@ -489,7 +489,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.0.2 + GIT_TAG tags/v8.0.3 GIT_SHALLOW TRUE) FetchContent_MakeAvailable(fast_float) @@ -505,7 +505,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.0.2) + GIT_TAG v8.0.3) ``` ## Using as single header @@ -517,7 +517,7 @@ if desired as described in the command line help. You may directly download automatically generated single-header files: - + ## Benchmarking diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index e14c7dc..1db1695 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -17,7 +17,7 @@ #define FASTFLOAT_VERSION_MAJOR 8 #define FASTFLOAT_VERSION_MINOR 0 -#define FASTFLOAT_VERSION_PATCH 2 +#define FASTFLOAT_VERSION_PATCH 3 #define FASTFLOAT_STRINGIZE_IMPL(x) #x #define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x) @@ -1134,7 +1134,7 @@ template fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) { // wchar_t and char can be signed, so we need to be careful. using UnsignedUC = typename std::make_unsigned::type; - return int_luts<>::chdigit[static_cast(static_cast(c) + return int_luts<>::chdigit[static_cast(static_cast(c) & static_cast(-((static_cast(c) & ~0xFFull) == 0)))]; } From 7a772275214a4884050e0ded965fc976dc10e503 Mon Sep 17 00:00:00 2001 From: Pavel Novikov Date: Thu, 18 Sep 2025 17:02:24 +0300 Subject: [PATCH 15/17] minor fix of forward declaration --- include/fast_float/fast_float.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/fast_float/fast_float.h b/include/fast_float/fast_float.h index e316f70..a190d7c 100644 --- a/include/fast_float/fast_float.h +++ b/include/fast_float/fast_float.h @@ -60,10 +60,8 @@ from_chars_advanced(UC const *first, UC const *last, T &value, */ FASTFLOAT_CONSTEXPR20 inline double integer_times_pow10(uint64_t mantissa, int decimal_exponent) noexcept; -FASTFLOAT_CONSTEXPR20 inline - typename std::enable_if::value, - double>::type - integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept; +FASTFLOAT_CONSTEXPR20 inline double +integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept; /** * from_chars for integer types. From 0b6d91122005a53d06f21aa95adb944033fb7cd6 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Thu, 18 Sep 2025 08:30:28 -0600 Subject: [PATCH 16/17] format --- include/fast_float/float_common.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 1db1695..fcb87d1 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -1134,8 +1134,10 @@ template fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) { // wchar_t and char can be signed, so we need to be careful. using UnsignedUC = typename std::make_unsigned::type; - return int_luts<>::chdigit[static_cast(static_cast(c) - & static_cast(-((static_cast(c) & ~0xFFull) == 0)))]; + return int_luts<>::chdigit[static_cast( + static_cast(c) & + static_cast( + -((static_cast(c) & ~0xFFull) == 0)))]; } fastfloat_really_inline constexpr size_t max_digits_u64(int base) { From 88b1e5321c6918cbe091afd76728296290668403 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Thu, 18 Sep 2025 09:38:45 -0600 Subject: [PATCH 17/17] version 8.1.0 --- CMakeLists.txt | 2 +- README.md | 6 +++--- include/fast_float/float_common.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b979a1..645fce7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14) -project(fast_float VERSION 8.0.3 LANGUAGES CXX) +project(fast_float VERSION 8.1.0 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) diff --git a/README.md b/README.md index 6c66bb4..8fdddbc 100644 --- a/README.md +++ b/README.md @@ -516,7 +516,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.0.3 + GIT_TAG tags/v8.1.0 GIT_SHALLOW TRUE) FetchContent_MakeAvailable(fast_float) @@ -532,7 +532,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.0.3) + GIT_TAG v8.1.0) ``` ## Using as single header @@ -544,7 +544,7 @@ if desired as described in the command line help. You may directly download automatically generated single-header files: - + ## Benchmarking diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index fcb87d1..62d199c 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -16,8 +16,8 @@ #include "constexpr_feature_detect.h" #define FASTFLOAT_VERSION_MAJOR 8 -#define FASTFLOAT_VERSION_MINOR 0 -#define FASTFLOAT_VERSION_PATCH 3 +#define FASTFLOAT_VERSION_MINOR 1 +#define FASTFLOAT_VERSION_PATCH 0 #define FASTFLOAT_STRINGIZE_IMPL(x) #x #define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x)