From 4a71c111f25905da221de3d370e89f9dee877909 Mon Sep 17 00:00:00 2001 From: HedgehogInTheCPP Date: Wed, 19 Aug 2026 17:00:32 +0300 Subject: [PATCH] improve error messages when type is unsupported. cleanup of functions naming. logical improvements of the FASTFLOAT_ISNOT_CHECKED_BOUNDS when FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN isn't enabled. --- include/fast_float/ascii_number.h | 93 +++++++++++++-------------- include/fast_float/digit_comparison.h | 2 +- include/fast_float/fast_float.h | 2 + include/fast_float/float_common.h | 7 +- include/fast_float/parse_number.h | 12 ++-- 5 files changed, 57 insertions(+), 59 deletions(-) diff --git a/include/fast_float/ascii_number.h b/include/fast_float/ascii_number.h index acb654e..6c30be5 100644 --- a/include/fast_float/ascii_number.h +++ b/include/fast_float/ascii_number.h @@ -83,7 +83,7 @@ read_chars_to_unsigned(UC const *chars) noexcept { #if FASTFLOAT_X86_SIMD -fastfloat_really_inline uint64_t simd_read8_to_u64(__m128i const data) { +fastfloat_really_inline uint64_t simd_read8(__m128i const data) { // _mm_packus_epi16 is SSE2, converts 8×u16 → 8×u8 __m128i const packed = _mm_packus_epi16(data, data); @@ -102,25 +102,23 @@ fastfloat_really_inline uint64_t simd_read8_to_u64(__m128i const data) { #endif } -fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) { +fastfloat_really_inline uint64_t simd_read8(char16_t const *chars) { FASTFLOAT_SIMD_DISABLE_WARNINGS // unaligned SIMD instruction -> all fine. - return simd_read8_to_u64( - _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars))); //-V1032 + return simd_read8(_mm_loadu_si128(reinterpret_cast<__m128i const *>(chars))); FASTFLOAT_SIMD_RESTORE_WARNINGS } #elif defined(FASTFLOAT_ARM_NEON) -fastfloat_really_inline uint64_t simd_read8_to_u64(uint16x8_t const &data) { +fastfloat_really_inline uint64_t simd_read8(uint16x8_t const &data) { uint8x8_t utf8_packed = vmovn_u16(data); return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0); } -fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) { +fastfloat_really_inline uint64_t simd_read8(char16_t const *chars) { FASTFLOAT_SIMD_DISABLE_WARNINGS - return simd_read8_to_u64( - vld1q_u16(reinterpret_cast(chars))); + return simd_read8(vld1q_u16(reinterpret_cast(chars))); FASTFLOAT_SIMD_RESTORE_WARNINGS } @@ -135,13 +133,13 @@ template template ()) = 0> #endif // dummy for compile -uint64_t simd_read8_to_u64(UC const *) { +uint64_t simd_read8(UC const *) { return 0; } // credit @aqrit fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t -parse_eight_digits_unrolled(uint64_t val) noexcept { +parse_8_digits(uint64_t val) noexcept { uint64_t const mask = 0x000000FF000000FF; uint64_t const mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) uint64_t const mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) @@ -154,28 +152,28 @@ parse_eight_digits_unrolled(uint64_t val) noexcept { // Call this if chars are definitely 8 digits. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t -parse_eight_digits_unrolled(UC const *chars) noexcept { +parse_8_digits(UC const *chars) noexcept { if (is_constant_evaluated() || !has_simd_opt()) { - return parse_eight_digits_unrolled( + return parse_8_digits( read_chars_to_unsigned(chars)); // truncation okay } - return parse_eight_digits_unrolled(simd_read8_to_u64(chars)); + return parse_8_digits(simd_read8(chars)); } // credit @aqrit fastfloat_really_inline constexpr bool -is_made_of_eight_digits_fast(uint64_t val) noexcept { +is_made_of_8_digits(uint64_t val) noexcept { return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & 0x8080808080808080)); } fastfloat_really_inline constexpr bool -is_made_of_four_digits_fast(uint32_t val) noexcept { +is_made_of_4_digits(uint32_t val) noexcept { return !((((val + 0x46464646) | (val - 0x30303030)) & 0x80808080)); } fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t -parse_four_digits_unrolled(uint32_t val) noexcept { +parse_4_digits(uint32_t val) noexcept { val -= 0x30303030; val = (val * 10) + (val >> 8); return (((val & 0x00FF00FF) * 0x00640001) >> 16) & 0xFFFF; @@ -187,7 +185,7 @@ parse_four_digits_unrolled(uint32_t val) noexcept { #if FASTFLOAT_X86_SIMD >= 31 // credit @hedgehoginthecpp -fastfloat_really_inline __m128i x86_parse_4x4_digits(__m128i data) noexcept { +fastfloat_really_inline __m128i parse_4x4_digits(__m128i data) noexcept { // 1. convert from ASCII '0' .. '9' to numbers 0 .. 9 const __m128i ascii0 = _mm_set1_epi8('0'); const __m128i t0 = _mm_subs_epu8(data, ascii0); @@ -205,7 +203,7 @@ fastfloat_really_inline __m128i x86_parse_4x4_digits(__m128i data) noexcept { // credit @hedgehoginthecpp fastfloat_really_inline uint64_t -convert_4x4_to_16digits(__m128i data) noexcept { +convert_4x4_to_16_digits(__m128i data) noexcept { // 4. convert to 16-digit number // v[0] * 10^12 + v[1] * 10^8 + v[2] * 10^4 + v[3] const uint64_t lo = static_cast(_mm_cvtsi128_si32(data)); @@ -222,8 +220,8 @@ convert_4x4_to_16digits(__m128i data) noexcept { #if FASTFLOAT_X86_SIMD >= 42 // credit @hedgehoginthecpp -fastfloat_really_inline bool x86_parse_if_16_digits(char const *chars, - uint64_t &value) noexcept { +fastfloat_really_inline bool parse_if_16_digits(char const *chars, + uint64_t &value) noexcept { FASTFLOAT_SIMD_DISABLE_WARNINGS const __m128i data = _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars)); @@ -254,7 +252,7 @@ fastfloat_really_inline bool x86_parse_if_16_digits(char const *chars, return false; value = value * 10000000000000000ULL + - convert_4x4_to_16digits(x86_parse_4x4_digits(data)); + convert_4x4_to_16_digits(parse_4x4_digits(data)); return true; } @@ -262,9 +260,9 @@ fastfloat_really_inline bool x86_parse_if_16_digits(char const *chars, #if FASTFLOAT_X86_SIMD >= 31 // credit @hedgehoginthecpp -fastfloat_really_inline uint64_t x86_parse_16_digits(char const *p) noexcept { +fastfloat_really_inline uint64_t parse_16_digits(char const *p) noexcept { const __m128i data = _mm_loadu_si128(reinterpret_cast(p)); - return convert_4x4_to_16digits(x86_parse_4x4_digits(data)); + return convert_4x4_to_16_digits(parse_4x4_digits(data)); } #endif @@ -278,7 +276,7 @@ parse_digits_until_19(char const *&p, char const *pend, am_mant_t &mantissa) { * 16-digit block cannot exceed 10^18 - 1. */ while (std::distance(p, pend) >= 16 && mantissa < 100) { - auto const value = x86_parse_16_digits(p); + auto const value = parse_16_digits(p); mantissa = mantissa * 10000000000000000ULL + value; p += 16; } @@ -287,13 +285,14 @@ parse_digits_until_19(char const *&p, char const *pend, am_mant_t &mantissa) { // An 8-digit block is safe while: mantissa < 10^10 because the result is // guaranteed < 10^18. while (std::distance(p, pend) >= 8 && mantissa < 10000000000ULL) { - auto const value = parse_eight_digits_unrolled(p); + auto const value = parse_8_digits(p); mantissa = mantissa * 100000000ULL + value; p += 8; } + // ????? while (std::distance(p, pend) >= 4 && mantissa < 10000000000ULL) { auto const value = read_chars_to_unsigned(p); - mantissa = mantissa * 10000 + parse_four_digits_unrolled(value); + mantissa = mantissa * 10000 + parse_4_digits(value); p += 4; } // Finalizer @@ -323,11 +322,10 @@ parse_digits_until_19(UC const *&p, UC const *pend, #endif // Call this if chars might not be 8 digits. -// Using this style (instead of is_made_of_eight_digits_fast() then -// parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice. +// Using this style (instead of is_made_of_8_digits() then +// parse_8_digits()) ensures we don't load SIMD registers twice. fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool -simd_parse_if_eight_digits_unrolled(char16_t const *chars, - uint64_t &i) noexcept { +simd_parse_if_8_digits(char16_t const *chars, uint64_t &i) noexcept { if (is_constant_evaluated()) { return false; } @@ -336,7 +334,7 @@ simd_parse_if_eight_digits_unrolled(char16_t const *chars, // Load 8 UTF-16 characters (16 bytes) // unaligned SIMD instruction -> all fine. __m128i const data = - _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars)); //-V1032 + _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars)); FASTFLOAT_SIMD_RESTORE_WARNINGS // Branchless "are all digits?" trick from Lemire: @@ -348,7 +346,7 @@ simd_parse_if_eight_digits_unrolled(char16_t const *chars, // If mask == 0 → all digits valid. if (_mm_movemask_epi8(mask) == 0) { - i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + i = i * 100000000 + parse_8_digits(simd_read8(data)); return true; } #elif FASTFLOAT_ARM_NEON @@ -362,7 +360,7 @@ simd_parse_if_eight_digits_unrolled(char16_t const *chars, uint16x8_t const mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1)); if (vminvq_u16(mask) == 0xFFFF) { - i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + i = i * 100000000 + parse_8_digits(simd_read8(data)); return true; } #else @@ -388,9 +386,9 @@ template #else template ()) = 0> #endif -// dummy for compile -bool simd_parse_if_eight_digits_unrolled(UC const *, uint64_t &) { - return 0; +// dummy for compiler +bool simd_parse_if_8_digits_unrolled(UC const *, uint64_t &) { + return false; } template ::value) = 0> @@ -398,10 +396,9 @@ fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void loop_parse_if_digits(UC const *&p, UC const *const pend, uint64_t &i) noexcept { if (!is_constant_evaluated()) { if FASTFLOAT_CONSTEXPR17 (has_simd_opt()) { - while (std::distance(p, pend) >= 8 /*sizeof(uint64_t)*/ && - simd_parse_if_eight_digits_unrolled( - p, i)) { // may overflow, that's ok - p += 8 /*sizeof(uint64_t)*/; + while (std::distance(p, pend) >= 8 && + simd_parse_if_8_digits_unrolled(p, i)) { // may overflow, that's ok + p += 8; } } } @@ -423,7 +420,7 @@ loop_parse_if_digits(char const *&p, char const *const pend, * This is only used when a complete 16-byte block exists, * so the load is always inside [p, pend). */ - while (std::distance(p, pend) >= 16 && x86_parse_if_16_digits(p, i)) { + while (std::distance(p, pend) >= 16 && parse_if_16_digits(p, i)) { p += 16; } } @@ -431,9 +428,8 @@ loop_parse_if_digits(char const *&p, char const *const pend, // optimizes better than parse_if_eight_digits_unrolled() for UC = char. while (std::distance(p, pend) >= 8 /*sizeof(uint64_t)*/) { auto const val = read_chars_to_unsigned(p); - if (is_made_of_eight_digits_fast(val)) { - i = i * 100000000 /*10 ^ sizeof(uint64_t)*/ + - parse_eight_digits_unrolled(val); // may overflow, that's ok + if (is_made_of_8_digits(val)) { + i = i * 100000000 + parse_8_digits(val); // may overflow, that's ok p += sizeof(uint64_t); } else { break; @@ -446,9 +442,8 @@ loop_parse_if_digits(char const *&p, char const *const pend, // with the leaner hot path the 4-digit step now wins on gcc as well. if (std::distance(p, pend) >= 4 /*sizeof(uint32_t)*/) { auto const val = read_chars_to_unsigned(p); - if (is_made_of_four_digits_fast(val)) { - i = i * 10000 /*10 ^ sizeof(uint32_t)*/ + - parse_four_digits_unrolled(val); // may overflow, that's ok + if (is_made_of_4_digits(val)) { + i = i * 10000 + parse_4_digits(val); // may overflow, that's ok p += sizeof(uint32_t); } } @@ -911,8 +906,8 @@ parse_int_string(UC const *p, UC const *pend, T &value, sizeof(UC) == 1) { if (len >= sizeof(uint32_t)) { auto const digits = read_chars_to_unsigned(p); - if (is_made_of_four_digits_fast(digits)) { - auto v = parse_four_digits_unrolled(digits); + if (is_made_of_4_digits(digits)) { + auto v = parse_4_digits(digits); if (len >= 5 && is_integer(p[4])) { v = v * 10 + static_cast(p[4] - '0'); if (len >= 6 && is_integer(p[5])) { diff --git a/include/fast_float/digit_comparison.h b/include/fast_float/digit_comparison.h index 8e7a348..7ac20da 100644 --- a/include/fast_float/digit_comparison.h +++ b/include/fast_float/digit_comparison.h @@ -222,7 +222,7 @@ template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void parse_eight_digits(UC const *&p, limb &value, am_digits &counter, am_digits &count) noexcept { - value = value * 100000000 + parse_eight_digits_unrolled(p); + value = value * 100000000 + parse_8_digits(p); p += 8; counter += 8; count += 8; diff --git a/include/fast_float/fast_float.h b/include/fast_float/fast_float.h index 9aac5e4..4d89b12 100644 --- a/include/fast_float/fast_float.h +++ b/include/fast_float/fast_float.h @@ -61,6 +61,7 @@ from_chars_advanced(UC const *first, UC const *last, T &value, FASTFLOAT_CONSTEXPR20 inline double integer_times_pow10(uint64_t const mantissa, int const decimal_exponent) noexcept; + FASTFLOAT_CONSTEXPR20 inline double integer_times_pow10(int64_t const mantissa, int const decimal_exponent) noexcept; @@ -75,6 +76,7 @@ FASTFLOAT_CONSTEXPR20 typename std::enable_if::value, T>::type integer_times_pow10(uint64_t const mantissa, int const decimal_exponent) noexcept; + template FASTFLOAT_CONSTEXPR20 typename std::enable_if::value, T>::type diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index eb42b4c..14efd3a 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -47,10 +47,9 @@ typedef uint_fast8_t limb_t; // Size of bits in the mantissa and path and rounding shifts typedef int_fast8_t am_bits_t; -// 16 bit signed integer is used for power to cover all double exponents. +// 16 bit signed integer is used for power to cover all exponents up to double. typedef int_fast16_t am_pow_t; -// Power bias is signed for handling a denormal float -// or an invalid mantissa. +// Power bias is signed for handling a denormal float or an invalid mantissa. // Bias so we can get the real exponent with an invalid adjusted_mantissa. constexpr static am_pow_t invalid_am_bias = std::numeric_limits::min() + 1; @@ -269,6 +268,7 @@ static_assert(FASTFLOAT_X86_SIMD == 20 || FASTFLOAT_X86_SIMD == 42 || #define fastfloat_unlikely(x) (x) #endif +// clang-format off #ifndef FASTFLOAT_ASSERT #define FASTFLOAT_ASSERT(x) \ { ((void)(x)); } @@ -278,6 +278,7 @@ static_assert(FASTFLOAT_X86_SIMD == 20 || FASTFLOAT_X86_SIMD == 42 || #define FASTFLOAT_DEBUG_ASSERT(x) \ { ((void)(x)); } #endif +// clang-format on // rust style `try!()` macro, or `?` operator #define FASTFLOAT_TRY(x) \ diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index 67024ba..fcafc4a 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -278,7 +278,7 @@ template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_advanced(parsed_number_string_t const &pns, T &value) noexcept { static_assert(is_supported_float_type::value, - "only some floating-point types are supported"); + "this type of floating-point type isn't supported"); static_assert(is_supported_char_type::value, "only char, wchar_t, char16_t and char32_t are supported"); @@ -349,7 +349,7 @@ fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_float_advanced(UC const *first, UC const *last, T &value, parse_options_t const options) noexcept { static_assert(is_supported_float_type::value, - "only some floating-point types are supported"); + "this type of floating-point type isn't supported"); static_assert(is_supported_char_type::value, "only char, wchar_t, char16_t and char32_t are supported"); @@ -590,6 +590,10 @@ from_chars_int_advanced(UC const *first, UC const *last, T &value, static_assert(is_supported_char_type::value, "only char, wchar_t, char16_t and char32_t are supported"); +#ifdef FASTFLOAT_ISNOT_CHECKED_BOUNDS + // We are in parser code with external loop that checks bounds. + FASTFLOAT_ASSUME(first < last); +#endif #ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN if (chars_format_t(options.format & chars_format::skip_white_space)) { while ((first != last) && fast_float::is_space(*first)) { @@ -597,10 +601,6 @@ from_chars_int_advanced(UC const *first, UC const *last, T &value, } } #else -#ifdef FASTFLOAT_ISNOT_CHECKED_BOUNDS - // We are in parser code with external loop that checks bounds. - FASTFLOAT_ASSUME(first < last); -#endif #endif if ( #ifndef FASTFLOAT_ISNOT_CHECKED_BOUNDS