From 265cb849f3ef03028f80f6d058fc400bca41e1d1 Mon Sep 17 00:00:00 2001 From: sleepingieght Date: Tue, 30 Dec 2025 01:15:22 +0530 Subject: [PATCH 01/17] optimise fastfloat_strncasecmp --- include/fast_float/float_common.h | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 88efe85f..dc0c4173 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -2,6 +2,7 @@ #define FASTFLOAT_FLOAT_COMMON_H #include +#include #include #include #include @@ -272,9 +273,27 @@ template inline FASTFLOAT_CONSTEXPR14 bool fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, size_t length) { - for (size_t i = 0; i < length; ++i) { - UC const actual = actual_mixedcase[i]; - if ((actual < 256 ? actual | 32 : actual) != expected_lowercase[i]) { + uint64_t mask{0}; + FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) { mask = 0x2020202020202020; } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 2) { + mask = 0x0020002000200020; + } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) { + mask = 0x0000002000000020; + } + else { + return false; + } + uint64_t val1{0}, val2{0}; + size_t sz{8 / (sizeof(UC))}; + for (size_t i = 0; i < length; i += sz) { + val1 = val2 = 0; + sz = std::min(sz, length - i); + ::memcpy(&val1, actual_mixedcase + i, sz * sizeof(UC)); + ::memcpy(&val2, expected_lowercase + i, sz * sizeof(UC)); + val1 |= mask; + val2 |= mask; + if (val1 != val2) { return false; } } From 4eb0d806fa9774849159501bcad627492a93b1f9 Mon Sep 17 00:00:00 2001 From: sleepingieght Date: Tue, 30 Dec 2025 20:27:45 +0530 Subject: [PATCH 02/17] add specialisations --- include/fast_float/float_common.h | 139 +++++++++++++++++++++++++++--- include/fast_float/parse_number.h | 6 +- 2 files changed, 130 insertions(+), 15 deletions(-) diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index dc0c4173..6c1e812c 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -268,11 +268,10 @@ struct is_supported_char_type > { }; -// Compares two ASCII strings in a case insensitive manner. template inline FASTFLOAT_CONSTEXPR14 bool -fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, - size_t length) { +fastfloat_strncasecmp3(UC const *actual_mixedcase, + UC const *expected_lowercase) { uint64_t mask{0}; FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) { mask = 0x2020202020202020; } else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 2) { @@ -284,22 +283,138 @@ fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, else { return false; } + + uint64_t val1{0}, val2{0}; + if (cpp20_and_in_constexpr()) { + for (size_t i = 0; i < 3; i++) { + if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) { + return false; + } + return true; + } + } else { + FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1 || sizeof(UC) == 2) { + ::memcpy(&val1, actual_mixedcase, 3 * sizeof(UC)); + ::memcpy(&val2, expected_lowercase, 3 * sizeof(UC)); + val1 |= mask; + val2 |= mask; + return val1 == val2; + } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) { + ::memcpy(&val1, actual_mixedcase, 2 * sizeof(UC)); + ::memcpy(&val2, expected_lowercase, 2 * sizeof(UC)); + val1 |= mask; + if (val1 != val2) { + return false; + } + return (actual_mixedcase[2] | 32) == (expected_lowercase[2]); + } + else { + return false; + } + } + + return true; +} + +template +inline FASTFLOAT_CONSTEXPR14 bool +fastfloat_strncasecmp5(UC const *actual_mixedcase, + UC const *expected_lowercase) { + uint64_t mask{0}; uint64_t val1{0}, val2{0}; - size_t sz{8 / (sizeof(UC))}; - for (size_t i = 0; i < length; i += sz) { - val1 = val2 = 0; - sz = std::min(sz, length - i); - ::memcpy(&val1, actual_mixedcase + i, sz * sizeof(UC)); - ::memcpy(&val2, expected_lowercase + i, sz * sizeof(UC)); - val1 |= mask; - val2 |= mask; - if (val1 != val2) { + if (cpp20_and_in_constexpr()) { + for (size_t i = 0; i < 5; i++) { + if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) { + return false; + } + return true; + } + } else { + FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) { + mask = 0x2020202020202020; + ::memcpy(&val1, actual_mixedcase, 5 * sizeof(UC)); + ::memcpy(&val2, expected_lowercase, 5 * sizeof(UC)); + val1 |= mask; + val2 |= mask; + return val1 == val2; + } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 2) { + mask = 0x0020002000200020; + ::memcpy(&val1, actual_mixedcase, 4 * sizeof(UC)); + ::memcpy(&val2, expected_lowercase, 4 * sizeof(UC)); + val1 |= mask; + if (val1 != val2) { + return false; + } + return (actual_mixedcase[4] | 32) == (expected_lowercase[4]); + } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) { + mask = 0x0000002000000020; + ::memcpy(&val1, actual_mixedcase, 2 * sizeof(UC)); + ::memcpy(&val2, expected_lowercase, 2 * sizeof(UC)); + val1 |= mask; + if (val1 != val2) { + return false; + } + ::memcpy(&val1, actual_mixedcase + 2, 2 * sizeof(UC)); + ::memcpy(&val2, expected_lowercase + 2, 2 * sizeof(UC)); + val1 |= mask; + if (val1 != val2) { + return false; + } + return (actual_mixedcase[4] | 32) == (expected_lowercase[4]); + } + else { return false; } } + return true; } +// Compares two ASCII strings in a case insensitive manner. +template +inline FASTFLOAT_CONSTEXPR14 bool +fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, + size_t length) { + uint64_t mask{0}; + FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) { mask = 0x2020202020202020; } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 2) { + mask = 0x0020002000200020; + } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) { + mask = 0x0000002000000020; + } + else { + return false; + } + + if (cpp20_and_in_constexpr()) { + for (size_t i = 0; i < length; i++) { + if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) { + return false; + } + return true; + } + } else { + uint64_t val1{0}, val2{0}; + size_t sz{8 / (sizeof(UC))}; + for (size_t i = 0; i < length; i += sz) { + val1 = val2 = 0; + sz = std::min(sz, length - i); + ::memcpy(&val1, actual_mixedcase + i, sz * sizeof(UC)); + ::memcpy(&val2, expected_lowercase + i, sz * sizeof(UC)); + val1 |= mask; + val2 |= mask; + if (val1 != val2) { + return false; + } + } + return true; + } +} + #ifndef FLT_EVAL_METHOD #error "FLT_EVAL_METHOD should be defined, please include cfloat." #endif diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index d453c145..c01bb15a 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -35,7 +35,7 @@ from_chars_result_t ++first; } if (last - first >= 3) { - if (fastfloat_strncasecmp(first, str_const_nan(), 3)) { + if (fastfloat_strncasecmp3(first, str_const_nan())) { answer.ptr = (first += 3); value = minusSign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); @@ -54,9 +54,9 @@ from_chars_result_t } return answer; } - if (fastfloat_strncasecmp(first, str_const_inf(), 3)) { + if (fastfloat_strncasecmp3(first, str_const_inf())) { if ((last - first >= 8) && - fastfloat_strncasecmp(first + 3, str_const_inf() + 3, 5)) { + fastfloat_strncasecmp5(first + 3, str_const_inf() + 3)) { answer.ptr = first + 8; } else { answer.ptr = first + 3; From 4dc522579746154de535f3866ff1c228332aa448 Mon Sep 17 00:00:00 2001 From: Shikhar Date: Wed, 31 Dec 2025 22:07:45 +0530 Subject: [PATCH 03/17] add base check for uint8 parsing Signed-off-by: Shikhar --- include/fast_float/ascii_number.h | 138 +++++++++++++++--------------- 1 file changed, 70 insertions(+), 68 deletions(-) diff --git a/include/fast_float/ascii_number.h b/include/fast_float/ascii_number.h index aa2761aa..5609ba1a 100644 --- a/include/fast_float/ascii_number.h +++ b/include/fast_float/ascii_number.h @@ -515,93 +515,95 @@ parse_int_string(UC const *p, UC const *pend, T &value, UC const *const start_digits = p; FASTFLOAT_IF_CONSTEXPR17((std::is_same::value)) { - const size_t len = (size_t)(pend - p); - if (len == 0) { - if (has_leading_zeros) { - value = 0; - answer.ec = std::errc(); - answer.ptr = p; - } else { - answer.ec = std::errc::invalid_argument; - answer.ptr = first; + if (base == 10) { + const size_t len = (size_t)(pend - p); + if (len == 0) { + if (has_leading_zeros) { + value = 0; + answer.ec = std::errc(); + answer.ptr = p; + } else { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + } + return answer; } - return answer; - } - uint32_t digits; + uint32_t digits; #if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST - if (std::is_constant_evaluated()) { - uint8_t str[4]{}; - for (size_t j = 0; j < 4 && j < len; ++j) { - str[j] = static_cast(p[j]); - } - digits = std::bit_cast(str); + if (std::is_constant_evaluated()) { + uint8_t str[4]{}; + for (size_t j = 0; j < 4 && j < len; ++j) { + str[j] = static_cast(p[j]); + } + digits = std::bit_cast(str); #if FASTFLOAT_IS_BIG_ENDIAN - digits = byteswap_32(digits); + digits = byteswap_32(digits); #endif - } + } #else - if (false) { - } + if (false) { + } #endif - else if (len >= 4) { - ::memcpy(&digits, p, 4); + else if (len >= 4) { + ::memcpy(&digits, p, 4); #if FASTFLOAT_IS_BIG_ENDIAN - digits = byteswap_32(digits); + digits = byteswap_32(digits); #endif - } else { - uint32_t b0 = static_cast(p[0]); - uint32_t b1 = (len > 1) ? static_cast(p[1]) : 0xFFu; - uint32_t b2 = (len > 2) ? static_cast(p[2]) : 0xFFu; - uint32_t b3 = 0xFFu; - digits = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); - } + } else { + uint32_t b0 = static_cast(p[0]); + uint32_t b1 = (len > 1) ? static_cast(p[1]) : 0xFFu; + uint32_t b2 = (len > 2) ? static_cast(p[2]) : 0xFFu; + uint32_t b3 = 0xFFu; + digits = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); + } - uint32_t magic = - ((digits + 0x46464646u) | (digits - 0x30303030u)) & 0x80808080u; - uint32_t tz = (uint32_t)countr_zero_32(magic); // 7, 15, 23, 31, or 32 - uint32_t nd = (tz == 32) ? 4 : (tz >> 3); - nd = (uint32_t)std::min((size_t)nd, len); - if (nd == 0) { - if (has_leading_zeros) { - value = 0; - answer.ec = std::errc(); - answer.ptr = p; + uint32_t magic = + ((digits + 0x46464646u) | (digits - 0x30303030u)) & 0x80808080u; + uint32_t tz = (uint32_t)countr_zero_32(magic); // 7, 15, 23, 31, or 32 + uint32_t nd = (tz == 32) ? 4 : (tz >> 3); + nd = (uint32_t)std::min((size_t)nd, len); + if (nd == 0) { + if (has_leading_zeros) { + value = 0; + answer.ec = std::errc(); + answer.ptr = p; + return answer; + } + answer.ec = std::errc::invalid_argument; + answer.ptr = first; return answer; } - answer.ec = std::errc::invalid_argument; - answer.ptr = first; - return answer; - } - if (nd > 3) { - const UC *q = p + nd; - size_t rem = len - nd; - while (rem) { - if (*q < UC('0') || *q > UC('9')) - break; - ++q; - --rem; + if (nd > 3) { + const UC *q = p + nd; + size_t rem = len - nd; + while (rem) { + if (*q < UC('0') || *q > UC('9')) + break; + ++q; + --rem; + } + answer.ec = std::errc::result_out_of_range; + answer.ptr = q; + return answer; } - answer.ec = std::errc::result_out_of_range; - answer.ptr = q; - return answer; - } - digits ^= 0x30303030u; - digits <<= ((4 - nd) * 8); + digits ^= 0x30303030u; + digits <<= ((4 - nd) * 8); - uint32_t check = ((digits >> 24) & 0xff) | ((digits >> 8) & 0xff00) | - ((digits << 8) & 0xff0000); - if (check > 0x00020505) { - answer.ec = std::errc::result_out_of_range; + uint32_t check = ((digits >> 24) & 0xff) | ((digits >> 8) & 0xff00) | + ((digits << 8) & 0xff0000); + if (check > 0x00020505) { + answer.ec = std::errc::result_out_of_range; + answer.ptr = p + nd; + return answer; + } + value = (uint8_t)((0x640a01 * digits) >> 24); + answer.ec = std::errc(); answer.ptr = p + nd; return answer; } - value = (uint8_t)((0x640a01 * digits) >> 24); - answer.ec = std::errc(); - answer.ptr = p + nd; - return answer; } uint64_t i = 0; From 011763f31c2af78da22029f6e2aa6f910d2ec577 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Wed, 31 Dec 2025 13:46:53 -0500 Subject: [PATCH 04/17] adding tests --- .github/workflows/s390x.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/s390x.yml b/.github/workflows/s390x.yml index bcce4c6c..91918f35 100644 --- a/.github/workflows/s390x.yml +++ b/.github/workflows/s390x.yml @@ -24,7 +24,7 @@ jobs: apt-get update -q -y apt-get install -y cmake make g++ run: | - cmake -DCMAKE_BUILD_TYPE=Release -B build + cmake -DCMAKE_BUILD_TYPE=Release -B build -DFASTFLOAT_TEST=ON cmake --build build -j=2 ctest --output-on-failure --test-dir build From 6440936afb5c0bd8dd1e9c88d2f35b3e185a4865 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Thu, 1 Jan 2026 17:49:15 -0500 Subject: [PATCH 05/17] see if this fixes the issue with s390x. --- .github/workflows/s390x.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/s390x.yml b/.github/workflows/s390x.yml index 91918f35..0bc90d98 100644 --- a/.github/workflows/s390x.yml +++ b/.github/workflows/s390x.yml @@ -22,7 +22,7 @@ jobs: distro: ubuntu_latest install: | apt-get update -q -y - apt-get install -y cmake make g++ + apt-get install -y cmake make g++ git run: | cmake -DCMAKE_BUILD_TYPE=Release -B build -DFASTFLOAT_TEST=ON cmake --build build -j=2 From 36d3441dc03d1551d48aa424c55905b6c9e5c14d Mon Sep 17 00:00:00 2001 From: Shikhar Date: Thu, 1 Jan 2026 08:09:00 +0530 Subject: [PATCH 06/17] add bench_uint16 Signed-off-by: Shikhar --- benchmarks/CMakeLists.txt | 6 ++ benchmarks/bench_uint16.cpp | 139 ++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 benchmarks/bench_uint16.cpp diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 4ee57895..81ea92a6 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -11,7 +11,9 @@ FetchContent_MakeAvailable(counters) add_executable(realbenchmark benchmark.cpp) target_link_libraries(realbenchmark PRIVATE counters::counters) add_executable(bench_ip bench_ip.cpp) +add_executable(bench_uint16 bench_uint16.cpp) target_link_libraries(bench_ip PRIVATE counters::counters) +target_link_libraries(bench_uint16 PRIVATE counters::counters) set_property( TARGET realbenchmark @@ -19,8 +21,12 @@ set_property( set_property( TARGET bench_ip PROPERTY CXX_STANDARD 17) +set_property( + TARGET bench_uint16 + PROPERTY CXX_STANDARD 17) target_link_libraries(realbenchmark PUBLIC fast_float) target_link_libraries(bench_ip PUBLIC fast_float) +target_link_libraries(bench_uint16 PUBLIC fast_float) include(ExternalProject) diff --git a/benchmarks/bench_uint16.cpp b/benchmarks/bench_uint16.cpp new file mode 100644 index 00000000..c4cef81b --- /dev/null +++ b/benchmarks/bench_uint16.cpp @@ -0,0 +1,139 @@ +#include "counters/bench.h" +#include "fast_float/fast_float.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void pretty_print(size_t volume, size_t bytes, std::string name, + counters::event_aggregate agg) { + if (agg.inner_count > 1) { + printf("# (inner count: %d)\n", agg.inner_count); + } + printf("%-40s : ", name.c_str()); + printf(" %5.2f GB/s ", bytes / agg.fastest_elapsed_ns()); + printf(" %5.1f Mip/s ", volume * 1000.0 / agg.fastest_elapsed_ns()); + printf(" %5.2f ns/ip ", agg.fastest_elapsed_ns() / volume); + if (counters::event_collector().has_events()) { + printf(" %5.2f GHz ", agg.fastest_cycles() / agg.fastest_elapsed_ns()); + printf(" %5.2f c/ip ", agg.fastest_cycles() / volume); + printf(" %5.2f i/ip ", agg.fastest_instructions() / volume); + printf(" %5.2f c/b ", agg.fastest_cycles() / bytes); + printf(" %5.2f i/b ", agg.fastest_instructions() / bytes); + printf(" %5.2f i/c ", agg.fastest_instructions() / agg.fastest_cycles()); + } + printf("\n"); +} + +enum class parse_method { standard, fast_float }; + +void validate(const std::string &buffer, const std::vector &expected, + char delimiter) { + const char *p = buffer.data(); + const char *pend = p + buffer.size(); + + for (size_t i = 0; i < expected.size(); i++) { + uint16_t val; + auto r = fast_float::from_chars(p, pend, val); + if (r.ec != std::errc() || val != expected[i]) { + printf("Validation failed at index %zu: expected %u, got %u\n", i, + expected[i], val); + std::abort(); + } + p = r.ptr; + if (i + 1 < expected.size()) { + if (p >= pend || *p != delimiter) { + printf("Validation failed at index %zu: delimiter mismatch\n", i); + std::abort(); + } + ++p; + } + } + + if (p != pend) { + printf("Validation failed: trailing bytes remain\n"); + std::abort(); + } + printf("Validation passed!\n"); +} + +int main() { + constexpr size_t N = 500000; + constexpr char delimiter = ','; + std::mt19937 rng(1234); + std::uniform_int_distribution dist(0, 65535); + + std::vector expected; + expected.reserve(N); + + std::string buffer; + buffer.reserve(N * 6); // up to 5 digits + delimiter + + for (size_t i = 0; i < N; ++i) { + uint16_t val = (uint16_t)dist(rng); + expected.push_back(val); + std::string s = std::to_string(val); + buffer.append(s); + if (i + 1 < N) { + buffer.push_back(delimiter); + } + } + + size_t total_bytes = buffer.size(); + + validate(buffer, expected, delimiter); + + volatile uint64_t sink = 0; + + pretty_print(N, total_bytes, "parse_uint16_std_fromchars", + counters::bench([&]() { + uint64_t sum = 0; + const char *p = buffer.data(); + const char *pend = p + buffer.size(); + for (size_t i = 0; i < N; ++i) { + uint16_t value = 0; + auto r = std::from_chars(p, pend, value); + if (r.ec != std::errc()) + std::abort(); + sum += value; + p = r.ptr; + if (i + 1 < N) { + if (p >= pend || *p != delimiter) + std::abort(); + ++p; + } + } + if (p != pend) + std::abort(); + sink += sum; + })); + + pretty_print(N, total_bytes, "parse_uint16_fastfloat", counters::bench([&]() { + uint64_t sum = 0; + const char *p = buffer.data(); + const char *pend = p + buffer.size(); + for (size_t i = 0; i < N; ++i) { + uint16_t value = 0; + auto r = fast_float::from_chars(p, pend, value); + if (r.ec != std::errc()) + std::abort(); + sum += value; + p = r.ptr; + if (i + 1 < N) { + if (p >= pend || *p != delimiter) + std::abort(); + ++p; + } + } + if (p != pend) + std::abort(); + sink += sum; + })); + + return EXIT_SUCCESS; +} From d0af1cfdbd7dfa781f2e40c89bd6b85d886a538a Mon Sep 17 00:00:00 2001 From: Shikhar Date: Thu, 1 Jan 2026 08:29:14 +0530 Subject: [PATCH 07/17] optimize uint16 parsing Signed-off-by: Shikhar --- include/fast_float/ascii_number.h | 116 ++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/include/fast_float/ascii_number.h b/include/fast_float/ascii_number.h index 5609ba1a..b5826f6d 100644 --- a/include/fast_float/ascii_number.h +++ b/include/fast_float/ascii_number.h @@ -68,6 +68,26 @@ read8_to_u64(UC const *chars) { return val; } +// Read 4 UC into a u32. Truncates UC if not char. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t +read4_to_u32(UC const *chars) { + if (cpp20_and_in_constexpr() || !std::is_same::value) { + uint32_t val = 0; + for (int i = 0; i < 4; ++i) { + val |= uint32_t(uint8_t(*chars)) << (i * 8); + ++chars; + } + return val; + } + uint32_t val; + ::memcpy(&val, chars, sizeof(uint32_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + val = byteswap(val); +#endif + return val; +} + #ifdef FASTFLOAT_SSE2 fastfloat_really_inline uint64_t simd_read8_to_u64(__m128i const data) { @@ -149,6 +169,13 @@ is_made_of_eight_digits_fast(uint64_t val) noexcept { 0x8080808080808080)); } +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t +parse_four_digits_unrolled(uint32_t val) noexcept { + val -= 0x30303030; + val = (val * 10) + (val >> 8); + return (((val & 0x00FF00FF) * 0x00640001) >> 16) & 0xFFFF; +} + #ifdef FASTFLOAT_HAS_SIMD // Call this if chars might not be 8 digits. @@ -606,6 +633,95 @@ parse_int_string(UC const *p, UC const *pend, T &value, } } + FASTFLOAT_IF_CONSTEXPR17((std::is_same::value)) { + if (base == 10) { + const size_t len = size_t(pend - p); + if (len == 0) { + if (has_leading_zeros) { + value = 0; + answer.ec = std::errc(); + answer.ptr = p; + } else { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + } + return answer; + } + + uint32_t digits; + if (len >= 4) { + digits = read4_to_u32(p); + } else { + uint32_t b0 = uint32_t(uint8_t(p[0])); + uint32_t b1 = (len > 1) ? uint32_t(uint8_t(p[1])) : 0xFFu; + uint32_t b2 = (len > 2) ? uint32_t(uint8_t(p[2])) : 0xFFu; + digits = b0 | (b1 << 8) | (b2 << 16) | (0xFFu << 24); + } + + uint32_t magic = + ((digits + 0x46464646u) | (digits - 0x30303030u)) & 0x80808080u; + uint32_t nd = (magic == 0) ? 4u : (uint32_t(countr_zero_32(magic)) >> 3); + + if (nd == 0) { + if (has_leading_zeros) { + value = 0; + answer.ec = std::errc(); + answer.ptr = p; + return answer; + } + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + + if (nd < 4) { + // mask out non-digit bytes and replace with '0' (0x30) + uint32_t mask = 0xFFFFFFFFu >> ((4u - nd) * 8u); + uint32_t padded = (digits & mask) | (~mask & 0x30303030u); + uint32_t v = parse_four_digits_unrolled(padded); + static constexpr uint32_t divs[] = {0, 1000, 100, 10}; + value = (uint16_t)(v / divs[nd]); + answer.ec = std::errc(); + answer.ptr = p + nd; + return answer; + } + + uint32_t v = parse_four_digits_unrolled(digits); + + uint32_t d4 = (len > 4) ? uint32_t(p[4] - '0') : 10u; + if (d4 > 9u) { + value = (uint16_t)v; + answer.ec = std::errc(); + answer.ptr = p + 4; + return answer; + } + + if (len > 5) { + uint32_t d5 = uint32_t(p[5]) - uint32_t('0'); + if (d5 <= 9u) { + const UC *q = p + 6; + while (q < pend && uint32_t(*q) - uint32_t('0') <= 9u) + ++q; + answer.ec = std::errc::result_out_of_range; + answer.ptr = q; + return answer; + } + } + + // overflow check + if (v > 6553u || (v == 6553u && d4 > 5u)) { + answer.ec = std::errc::result_out_of_range; + answer.ptr = p + 5; + return answer; + } + + value = (uint16_t)(v * 10u + d4); + answer.ec = std::errc(); + answer.ptr = p + 5; + return answer; + } + } + uint64_t i = 0; if (base == 10) { loop_parse_if_eight_digits(p, pend, i); // use SIMD if possible From 13d4b9418364ab8c68b9f235065c700f88b3719a Mon Sep 17 00:00:00 2001 From: Shikhar Date: Thu, 1 Jan 2026 17:42:30 +0530 Subject: [PATCH 08/17] small fix --- include/fast_float/ascii_number.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fast_float/ascii_number.h b/include/fast_float/ascii_number.h index b5826f6d..7422e74f 100644 --- a/include/fast_float/ascii_number.h +++ b/include/fast_float/ascii_number.h @@ -679,7 +679,7 @@ parse_int_string(UC const *p, UC const *pend, T &value, uint32_t mask = 0xFFFFFFFFu >> ((4u - nd) * 8u); uint32_t padded = (digits & mask) | (~mask & 0x30303030u); uint32_t v = parse_four_digits_unrolled(padded); - static constexpr uint32_t divs[] = {0, 1000, 100, 10}; + constexpr uint32_t divs[] = {0, 1000, 100, 10}; value = (uint16_t)(v / divs[nd]); answer.ec = std::errc(); answer.ptr = p + nd; From b14e6a466aba8813a9e8da2defb0671fa2db3410 Mon Sep 17 00:00:00 2001 From: Shikhar Date: Fri, 2 Jan 2026 02:45:11 +0530 Subject: [PATCH 09/17] simpler optimizations Signed-off-by: Shikhar --- include/fast_float/ascii_number.h | 103 ++++++++++-------------------- 1 file changed, 34 insertions(+), 69 deletions(-) diff --git a/include/fast_float/ascii_number.h b/include/fast_float/ascii_number.h index 7422e74f..85435373 100644 --- a/include/fast_float/ascii_number.h +++ b/include/fast_float/ascii_number.h @@ -32,7 +32,7 @@ template fastfloat_really_inline constexpr bool has_simd_opt() { // able to optimize it well. template fastfloat_really_inline constexpr bool is_integer(UC c) noexcept { - return !(c > UC('9') || c < UC('0')); + return (unsigned)(c - UC('0')) <= 9u; } fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { @@ -83,11 +83,10 @@ read4_to_u32(UC const *chars) { uint32_t val; ::memcpy(&val, chars, sizeof(uint32_t)); #if FASTFLOAT_IS_BIG_ENDIAN == 1 - val = byteswap(val); + val = byteswap_32(val); #endif return val; } - #ifdef FASTFLOAT_SSE2 fastfloat_really_inline uint64_t simd_read8_to_u64(__m128i const data) { @@ -169,6 +168,11 @@ is_made_of_eight_digits_fast(uint64_t val) noexcept { 0x8080808080808080)); } +fastfloat_really_inline constexpr bool +is_made_of_four_digits_fast(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 { val -= 0x30303030; @@ -648,77 +652,38 @@ parse_int_string(UC const *p, UC const *pend, T &value, return answer; } - uint32_t digits; if (len >= 4) { - digits = read4_to_u32(p); - } else { - uint32_t b0 = uint32_t(uint8_t(p[0])); - uint32_t b1 = (len > 1) ? uint32_t(uint8_t(p[1])) : 0xFFu; - uint32_t b2 = (len > 2) ? uint32_t(uint8_t(p[2])) : 0xFFu; - digits = b0 | (b1 << 8) | (b2 << 16) | (0xFFu << 24); - } - - uint32_t magic = - ((digits + 0x46464646u) | (digits - 0x30303030u)) & 0x80808080u; - uint32_t nd = (magic == 0) ? 4u : (uint32_t(countr_zero_32(magic)) >> 3); - - if (nd == 0) { - if (has_leading_zeros) { - value = 0; + uint32_t digits = read4_to_u32(p); + if (is_made_of_four_digits_fast(digits)) { + uint32_t v = parse_four_digits_unrolled(digits); + if (len >= 5 && is_integer(p[4])) { + v = v * 10 + uint32_t(p[4] - '0'); + if (len >= 6 && is_integer(p[5])) { + answer.ec = std::errc::result_out_of_range; + const UC *q = p + 5; + while (q != pend && is_integer(*q)) { + q++; + } + answer.ptr = q; + return answer; + } + if (v > 65535) { + answer.ec = std::errc::result_out_of_range; + answer.ptr = p + 5; + return answer; + } + value = uint16_t(v); + answer.ec = std::errc(); + answer.ptr = p + 5; + return answer; + } + // 4 digits + value = uint16_t(v); answer.ec = std::errc(); - answer.ptr = p; - return answer; - } - answer.ec = std::errc::invalid_argument; - answer.ptr = first; - return answer; - } - - if (nd < 4) { - // mask out non-digit bytes and replace with '0' (0x30) - uint32_t mask = 0xFFFFFFFFu >> ((4u - nd) * 8u); - uint32_t padded = (digits & mask) | (~mask & 0x30303030u); - uint32_t v = parse_four_digits_unrolled(padded); - constexpr uint32_t divs[] = {0, 1000, 100, 10}; - value = (uint16_t)(v / divs[nd]); - answer.ec = std::errc(); - answer.ptr = p + nd; - return answer; - } - - uint32_t v = parse_four_digits_unrolled(digits); - - uint32_t d4 = (len > 4) ? uint32_t(p[4] - '0') : 10u; - if (d4 > 9u) { - value = (uint16_t)v; - answer.ec = std::errc(); - answer.ptr = p + 4; - return answer; - } - - if (len > 5) { - uint32_t d5 = uint32_t(p[5]) - uint32_t('0'); - if (d5 <= 9u) { - const UC *q = p + 6; - while (q < pend && uint32_t(*q) - uint32_t('0') <= 9u) - ++q; - answer.ec = std::errc::result_out_of_range; - answer.ptr = q; + answer.ptr = p + 4; return answer; } } - - // overflow check - if (v > 6553u || (v == 6553u && d4 > 5u)) { - answer.ec = std::errc::result_out_of_range; - answer.ptr = p + 5; - return answer; - } - - value = (uint16_t)(v * 10u + d4); - answer.ec = std::errc(); - answer.ptr = p + 5; - return answer; } } From d1af5b18ac78bc76fdcb40ae9dedef803f7ab899 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 00:11:02 +0000 Subject: [PATCH 10/17] Bump the github-actions group across 1 directory with 4 updates Bumps the github-actions group with 4 updates in the / directory: [actions/checkout](https://github.com/actions/checkout), [actions/upload-artifact](https://github.com/actions/upload-artifact), [actions/setup-node](https://github.com/actions/setup-node) and [jidicula/clang-format-action](https://github.com/jidicula/clang-format-action). Updates `actions/checkout` from 5 to 6 - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v5...v6) Updates `actions/upload-artifact` from 5 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) Updates `actions/setup-node` from 6.0.0 to 6.1.0 - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/2028fbc5c25fe9cf00d9f06a71cc4710d4507903...395ad3262231945c25e8478fd5baf05154b1d79f) Updates `jidicula/clang-format-action` from 4.15.0 to 4.16.0 - [Release notes](https://github.com/jidicula/clang-format-action/releases) - [Commits](https://github.com/jidicula/clang-format-action/compare/4726374d1aa3c6aecf132e5197e498979588ebc8...6cd220de46c89139a0365edae93eee8eb30ca8fe) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/setup-node dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: jidicula/clang-format-action dependency-version: 4.16.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/alpine.yml | 2 +- .github/workflows/amalgamate-ubuntu24.yml | 2 +- .github/workflows/cifuzz.yml | 2 +- .github/workflows/emscripten.yml | 6 +++--- .github/workflows/lint_and_format_check.yml | 4 ++-- .github/workflows/msys2-clang.yml | 2 +- .github/workflows/msys2.yml | 2 +- .github/workflows/on-release.yml | 2 +- .github/workflows/risc.yml | 2 +- .github/workflows/s390x.yml | 2 +- .github/workflows/ubuntu22-clang.yml | 2 +- .github/workflows/ubuntu22-gcc12.yml | 2 +- .github/workflows/ubuntu22-sanitize.yml | 2 +- .github/workflows/ubuntu22.yml | 2 +- .github/workflows/ubuntu24-cxx20.yml | 2 +- .github/workflows/ubuntu24.yml | 2 +- .github/workflows/vs17-arm-ci.yml | 2 +- .github/workflows/vs17-ci.yml | 2 +- .github/workflows/vs17-clang-ci.yml | 2 +- .github/workflows/vs17-cxx20.yml | 2 +- 20 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/alpine.yml b/.github/workflows/alpine.yml index 9b24bdef..b77fd44f 100644 --- a/.github/workflows/alpine.yml +++ b/.github/workflows/alpine.yml @@ -18,7 +18,7 @@ jobs: - riscv64 steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install latest Alpine Linux for ${{ matrix.arch }} uses: jirutka/setup-alpine@v1 diff --git a/.github/workflows/amalgamate-ubuntu24.yml b/.github/workflows/amalgamate-ubuntu24.yml index ca57ff65..db822cb8 100644 --- a/.github/workflows/amalgamate-ubuntu24.yml +++ b/.github/workflows/amalgamate-ubuntu24.yml @@ -6,7 +6,7 @@ jobs: ubuntu-build: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Compile with amalgamation run: | mkdir build && diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index bd9e1e6c..2d0bdaa5 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -20,7 +20,7 @@ jobs: fuzz-seconds: 300 output-sarif: true - name: Upload Crash - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 if: failure() && steps.build.outcome == 'success' with: name: artifacts diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml index 399f0c9e..e5ddb06e 100644 --- a/.github/workflows/emscripten.yml +++ b/.github/workflows/emscripten.yml @@ -4,13 +4,13 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.2.2 - - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v4.2.2 + - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - uses: mymindstorm/setup-emsdk@6ab9eb1bda2574c4ddb79809fc9247783eaf9021 # v14 - name: Verify run: emcc -v - name: Checkout - uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v3.6.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v3.6.0 - name: Configure run: emcmake cmake -B build - name: Build # We build but do not test diff --git a/.github/workflows/lint_and_format_check.yml b/.github/workflows/lint_and_format_check.yml index ce6a2af5..164cd225 100644 --- a/.github/workflows/lint_and_format_check.yml +++ b/.github/workflows/lint_and_format_check.yml @@ -24,10 +24,10 @@ jobs: lint-and-format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.1.7 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v4.1.7 - name: Run clang-format - uses: jidicula/clang-format-action@4726374d1aa3c6aecf132e5197e498979588ebc8 # v4.15.0 + uses: jidicula/clang-format-action@6cd220de46c89139a0365edae93eee8eb30ca8fe # v4.16.0 with: clang-format-version: '17' diff --git a/.github/workflows/msys2-clang.yml b/.github/workflows/msys2-clang.yml index 7697bb59..d263b6d4 100644 --- a/.github/workflows/msys2-clang.yml +++ b/.github/workflows/msys2-clang.yml @@ -23,7 +23,7 @@ jobs: CMAKE_GENERATOR: Ninja steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: msys2/setup-msys2@v2 with: update: true diff --git a/.github/workflows/msys2.yml b/.github/workflows/msys2.yml index 4bd814e6..4848f2e0 100644 --- a/.github/workflows/msys2.yml +++ b/.github/workflows/msys2.yml @@ -29,7 +29,7 @@ jobs: CMAKE_GENERATOR: Ninja steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: msys2/setup-msys2@v2 with: update: true diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml index 26ef5d58..371cba0a 100644 --- a/.github/workflows/on-release.yml +++ b/.github/workflows/on-release.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Amalgamate fast_float.h run: | diff --git a/.github/workflows/risc.yml b/.github/workflows/risc.yml index 8bc85588..a8f4d399 100644 --- a/.github/workflows/risc.yml +++ b/.github/workflows/risc.yml @@ -6,7 +6,7 @@ jobs: build: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install packages run: | sudo apt-get update -q -y diff --git a/.github/workflows/s390x.yml b/.github/workflows/s390x.yml index 0bc90d98..0799e215 100644 --- a/.github/workflows/s390x.yml +++ b/.github/workflows/s390x.yml @@ -12,7 +12,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: uraimo/run-on-arch-action@v3 name: Test id: runcmd diff --git a/.github/workflows/ubuntu22-clang.yml b/.github/workflows/ubuntu22-clang.yml index f8af4374..3e6df05e 100644 --- a/.github/workflows/ubuntu22-clang.yml +++ b/.github/workflows/ubuntu22-clang.yml @@ -6,7 +6,7 @@ jobs: ubuntu-build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install clang++-14 run: sudo apt-get install -y clang++-14 - name: Use cmake diff --git a/.github/workflows/ubuntu22-gcc12.yml b/.github/workflows/ubuntu22-gcc12.yml index 91abf7ce..b7bba1e0 100644 --- a/.github/workflows/ubuntu22-gcc12.yml +++ b/.github/workflows/ubuntu22-gcc12.yml @@ -6,7 +6,7 @@ jobs: ubuntu-build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Use cmake run: | mkdir build && diff --git a/.github/workflows/ubuntu22-sanitize.yml b/.github/workflows/ubuntu22-sanitize.yml index 08fe8d73..c51524e1 100644 --- a/.github/workflows/ubuntu22-sanitize.yml +++ b/.github/workflows/ubuntu22-sanitize.yml @@ -6,7 +6,7 @@ jobs: ubuntu-build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Use cmake run: | mkdir build && diff --git a/.github/workflows/ubuntu22.yml b/.github/workflows/ubuntu22.yml index 71543954..c8fb3c11 100644 --- a/.github/workflows/ubuntu22.yml +++ b/.github/workflows/ubuntu22.yml @@ -6,7 +6,7 @@ jobs: ubuntu-build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Use cmake run: | mkdir build && diff --git a/.github/workflows/ubuntu24-cxx20.yml b/.github/workflows/ubuntu24-cxx20.yml index 85167601..c705631f 100644 --- a/.github/workflows/ubuntu24-cxx20.yml +++ b/.github/workflows/ubuntu24-cxx20.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Use cmake run: | mkdir build && diff --git a/.github/workflows/ubuntu24.yml b/.github/workflows/ubuntu24.yml index 511c7ce3..1b998099 100644 --- a/.github/workflows/ubuntu24.yml +++ b/.github/workflows/ubuntu24.yml @@ -6,7 +6,7 @@ jobs: ubuntu-build: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Use cmake run: | set -xe diff --git a/.github/workflows/vs17-arm-ci.yml b/.github/workflows/vs17-arm-ci.yml index 6769a2a4..f4fa84e4 100644 --- a/.github/workflows/vs17-arm-ci.yml +++ b/.github/workflows/vs17-arm-ci.yml @@ -14,7 +14,7 @@ jobs: - {gen: Visual Studio 17 2022, arch: ARM64, cfg: Debug} steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: configure run: | cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DCMAKE_CROSSCOMPILING=1 -DFASTFLOAT_TEST=ON diff --git a/.github/workflows/vs17-ci.yml b/.github/workflows/vs17-ci.yml index 091b1c5b..48f4985e 100644 --- a/.github/workflows/vs17-ci.yml +++ b/.github/workflows/vs17-ci.yml @@ -16,7 +16,7 @@ jobs: - {gen: Visual Studio 17 2022, arch: x64, cfg: Debug} steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: configure run: | cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DFASTFLOAT_BENCHMARKS=ON -DFASTFLOAT_TEST=ON -DCMAKE_INSTALL_PREFIX:PATH=destination diff --git a/.github/workflows/vs17-clang-ci.yml b/.github/workflows/vs17-clang-ci.yml index 56b51611..31b3a4b6 100644 --- a/.github/workflows/vs17-clang-ci.yml +++ b/.github/workflows/vs17-clang-ci.yml @@ -16,7 +16,7 @@ jobs: - {gen: Visual Studio 17 2022, arch: x64, cfg: Debug} steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Configure run: | cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DFASTFLOAT_BENCHMARKS=ON -T ClangCL -DFASTFLOAT_TEST=ON diff --git a/.github/workflows/vs17-cxx20.yml b/.github/workflows/vs17-cxx20.yml index aecbca8f..93b7a896 100644 --- a/.github/workflows/vs17-cxx20.yml +++ b/.github/workflows/vs17-cxx20.yml @@ -16,7 +16,7 @@ jobs: - {gen: Visual Studio 17 2022, arch: x64, cfg: Debug} steps: - name: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: configure run: >- cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} From 64a68590fd3e41826351003ee9b03f53d25f0aa2 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Sun, 18 Jan 2026 19:05:51 -0500 Subject: [PATCH 11/17] Clarify `std::from_chars` conversion method Updated explanation of `std::from_chars` conversion checks. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d19283ff..71a892db 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ int main() { } ``` -Though the C++17 standard has you do a comparison with `std::errc()` to check whether the conversion worked, you can avoid it by casting the result to a `bool` like so: +Prior to C++26, checking for a successful `std::from_chars` conversion requires comparing the `from_chars_result::ec` member to `std::errc()`. As an extension `fast_float::from_chars` supports the improved C++26 API that allows checking the result by converting it to `bool`, like so: ```cpp #include "fast_float/fast_float.h" From 71ab1cce81c126f9cbf34d1b29f75695d131861d Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Mon, 19 Jan 2026 20:36:29 -0500 Subject: [PATCH 12/17] Fix error message to display input instead of result --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71a892db..9bcae052 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ int main() { std::cout << "parsed the number " << result << std::endl; return EXIT_SUCCESS; } - std::cerr << "failed to parse " << result << std::endl; + std::cerr << "failed to parse " << input << std::endl; return EXIT_FAILURE; } ``` From 4fa83ccff44a36d8c8bc1dcf469a0cfe6929224b Mon Sep 17 00:00:00 2001 From: sleepingieght Date: Wed, 21 Jan 2026 19:21:06 +0530 Subject: [PATCH 13/17] fix early return error in fastfloat_strncasecmp --- include/fast_float/float_common.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 7aeb2c28..595f43a6 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -290,8 +290,8 @@ fastfloat_strncasecmp3(UC const *actual_mixedcase, if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) { return false; } - return true; } + return true; } else { FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1 || sizeof(UC) == 2) { ::memcpy(&val1, actual_mixedcase, 3 * sizeof(UC)); @@ -328,8 +328,8 @@ fastfloat_strncasecmp5(UC const *actual_mixedcase, if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) { return false; } - return true; } + return true; } else { FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) { mask = 0x2020202020202020; @@ -395,8 +395,8 @@ fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) { return false; } - return true; } + return true; } else { uint64_t val1{0}, val2{0}; size_t sz{8 / (sizeof(UC))}; From cb299bdeccf428f64127674ce4bca4b00fea732d Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Wed, 28 Jan 2026 10:55:48 +0100 Subject: [PATCH 14/17] Clarify behavior for underflow In https://github.com/fastfloat/fast_float/pull/189 the behavior on underflow was changed to better match the standard's recommendations, but the README does not mention underflow explicitly. --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9bcae052..3490a583 100644 --- a/README.md +++ b/README.md @@ -141,9 +141,12 @@ Furthermore, we have the following restrictions: fixed-width floating-point types such as `std::float64_t`, `std::float32_t`, `std::float16_t`, and `std::bfloat16_t`. * We only support the decimal format: we do not support hexadecimal strings. -* For values that are either very large or very small (e.g., `1e9999`), we - represent it using the infinity or negative infinity value and the returned +* For values that are very large positives or negatives (e.g., `1e9999`), we + represent them using a positive or negative infinity and the returned `ec` is set to `std::errc::result_out_of_range`. +* For values that are very close to zero (e.g., `1e-9999`), we represent them + using a positive or negative zero and the returned `ec` is set to + `std::errc::result_out_of_range`. We support Visual Studio, macOS, Linux, freeBSD. We support big and little endian. We support 32-bit and 64-bit systems. From 95295d139802c92a0e3916a54a3b96652a677a94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 00:10:32 +0000 Subject: [PATCH 15/17] Bump the github-actions group with 2 updates Bumps the github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [actions/setup-node](https://github.com/actions/setup-node). Updates `actions/checkout` from 5.0.1 to 6.0.1 - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v5.0.1...v6.0.1) Updates `actions/setup-node` from 6.1.0 to 6.2.0 - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/395ad3262231945c25e8478fd5baf05154b1d79f...6044e13b5dc448c55e2357c09f80417699197238) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/setup-node dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/alpine.yml | 2 +- .github/workflows/amalgamate-ubuntu24.yml | 2 +- .github/workflows/emscripten.yml | 6 +++--- .github/workflows/lint_and_format_check.yml | 2 +- .github/workflows/msys2-clang.yml | 2 +- .github/workflows/msys2.yml | 2 +- .github/workflows/on-release.yml | 2 +- .github/workflows/risc.yml | 2 +- .github/workflows/s390x.yml | 2 +- .github/workflows/ubuntu22-clang.yml | 2 +- .github/workflows/ubuntu22-gcc12.yml | 2 +- .github/workflows/ubuntu22-sanitize.yml | 2 +- .github/workflows/ubuntu22.yml | 2 +- .github/workflows/ubuntu24-cxx20.yml | 2 +- .github/workflows/ubuntu24.yml | 2 +- .github/workflows/vs17-arm-ci.yml | 2 +- .github/workflows/vs17-ci.yml | 2 +- .github/workflows/vs17-clang-ci.yml | 2 +- .github/workflows/vs17-cxx20.yml | 2 +- 19 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/alpine.yml b/.github/workflows/alpine.yml index b77fd44f..7917a051 100644 --- a/.github/workflows/alpine.yml +++ b/.github/workflows/alpine.yml @@ -18,7 +18,7 @@ jobs: - riscv64 steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 - name: Install latest Alpine Linux for ${{ matrix.arch }} uses: jirutka/setup-alpine@v1 diff --git a/.github/workflows/amalgamate-ubuntu24.yml b/.github/workflows/amalgamate-ubuntu24.yml index db822cb8..6d2d29e9 100644 --- a/.github/workflows/amalgamate-ubuntu24.yml +++ b/.github/workflows/amalgamate-ubuntu24.yml @@ -6,7 +6,7 @@ jobs: ubuntu-build: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - name: Compile with amalgamation run: | mkdir build && diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml index e5ddb06e..789aa66f 100644 --- a/.github/workflows/emscripten.yml +++ b/.github/workflows/emscripten.yml @@ -4,13 +4,13 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v4.2.2 - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.2.2 + - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 - uses: mymindstorm/setup-emsdk@6ab9eb1bda2574c4ddb79809fc9247783eaf9021 # v14 - name: Verify run: emcc -v - name: Checkout - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v3.6.0 + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v3.6.0 - name: Configure run: emcmake cmake -B build - name: Build # We build but do not test diff --git a/.github/workflows/lint_and_format_check.yml b/.github/workflows/lint_and_format_check.yml index 164cd225..dd30d18e 100644 --- a/.github/workflows/lint_and_format_check.yml +++ b/.github/workflows/lint_and_format_check.yml @@ -24,7 +24,7 @@ jobs: lint-and-format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v4.1.7 + - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.7 - name: Run clang-format uses: jidicula/clang-format-action@6cd220de46c89139a0365edae93eee8eb30ca8fe # v4.16.0 diff --git a/.github/workflows/msys2-clang.yml b/.github/workflows/msys2-clang.yml index d263b6d4..68b36ea1 100644 --- a/.github/workflows/msys2-clang.yml +++ b/.github/workflows/msys2-clang.yml @@ -23,7 +23,7 @@ jobs: CMAKE_GENERATOR: Ninja steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - uses: msys2/setup-msys2@v2 with: update: true diff --git a/.github/workflows/msys2.yml b/.github/workflows/msys2.yml index 4848f2e0..ae604c8b 100644 --- a/.github/workflows/msys2.yml +++ b/.github/workflows/msys2.yml @@ -29,7 +29,7 @@ jobs: CMAKE_GENERATOR: Ninja steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - uses: msys2/setup-msys2@v2 with: update: true diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml index 371cba0a..a68df4ed 100644 --- a/.github/workflows/on-release.yml +++ b/.github/workflows/on-release.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - name: Amalgamate fast_float.h run: | diff --git a/.github/workflows/risc.yml b/.github/workflows/risc.yml index a8f4d399..6609cffd 100644 --- a/.github/workflows/risc.yml +++ b/.github/workflows/risc.yml @@ -6,7 +6,7 @@ jobs: build: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - name: Install packages run: | sudo apt-get update -q -y diff --git a/.github/workflows/s390x.yml b/.github/workflows/s390x.yml index 0799e215..db7a03d3 100644 --- a/.github/workflows/s390x.yml +++ b/.github/workflows/s390x.yml @@ -12,7 +12,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - uses: uraimo/run-on-arch-action@v3 name: Test id: runcmd diff --git a/.github/workflows/ubuntu22-clang.yml b/.github/workflows/ubuntu22-clang.yml index 3e6df05e..4d7f49fd 100644 --- a/.github/workflows/ubuntu22-clang.yml +++ b/.github/workflows/ubuntu22-clang.yml @@ -6,7 +6,7 @@ jobs: ubuntu-build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - name: Install clang++-14 run: sudo apt-get install -y clang++-14 - name: Use cmake diff --git a/.github/workflows/ubuntu22-gcc12.yml b/.github/workflows/ubuntu22-gcc12.yml index b7bba1e0..a32033c6 100644 --- a/.github/workflows/ubuntu22-gcc12.yml +++ b/.github/workflows/ubuntu22-gcc12.yml @@ -6,7 +6,7 @@ jobs: ubuntu-build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - name: Use cmake run: | mkdir build && diff --git a/.github/workflows/ubuntu22-sanitize.yml b/.github/workflows/ubuntu22-sanitize.yml index c51524e1..b7f96602 100644 --- a/.github/workflows/ubuntu22-sanitize.yml +++ b/.github/workflows/ubuntu22-sanitize.yml @@ -6,7 +6,7 @@ jobs: ubuntu-build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - name: Use cmake run: | mkdir build && diff --git a/.github/workflows/ubuntu22.yml b/.github/workflows/ubuntu22.yml index c8fb3c11..09dac2b5 100644 --- a/.github/workflows/ubuntu22.yml +++ b/.github/workflows/ubuntu22.yml @@ -6,7 +6,7 @@ jobs: ubuntu-build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - name: Use cmake run: | mkdir build && diff --git a/.github/workflows/ubuntu24-cxx20.yml b/.github/workflows/ubuntu24-cxx20.yml index c705631f..1d451c98 100644 --- a/.github/workflows/ubuntu24-cxx20.yml +++ b/.github/workflows/ubuntu24-cxx20.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - name: Use cmake run: | mkdir build && diff --git a/.github/workflows/ubuntu24.yml b/.github/workflows/ubuntu24.yml index 1b998099..09c82c8f 100644 --- a/.github/workflows/ubuntu24.yml +++ b/.github/workflows/ubuntu24.yml @@ -6,7 +6,7 @@ jobs: ubuntu-build: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - name: Use cmake run: | set -xe diff --git a/.github/workflows/vs17-arm-ci.yml b/.github/workflows/vs17-arm-ci.yml index f4fa84e4..9eef0129 100644 --- a/.github/workflows/vs17-arm-ci.yml +++ b/.github/workflows/vs17-arm-ci.yml @@ -14,7 +14,7 @@ jobs: - {gen: Visual Studio 17 2022, arch: ARM64, cfg: Debug} steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 - name: configure run: | cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DCMAKE_CROSSCOMPILING=1 -DFASTFLOAT_TEST=ON diff --git a/.github/workflows/vs17-ci.yml b/.github/workflows/vs17-ci.yml index 48f4985e..39a5bd40 100644 --- a/.github/workflows/vs17-ci.yml +++ b/.github/workflows/vs17-ci.yml @@ -16,7 +16,7 @@ jobs: - {gen: Visual Studio 17 2022, arch: x64, cfg: Debug} steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 - name: configure run: | cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DFASTFLOAT_BENCHMARKS=ON -DFASTFLOAT_TEST=ON -DCMAKE_INSTALL_PREFIX:PATH=destination diff --git a/.github/workflows/vs17-clang-ci.yml b/.github/workflows/vs17-clang-ci.yml index 31b3a4b6..25a54b86 100644 --- a/.github/workflows/vs17-clang-ci.yml +++ b/.github/workflows/vs17-clang-ci.yml @@ -16,7 +16,7 @@ jobs: - {gen: Visual Studio 17 2022, arch: x64, cfg: Debug} steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 - name: Configure run: | cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DFASTFLOAT_BENCHMARKS=ON -T ClangCL -DFASTFLOAT_TEST=ON diff --git a/.github/workflows/vs17-cxx20.yml b/.github/workflows/vs17-cxx20.yml index 93b7a896..dda7afc1 100644 --- a/.github/workflows/vs17-cxx20.yml +++ b/.github/workflows/vs17-cxx20.yml @@ -16,7 +16,7 @@ jobs: - {gen: Visual Studio 17 2022, arch: x64, cfg: Debug} steps: - name: checkout - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 - name: configure run: >- cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} From 707fccb44505eb62c3736ec47473f964df883055 Mon Sep 17 00:00:00 2001 From: N'yoma Diamond Date: Mon, 2 Feb 2026 16:44:39 +0000 Subject: [PATCH 16/17] compile-time generator expression fixes overzealous /permissive- compiler option usage --- CMakeLists.txt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 83953908..b383b7bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,13 +57,7 @@ if(FASTFLOAT_SANITIZE) endif() endif() -include(CheckCXXCompilerFlag) -unset(FASTFLOAT_COMPILER_SUPPORTS_PERMISSIVE) -CHECK_CXX_COMPILER_FLAG(/permissive- FASTFLOAT_COMPILER_SUPPORTS_PERMISSIVE) - -if(FASTFLOAT_COMPILER_SUPPORTS_PERMISSIVE) - target_compile_options(fast_float INTERFACE /permissive-) -endif() +target_compile_options(fast_float INTERFACE $<$,$,19.10>>:/permissive->) if(FASTFLOAT_INSTALL) include(CMakePackageConfigHelpers) From 01ce95dfe46abccf3264fbccf9f5139ea9016cd2 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Tue, 3 Feb 2026 11:27:40 -0500 Subject: [PATCH 17/17] v8.2.3 --- CMakeLists.txt | 2 +- README.md | 6 +++--- include/fast_float/float_common.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b383b7bd..a75ad6dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14) -project(fast_float VERSION 8.2.2 LANGUAGES CXX) +project(fast_float VERSION 8.2.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 3490a583..c2b2b386 100644 --- a/README.md +++ b/README.md @@ -536,7 +536,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.2.2 + GIT_TAG tags/v8.2.3 GIT_SHALLOW TRUE) FetchContent_MakeAvailable(fast_float) @@ -552,7 +552,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.2.2) + GIT_TAG v8.2.3) ``` ## Using as single header @@ -564,7 +564,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 595f43a6..f35920ba 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -18,7 +18,7 @@ #define FASTFLOAT_VERSION_MAJOR 8 #define FASTFLOAT_VERSION_MINOR 2 -#define FASTFLOAT_VERSION_PATCH 2 +#define FASTFLOAT_VERSION_PATCH 3 #define FASTFLOAT_STRINGIZE_IMPL(x) #x #define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x)