diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..8a5ba71c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,24 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Ideally, you should provide a reproducible test case. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Additional context** +Add any other context about the problem here. + +**Note:** Bug reports should come with a test case or, at least, an analysis. + +**Automated tool policy**: If you use an automated tool (e.g., static analysis, +LLM, etc.), you need to demonstrate an understanding of the issue you are raising. Usually, a bug is demonstrated by a test case. Do not copy-paste what a tool is telling you. \ No newline at end of file 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..704d5f16 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@v7 if: failure() && steps.build.outcome == 'success' with: name: artifacts diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml index 399f0c9e..d991a2c6 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@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.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..df040cea 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@3a18028048f01a66653b4e3bf8d968d2e7e2cb8b # v4.17.0 with: clang-format-version: '17' diff --git a/.github/workflows/msys2-clang.yml b/.github/workflows/msys2-clang.yml index 7697bb59..c6f8747d 100644 --- a/.github/workflows/msys2-clang.yml +++ b/.github/workflows/msys2-clang.yml @@ -16,14 +16,11 @@ jobs: - msystem: "MINGW64" install: mingw-w64-x86_64-libxml2 mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-clang type: Release - - msystem: "MINGW32" - install: mingw-w64-i686-libxml2 mingw-w64-i686-cmake mingw-w64-i686-ninja mingw-w64-i686-clang - type: Release env: 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..62749135 100644 --- a/.github/workflows/msys2.yml +++ b/.github/workflows/msys2.yml @@ -16,20 +16,14 @@ jobs: - msystem: "MINGW64" install: mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-gcc type: Release - - msystem: "MINGW32" - install: mingw-w64-i686-cmake mingw-w64-i686-ninja mingw-w64-i686-gcc - type: Release - msystem: "MINGW64" install: mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-gcc type: Debug - - msystem: "MINGW32" - install: mingw-w64-i686-cmake mingw-w64-i686-ninja mingw-w64-i686-gcc - type: Debug env: 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/BUILD.bazel b/BUILD.bazel index fc784a3b..b08e9090 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,3 +1,5 @@ +load("@rules_cc//cc:cc_library.bzl", "cc_library") + cc_library( name = "fast_float", hdrs = glob(["include/fast_float/*.h"]), diff --git a/CMakeLists.txt b/CMakeLists.txt index a220f600..e7cedc2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14) -project(fast_float VERSION 8.2.1 LANGUAGES CXX) +project(fast_float VERSION 8.2.5 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/MODULE.bazel b/MODULE.bazel index 6704680e..9f63ac4e 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -2,8 +2,10 @@ module( name = "fast_float", - version = "6.1.6", - compatibility_level = 6, + version = "8.2.4", + compatibility_level = 8, ) bazel_dep(name = "doctest", version = "2.4.11", dev_dependency = True) + +bazel_dep(name = "rules_cc", version = "0.2.17") diff --git a/README.md b/README.md index 71029ebe..be0f1f56 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ [![Ubuntu 22.04 CI (GCC 11)](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml/badge.svg)](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml) +*Note: This library is for C++ users. C programmers should consider [ffc.h](https://github.com/kolemannix/ffc.h). It is a high-performance port of fast_float to C.* + + The fast_float library provides fast header-only implementations for the C++ from_chars functions for `float` and `double` types as well as integer types. These functions convert ASCII strings representing decimal values (e.g., @@ -10,6 +13,7 @@ These functions convert ASCII strings representing decimal values (e.g., even). In our experience, these `fast_float` functions many times faster than comparable number-parsing functions from existing C++ standard libraries. + Specifically, `fast_float` provides the following two functions to parse floating-point numbers with a C++17-like syntax (the library itself only requires C++11): @@ -69,7 +73,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 +87,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 +145,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. @@ -485,6 +492,7 @@ Packages [Jackson](https://github.com/FasterXML/jackson-core). * [There is a C# port of the fast_float library](https://github.com/CarlVerret/csFastFloat) called `csFastFloat`. +* [There is a plain C port of the fast_float library](https://github.com/kolemannix/ffc.h) called ffc.h ## How fast is it? @@ -533,7 +541,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.1 + GIT_TAG tags/v8.2.5 GIT_SHALLOW TRUE) FetchContent_MakeAvailable(fast_float) @@ -549,7 +557,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.1) + GIT_TAG v8.2.5) ``` ## Using as single header @@ -561,7 +569,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 cac91606..12c2fddc 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) { @@ -42,6 +42,11 @@ fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56; } +fastfloat_really_inline constexpr uint32_t byteswap_32(uint32_t val) { + return (val >> 24) | ((val >> 8) & 0x0000FF00u) | ((val << 8) & 0x00FF0000u) | + (val << 24); +} + // Read 8 UC into a u64. Truncates UC if not char. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t @@ -63,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) { @@ -144,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. @@ -510,97 +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(digits); + digits = byteswap_32(digits); #endif - } + } #else - if (false) { - } -#endif - else if (len >= 4) { - ::memcpy(&digits, p, 4); -#if FASTFLOAT_IS_BIG_ENDIAN - digits = byteswap(digits); + if (false) { + } #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; + else if (len >= 4) { + ::memcpy(&digits, p, 4); #if FASTFLOAT_IS_BIG_ENDIAN - digits = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; -#else - digits = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); + 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); + } - 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)(nd < len ? 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/digit_comparison.h b/include/fast_float/digit_comparison.h index 03e70dcc..c2c83b0c 100644 --- a/include/fast_float/digit_comparison.h +++ b/include/fast_float/digit_comparison.h @@ -1,7 +1,6 @@ #ifndef FASTFLOAT_DIGIT_COMPARISON_H #define FASTFLOAT_DIGIT_COMPARISON_H -#include #include #include #include @@ -109,7 +108,7 @@ fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am, if (-am.power2 >= mantissa_shift) { // have a denormal float int32_t shift = -am.power2 + 1; - cb(am, std::min(shift, 64)); + cb(am, (shift < 64 ? shift : 64)); // check for round-up: if rounding-nearest carried us to the hidden bit. am.power2 = (am.mantissa < (uint64_t(1) << binary_format::mantissa_explicit_bits())) diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 88efe85f..403eea1f 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 1 +#define FASTFLOAT_VERSION_PATCH 5 #define FASTFLOAT_STRINGIZE_IMPL(x) #x #define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x) @@ -267,18 +268,147 @@ 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; + } + } +} + +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; + } + } +} + // 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 = sz < (length - i) ? 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..ff9c53d0 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; @@ -155,7 +155,7 @@ template <> struct from_chars_caller { // if std::float32_t is defined, and we are in C++23 mode; macro set for // float32; set value to float due to equivalence between float and // float32_t - float val; + float val = 0.0f; auto ret = from_chars_advanced(first, last, val, options); value = val; return ret; @@ -172,7 +172,7 @@ template <> struct from_chars_caller { // if std::float64_t is defined, and we are in C++23 mode; macro set for // float64; set value as double due to equivalence between double and // float64_t - double val; + double val = 0.0; auto ret = from_chars_advanced(first, last, val, options); value = val; return ret; @@ -251,7 +251,7 @@ clinger_fast_path_impl(uint64_t mantissa, int64_t exponent, bool is_negative, * parsing options or other parsing custom function implemented by user. */ template -FASTFLOAT_CONSTEXPR20 from_chars_result_t +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { static_assert(is_supported_float_type::value, "only some floating-point types are supported"); @@ -290,7 +290,7 @@ from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { } template -FASTFLOAT_CONSTEXPR20 from_chars_result_t +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_float_advanced(UC const *first, UC const *last, T &value, parse_options_t options) noexcept { @@ -456,7 +456,7 @@ template struct from_chars_advanced_caller { template <> struct from_chars_advanced_caller<1> { template - FASTFLOAT_CONSTEXPR20 static from_chars_result_t + fastfloat_really_inline FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, T &value, parse_options_t options) noexcept { return from_chars_float_advanced(first, last, value, options); @@ -465,7 +465,7 @@ template <> struct from_chars_advanced_caller<1> { template <> struct from_chars_advanced_caller<2> { template - FASTFLOAT_CONSTEXPR20 static from_chars_result_t + fastfloat_really_inline FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, T &value, parse_options_t options) noexcept { return from_chars_int_advanced(first, last, value, options); @@ -473,7 +473,7 @@ template <> struct from_chars_advanced_caller<2> { }; template -FASTFLOAT_CONSTEXPR20 from_chars_result_t +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_advanced(UC const *first, UC const *last, T &value, parse_options_t options) noexcept { return from_chars_advanced_caller< diff --git a/tests/basictest.cpp b/tests/basictest.cpp index 1a5537bb..dba36e8a 100644 --- a/tests/basictest.cpp +++ b/tests/basictest.cpp @@ -1167,6 +1167,9 @@ TEST_CASE("double.general") { // DBL_TRUE_MIN / 2 + 0.0000000000000001e-324 verify("2.4703282292062328e-324", 0x0.0000000000001p-1022); + verify("0.2470328229206232720e-323", 0.0, std::errc::result_out_of_range); + verify("0.2470328229206232721e-323", 0x0.0000000000001p-1022); + verify("-2.2222222222223e-322", -0x1.68p-1069); verify("9007199254740993.0", 0x1p+53); verify("860228122.6654514319E+90", 0x1.92bb20990715fp+328); @@ -1262,8 +1265,13 @@ TEST_CASE("double.general") { verify("4.9406564584124654e-324", 0x0.0000000000001p-1022); verify("2.2250738585072009e-308", 0x0.fffffffffffffp-1022); verify("2.2250738585072014e-308", 0x1p-1022); + verify("0.2225073858507201136e-307", 0x0.fffffffffffffp-1022); + verify("0.2225073858507201137e-307", 0x1p-1022); verify("1.7976931348623157e308", 0x1.fffffffffffffp+1023); verify("1.7976931348623158e308", 0x1.fffffffffffffp+1023); + verify("1.7976931348623158079e308", std::numeric_limits::max()); + verify("1.7976931348623158080e308", std::numeric_limits::infinity(), + std::errc::result_out_of_range); verify("4503599627370496.5", 4503599627370496.5); verify("4503599627475352.5", 4503599627475352.5); verify("4503599627475353.5", 4503599627475353.5); @@ -1543,6 +1551,8 @@ TEST_CASE("float.general") { verify("0.7006492e-45", 0.f, std::errc::result_out_of_range); // FLT_TRUE_MIN / 2 + 0.0000001e-45 verify("0.7006493e-45", 0x1p-149f); + verify("0.7006492321624085354e-45", 0.f, std::errc::result_out_of_range); + verify("0.7006492321624085355e-45", 0x1p-149f); // max verify("340282346638528859811704183484516925440", 0x1.fffffep+127f); @@ -1553,6 +1563,9 @@ TEST_CASE("float.general") { // that rounds to FLT_MAX verify("340282356779733661637539395458142568447", std::numeric_limits::max()); + verify("0.3402823567797336616e39", std::numeric_limits::max()); + verify("0.3402823567797336617e39", std::numeric_limits::infinity(), + std::errc::result_out_of_range); verify("-1e-999", -0.0f, std::errc::result_out_of_range); verify("1." @@ -1563,6 +1576,8 @@ TEST_CASE("float.general") { "175494140627517859246175898662808184331245864732796240031385942718174" "6759860647699724722770042717456817626953125e-38", 0x1.fffff8p-127f); + verify("1.1754942807573642917e-38", 0x1.fffffcp-127f); + verify("1.1754942807573642918e-38", std::numeric_limits::min()); verify_runtime( append_zeros("1." "17549414062751785924617589866280818433124586473279624003138" diff --git a/tests/long_test.cpp b/tests/long_test.cpp index 40539ef0..feab4d02 100644 --- a/tests/long_test.cpp +++ b/tests/long_test.cpp @@ -22,7 +22,7 @@ template bool test() { char const *begin = input.data(); char const *end = input.data() + input.size(); for (size_t i = 0; i < answers.size(); i++) { - T result_value; + T result_value = 0; while ((begin < end) && (std::isspace(*begin))) { begin++; } diff --git a/tests/string_test.cpp b/tests/string_test.cpp index 68828ac3..69d2a31d 100644 --- a/tests/string_test.cpp +++ b/tests/string_test.cpp @@ -98,7 +98,7 @@ template bool test() { char const *begin = input.data(); char const *end = input.data() + input.size(); for (size_t i = 0; i < answers.size(); i++) { - T result_value; + T result_value = 0; while ((begin < end) && (std::isspace(*begin))) { begin++; }