diff --git a/.github/workflows/alpine.yml b/.github/workflows/alpine.yml index 9b24bdef..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@v5 + 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 ca57ff65..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@v5 + - uses: actions/checkout@v6.0.2 - 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..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@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.2.2 - - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.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@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # 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 ce6a2af5..dd30d18e 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@0c366fd6a839edf440554fa01a7085ccba70ac98 # 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..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@v5 + - 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 4bd814e6..ae604c8b 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.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 26ef5d58..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@v5 + - 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 8bc85588..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@v5 + - 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 bcce4c6c..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@v5 + - uses: actions/checkout@v6.0.2 - uses: uraimo/run-on-arch-action@v3 name: Test id: runcmd @@ -22,9 +22,9 @@ 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 + cmake -DCMAKE_BUILD_TYPE=Release -B build -DFASTFLOAT_TEST=ON cmake --build build -j=2 ctest --output-on-failure --test-dir build diff --git a/.github/workflows/ubuntu22-clang.yml b/.github/workflows/ubuntu22-clang.yml index f8af4374..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@v5 + - 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 91abf7ce..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@v5 + - 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 08fe8d73..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@v5 + - 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 71543954..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@v5 + - 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 85167601..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@v5 + - 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 511c7ce3..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@v5 + - 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 6769a2a4..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@v5 + 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 091b1c5b..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@v5 + 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 56b51611..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@v5 + 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 aecbca8f..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@v5 + uses: actions/checkout@v6.0.2 - name: configure run: >- cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} diff --git a/CMakeLists.txt b/CMakeLists.txt index 83953908..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) @@ -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) diff --git a/README.md b/README.md index d19283ff..c2b2b386 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" @@ -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; } ``` @@ -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. @@ -533,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) @@ -549,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 @@ -561,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/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; +} diff --git a/include/fast_float/ascii_number.h b/include/fast_float/ascii_number.h index aa2761aa..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) { @@ -68,6 +68,25 @@ 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_32(val); +#endif + return val; +} #ifdef FASTFLOAT_SSE2 fastfloat_really_inline uint64_t simd_read8_to_u64(__m128i const data) { @@ -149,6 +168,18 @@ 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; + val = (val * 10) + (val >> 8); + return (((val & 0x00FF00FF) * 0x00640001) >> 16) & 0xFFFF; +} + #ifdef FASTFLOAT_HAS_SIMD // Call this if chars might not be 8 digits. @@ -515,93 +546,145 @@ 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; + } + + 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; + } + + if (len >= 4) { + 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 + 4; + return answer; + } + } + } } uint64_t i = 0; diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 384b33ec..f35920ba 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 @@ -17,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) @@ -267,18 +268,151 @@ struct is_supported_char_type > { }; +template +inline FASTFLOAT_CONSTEXPR14 bool +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) { + mask = 0x0020002000200020; + } + else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) { + mask = 0x0000002000000020; + } + 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}; + 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) { - for (size_t i = 0; i < length; ++i) { - UC const actual = actual_mixedcase[i]; - if ((actual < 256 ? actual | 32 : actual) != expected_lowercase[i]) { - return false; + 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; } - return true; } #ifndef FLT_EVAL_METHOD 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;