├── .bazelrc ├── .cirrus.yml ├── .clang-format ├── .github ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── alpine.yml │ ├── amalgamate-ubuntu20.yml │ ├── cifuzz.yml │ ├── lint_and_format_check.yml │ ├── msys2-clang.yml │ ├── msys2.yml │ ├── on-release.yml │ ├── s390x.yml │ ├── ubuntu20-cxx20.yml │ ├── ubuntu20-fastmath.yml │ ├── ubuntu20.yml │ ├── ubuntu22-clang.yml │ ├── ubuntu22-gcc12.yml │ ├── ubuntu22-sanitize.yml │ ├── ubuntu22.yml │ ├── ubuntu24.yml │ ├── vs17-arm-ci.yml │ ├── vs17-ci.yml │ ├── vs17-clang-ci.yml │ └── vs17-cxx20.yml ├── .gitignore ├── .travis.yml ├── AUTHORS ├── BUILD.bazel ├── CMakeLists.txt ├── CONTRIBUTORS ├── LICENSE-APACHE ├── LICENSE-BOOST ├── LICENSE-MIT ├── MODULE.bazel ├── README.md ├── SECURITY.md ├── benchmarks ├── CMakeLists.txt ├── apple_arm_events.h ├── benchmark.cpp ├── event_counter.h └── linux-perf-events.h ├── ci └── script.sh ├── clang-format-ignore.txt ├── cmake └── config.cmake.in ├── fuzz ├── build.sh └── from_chars.cc ├── include └── fast_float │ ├── ascii_number.h │ ├── bigint.h │ ├── constexpr_feature_detect.h │ ├── decimal_to_binary.h │ ├── digit_comparison.h │ ├── fast_float.h │ ├── fast_table.h │ ├── float_common.h │ └── parse_number.h ├── script ├── amalgamate.py ├── analysis.py ├── mushtak_lemire.py ├── release.py ├── run-clangcldocker.sh └── table_generation.py └── tests ├── BUILD.bazel ├── CMakeLists.txt ├── basictest.cpp ├── bloat_analysis ├── CMakeLists.txt ├── a1.cpp ├── a10.cpp ├── a2.cpp ├── a3.cpp ├── a4.cpp ├── a5.cpp ├── a6.cpp ├── a7.cpp ├── a8.cpp ├── a9.cpp ├── main.cpp └── main_ref.cpp ├── build_tests ├── CMakeLists.txt └── issue72 │ ├── CMakeLists.txt │ ├── foo.cpp │ ├── main.cpp │ └── test.h ├── example_comma_test.cpp ├── example_test.cpp ├── exhaustive32.cpp ├── exhaustive32_64.cpp ├── exhaustive32_midpoint.cpp ├── fast_int.cpp ├── fixedwidthtest.cpp ├── fortran.cpp ├── installation_tests ├── find │ └── CMakeLists.txt └── issue72_installation │ └── CMakeLists.txt ├── json_fmt.cpp ├── long_exhaustive32.cpp ├── long_exhaustive32_64.cpp ├── long_random64.cpp ├── long_test.cpp ├── powersoffive_hardround.cpp ├── random64.cpp ├── random_string.cpp ├── rcppfastfloat_test.cpp ├── short_random_string.cpp ├── string_test.cpp ├── supported_chars_test.cpp └── wide_char_test.cpp /.bazelrc: -------------------------------------------------------------------------------- 1 | build --cxxopt="--std=c++17" 2 | -------------------------------------------------------------------------------- /.cirrus.yml: -------------------------------------------------------------------------------- 1 | 2 | task: 3 | timeout_in: 120m 4 | freebsd_instance: 5 | matrix: 6 | - image_family: freebsd-13-0-snap 7 | 8 | env: 9 | ASSUME_ALWAYS_YES: YES 10 | setup_script: 11 | - pkg update -f 12 | - pkg install bash 13 | - pkg install cmake 14 | - pkg install git 15 | build_script: 16 | - mkdir build 17 | - cd build 18 | - cmake -DFASTFLOAT_TEST=ON .. 19 | - make 20 | test_script: 21 | - cd build 22 | - ctest --output-on-failure -R basictest 23 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | SortIncludes: false 3 | SeparateDefinitionBlocks: Always 4 | MaxEmptyLinesToKeep: 1 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Keep GitHub Actions up to date with GitHub's Dependabot... 2 | # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot 3 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem 4 | version: 2 5 | updates: 6 | - package-ecosystem: github-actions 7 | directory: / 8 | groups: 9 | github-actions: 10 | patterns: 11 | - "*" # Group all Actions updates into a single larger pull request 12 | schedule: 13 | interval: weekly 14 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | We have benchmarks, please consider running them: [see our README for details](https://github.com/fastfloat/fast_float/blob/main/README.md#benchmarking). We expect you to run our benchmarks if you make claims with respect to the performance. 2 | 3 | 4 | Our CI tests check formatting automating. If such a test fails, please consider running the bash script: 5 | 6 | ```bash 7 | bash script/run-clangcldocker.sh 8 | ``` 9 | 10 | Make sure that you have [docker installed and running](https://docs.docker.com/engine/install/) on your system. Most Linux distributions support docker though some (like RedHat) have the equivalent (Podman). Users of Apple systems may want to [consider OrbStack](https://orbstack.dev). You do not need to familiar with docker, you just need to make sure that you are have it running. 11 | 12 | If you are unable to format the code, we may format it for you. 13 | -------------------------------------------------------------------------------- /.github/workflows/alpine.yml: -------------------------------------------------------------------------------- 1 | name: Alpine Linux 2 | on: 3 | - push 4 | - pull_request 5 | 6 | jobs: 7 | build: 8 | name: Build on Alpine ${{ matrix.arch }} 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | arch: 13 | - x86_64 14 | - x86 15 | - aarch64 16 | - armv7 17 | - ppc64le 18 | - riscv64 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | 23 | - name: Install latest Alpine Linux for ${{ matrix.arch }} 24 | uses: jirutka/setup-alpine@v1 25 | with: 26 | arch: ${{ matrix.arch }} 27 | branch: ${{ matrix.arch == 'riscv64' && 'edge' || 'latest-stable' }} 28 | packages: > 29 | build-base 30 | cmake 31 | g++ 32 | linux-headers 33 | git 34 | bash 35 | build-base 36 | - name: Prepare 37 | run: | 38 | cmake -DFASTFLOAT_TEST=ON -B build 39 | shell: alpine.sh {0} 40 | - name: Build 41 | run: | 42 | cmake --build build 43 | shell: alpine.sh {0} 44 | - name: Test 45 | run: | 46 | ctest --test-dir build -R basictest 47 | shell: alpine.sh {0} 48 | -------------------------------------------------------------------------------- /.github/workflows/amalgamate-ubuntu20.yml: -------------------------------------------------------------------------------- 1 | name: Amalgamate Ubuntu 20.04 CI (GCC 9) 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ubuntu-build: 7 | runs-on: ubuntu-20.04 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Compile with amalgamation 11 | run: | 12 | mkdir build && 13 | mkdir build/fast_float && 14 | python3 ./script/amalgamate.py > build/fast_float/fast_float.h && 15 | cp tests/string_test.cpp build/ && 16 | cd build && 17 | g++ string_test.cpp 18 | -------------------------------------------------------------------------------- /.github/workflows/cifuzz.yml: -------------------------------------------------------------------------------- 1 | name: CIFuzz 2 | on: [pull_request] 3 | jobs: 4 | Fuzzing: 5 | runs-on: ubuntu-latest 6 | permissions: 7 | security-events: write 8 | steps: 9 | - name: Build Fuzzers 10 | id: build 11 | uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master 12 | with: 13 | oss-fuzz-project-name: 'fast_float' 14 | language: c++ 15 | - name: Run Fuzzers 16 | uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master 17 | with: 18 | oss-fuzz-project-name: 'fast_float' 19 | language: c++ 20 | fuzz-seconds: 300 21 | output-sarif: true 22 | - name: Upload Crash 23 | uses: actions/upload-artifact@v4 24 | if: failure() && steps.build.outcome == 'success' 25 | with: 26 | name: artifacts 27 | path: ./out/artifacts 28 | - name: Upload Sarif 29 | if: always() && steps.build.outcome == 'success' 30 | uses: github/codeql-action/upload-sarif@v3 31 | with: 32 | # Path to SARIF file relative to the root of the repository 33 | sarif_file: cifuzz-sarif/results.sarif 34 | checkout_path: cifuzz-sarif 35 | category: CIFuzz 36 | -------------------------------------------------------------------------------- /.github/workflows/lint_and_format_check.yml: -------------------------------------------------------------------------------- 1 | name: Lint and format 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | branches: 11 | - main 12 | paths-ignore: 13 | - '**.md' 14 | - 'docs/**' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | lint-and-format: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@9a9194f87191a7e9055e3e9b95b8cfb13023bb08 # v4.1.7 28 | 29 | - name: Run clang-format 30 | uses: jidicula/clang-format-action@4726374d1aa3c6aecf132e5197e498979588ebc8 # v4.15.0 31 | with: 32 | clang-format-version: '17' 33 | 34 | -------------------------------------------------------------------------------- /.github/workflows/msys2-clang.yml: -------------------------------------------------------------------------------- 1 | name: MSYS2-CLANG-CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | windows-mingw: 7 | name: ${{ matrix.msystem }} 8 | runs-on: windows-latest 9 | defaults: 10 | run: 11 | shell: msys2 {0} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - msystem: "MINGW64" 17 | install: mingw-w64-x86_64-libxml2 mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-clang 18 | type: Release 19 | - msystem: "MINGW32" 20 | install: mingw-w64-i686-libxml2 mingw-w64-i686-cmake mingw-w64-i686-ninja mingw-w64-i686-clang 21 | type: Release 22 | env: 23 | CMAKE_GENERATOR: Ninja 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: msys2/setup-msys2@v2 28 | with: 29 | update: true 30 | msystem: ${{ matrix.msystem }} 31 | install: ${{ matrix.install }} 32 | - name: Prepare build dir 33 | run: mkdir build 34 | - name: Configure 35 | run: cd build && cmake -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DFASTFLOAT_TEST=ON .. 36 | - name: Build 37 | run: cmake --build build 38 | - name: Run basic tests 39 | run: cd build && ctest --output-on-failure -R basictest 40 | -------------------------------------------------------------------------------- /.github/workflows/msys2.yml: -------------------------------------------------------------------------------- 1 | name: MSYS2-CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | windows-mingw: 7 | name: ${{ matrix.msystem }} 8 | runs-on: windows-latest 9 | defaults: 10 | run: 11 | shell: msys2 {0} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - msystem: "MINGW64" 17 | install: mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-gcc 18 | type: Release 19 | - msystem: "MINGW32" 20 | install: mingw-w64-i686-cmake mingw-w64-i686-ninja mingw-w64-i686-gcc 21 | type: Release 22 | - msystem: "MINGW64" 23 | install: mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-gcc 24 | type: Debug 25 | - msystem: "MINGW32" 26 | install: mingw-w64-i686-cmake mingw-w64-i686-ninja mingw-w64-i686-gcc 27 | type: Debug 28 | env: 29 | CMAKE_GENERATOR: Ninja 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: msys2/setup-msys2@v2 34 | with: 35 | update: true 36 | msystem: ${{ matrix.msystem }} 37 | install: ${{ matrix.install }} 38 | - name: Prepare build dir 39 | run: mkdir build 40 | - name: Configure 41 | run: cd build && cmake -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DFASTFLOAT_TEST=ON .. 42 | - name: Build 43 | run: cmake --build build 44 | - name: Run basic tests 45 | run: cd build && ctest --output-on-failure -R basictest 46 | -------------------------------------------------------------------------------- /.github/workflows/on-release.yml: -------------------------------------------------------------------------------- 1 | name: On Release 2 | 3 | # By default, a workflow only has read permissions. 4 | # Add the needed permission to write release assets 5 | permissions: 6 | contents: write 7 | 8 | on: 9 | release: 10 | types: 11 | - published 12 | 13 | jobs: 14 | build: 15 | name: Add Release Assets 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Amalgamate fast_float.h 22 | run: | 23 | mkdir build 24 | mkdir build/fast_float 25 | python3 ./script/amalgamate.py > build/fast_float/fast_float.h 26 | 27 | - name: Test Amalgamation 28 | run: | 29 | cp tests/string_test.cpp build/ 30 | cd build 31 | g++ string_test.cpp 32 | 33 | - name: Upload Release Asset 34 | uses: actions/upload-release-asset@v1 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | with: 38 | upload_url: ${{ github.event.release.upload_url }} 39 | asset_path: build/fast_float/fast_float.h 40 | asset_name: fast_float.h 41 | asset_content_type: text/plain 42 | -------------------------------------------------------------------------------- /.github/workflows/s390x.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu s390x (GCC 11) 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: uraimo/run-on-arch-action@v3 17 | name: Test 18 | id: runcmd 19 | with: 20 | arch: s390x 21 | githubToken: ${{ github.token }} 22 | distro: ubuntu_latest 23 | install: | 24 | apt-get update -q -y 25 | apt-get install -y cmake make g++ 26 | run: | 27 | cmake -DCMAKE_BUILD_TYPE=Release -B build 28 | cmake --build build -j=2 29 | ctest --output-on-failure --test-dir build 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu20-cxx20.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 20.04 CI (C++20) 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ubuntu-build: 7 | runs-on: ubuntu-20.04 8 | strategy: 9 | fail-fast: false 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Use cmake 13 | run: | 14 | mkdir build && 15 | cd build && 16 | cmake -DFASTFLOAT_CXX_STANDARD=20 -DFASTFLOAT_TEST=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. && 17 | cmake --build . && 18 | ctest --output-on-failure && 19 | cmake --install . 20 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu20-fastmath.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 20.04 CI (GCC 9, fast-math) 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ubuntu-build: 7 | runs-on: ubuntu-20.04 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Use cmake 11 | run: | 12 | mkdir build && 13 | cd build && 14 | cmake -DCMAKE_CXX_FLAGS="-ffast-math" -DFASTFLOAT_TEST=ON .. && 15 | cmake --build . && 16 | ctest --output-on-failure 17 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu20.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 20.04 CI (GCC 9) 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ubuntu-build: 7 | runs-on: ubuntu-20.04 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Use cmake 11 | run: | 12 | mkdir build && 13 | cd build && 14 | cmake ${{matrix.cxx}} ${{matrix.arch}} -DFASTFLOAT_TEST=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. && 15 | cmake --build . && 16 | ctest --output-on-failure && 17 | cmake --install . && 18 | cd ../tests/installation_tests/find && 19 | mkdir build && cd build && cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../build/destination .. && cmake --build . && 20 | cd ../../issue72_installation && 21 | mkdir build && cd build && cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../build/destination .. && cmake --build . 22 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu22-clang.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 22.04 CI (clang 14) 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ubuntu-build: 7 | runs-on: ubuntu-22.04 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Install clang++-14 11 | run: sudo apt-get install -y clang++-14 12 | - name: Use cmake 13 | run: | 14 | mkdir build && 15 | cd build && 16 | CXX=clang++-14 cmake -DFASTFLOAT_TEST=ON .. && 17 | cmake --build . && 18 | ctest --output-on-failure 19 | - name: Use cmake CXX20 20 | run: | 21 | mkdir build20 && 22 | cd build20 && 23 | CXX=clang++-14 cmake -DFASTFLOAT_CONSTEXPR_TESTS=ON -DFASTFLOAT_CXX_STANDARD=20 -DFASTFLOAT_TEST=ON .. && 24 | cmake --build . && 25 | ctest --output-on-failure -------------------------------------------------------------------------------- /.github/workflows/ubuntu22-gcc12.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 22.04 CI (GCC 12) 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ubuntu-build: 7 | runs-on: ubuntu-22.04 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Use cmake 11 | run: | 12 | mkdir build && 13 | cd build && 14 | CXX=g++-12 CXXFLAGS=-Werror cmake -DFASTFLOAT_TEST=ON .. && 15 | cmake --build . && 16 | ctest --output-on-failure 17 | - name: Use cmake CXX20 18 | run: | 19 | mkdir build20 && 20 | cd build20 && 21 | CXX=g++-12 CXXFLAGS=-Werror cmake -DFASTFLOAT_CONSTEXPR_TESTS=ON -DFASTFLOAT_CXX_STANDARD=20 -DFASTFLOAT_TEST=ON .. && 22 | cmake --build . && 23 | ctest --output-on-failure -------------------------------------------------------------------------------- /.github/workflows/ubuntu22-sanitize.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 22.04 CI Sanitized (GCC 11) 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ubuntu-build: 7 | runs-on: ubuntu-22.04 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Use cmake 11 | run: | 12 | mkdir build && 13 | cd build && 14 | cmake -DFASTFLOAT_TEST=ON -D FASTFLOAT_SANITIZE=ON .. && 15 | cmake --build . && 16 | ctest --output-on-failure -------------------------------------------------------------------------------- /.github/workflows/ubuntu22.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 22.04 CI (GCC 11) 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ubuntu-build: 7 | runs-on: ubuntu-22.04 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Use cmake 11 | run: | 12 | mkdir build && 13 | cd build && 14 | cmake -DFASTFLOAT_TEST=ON .. && 15 | cmake --build . && 16 | ctest --output-on-failure -------------------------------------------------------------------------------- /.github/workflows/ubuntu24.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 24.04 CI (GCC 13) 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ubuntu-build: 7 | runs-on: ubuntu-24.04 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Use cmake 11 | run: | 12 | set -xe 13 | cmake -B build \ 14 | -DFASTFLOAT_TEST=ON \ 15 | -DFASTFLOAT_BENCHMARKS=ON \ 16 | -DCMAKE_CXX_FLAGS=' -Werror -Wundef ' 17 | cmake --build build --parallel 18 | ( cd build ; ctest --output-on-failure ) 19 | - name: Use cmake CXX23 20 | run: | 21 | set -xe 22 | cmake -B build20 \ 23 | -DFASTFLOAT_TEST=ON \ 24 | -DFASTFLOAT_CONSTEXPR_TESTS=ON \ 25 | -DFASTFLOAT_FIXEDWIDTH_TESTS=ON \ 26 | -DFASTFLOAT_CXX_STANDARD=23 \ 27 | -DCMAKE_CXX_FLAGS=' -Werror -Wundef ' 28 | cmake --build build20 --parallel 29 | ( cd build20 ; ctest --output-on-failure ) 30 | -------------------------------------------------------------------------------- /.github/workflows/vs17-arm-ci.yml: -------------------------------------------------------------------------------- 1 | name: VS17-ARM-CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ci: 7 | name: vs17/${{matrix.arch}}/${{matrix.cfg}} 8 | runs-on: windows-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | include: 13 | - {gen: Visual Studio 17 2022, arch: ARM64, cfg: Release} 14 | - {gen: Visual Studio 17 2022, arch: ARM64, cfg: Debug} 15 | steps: 16 | - name: checkout 17 | uses: actions/checkout@v4 18 | - name: configure 19 | run: | 20 | cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DCMAKE_CROSSCOMPILING=1 -DFASTFLOAT_TEST=ON 21 | - name: build 22 | run: | 23 | cmake --build build --verbose --config ${{matrix.cfg}} --parallel 24 | # disabled because it requires a toolchain 25 | #- name: test 26 | # run: | 27 | # cd build && 28 | # ctest --output-on-failure -C ${{matrix.cfg}} 29 | -------------------------------------------------------------------------------- /.github/workflows/vs17-ci.yml: -------------------------------------------------------------------------------- 1 | name: VS17-CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ci: 7 | name: vs17/${{matrix.arch}}/${{matrix.cfg}} 8 | runs-on: windows-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | include: 13 | - {gen: Visual Studio 17 2022, arch: Win32, cfg: Release} 14 | #- {gen: Visual Studio 17 2022, arch: Win32, cfg: Debug} 15 | - {gen: Visual Studio 17 2022, arch: x64, cfg: Release} 16 | - {gen: Visual Studio 17 2022, arch: x64, cfg: Debug} 17 | steps: 18 | - name: checkout 19 | uses: actions/checkout@v4 20 | - name: configure 21 | run: | 22 | cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DFASTFLOAT_BENCHMARKS=ON -DFASTFLOAT_TEST=ON -DCMAKE_INSTALL_PREFIX:PATH=destination 23 | - name: build 24 | run: | 25 | cmake --build build --verbose --config ${{matrix.cfg}} --parallel 26 | - name: test 27 | run: | 28 | cd build && 29 | ctest --output-on-failure -C ${{matrix.cfg}} 30 | - name: install 31 | run: | 32 | cd build && 33 | cmake --install . 34 | - name: test install (find) 35 | run: | 36 | cd tests/installation_tests/find && 37 | cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DCMAKE_INSTALL_PREFIX:PATH=../../../build/destination && 38 | cmake --build build --verbose --config ${{matrix.cfg}} --parallel 39 | - name: test install (issue 72) 40 | run: | 41 | cd tests/installation_tests/issue72_installation && 42 | cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DCMAKE_INSTALL_PREFIX:PATH=../../../build/destination && 43 | cmake --build build --verbose --config ${{matrix.cfg}} --parallel 44 | -------------------------------------------------------------------------------- /.github/workflows/vs17-clang-ci.yml: -------------------------------------------------------------------------------- 1 | name: VS17-CLANG-CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ci: 7 | name: vs17/${{matrix.arch}}/${{matrix.cfg}} 8 | runs-on: windows-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | include: 13 | - {gen: Visual Studio 17 2022, arch: Win32, cfg: Release} 14 | - {gen: Visual Studio 17 2022, arch: Win32, cfg: Debug} 15 | - {gen: Visual Studio 17 2022, arch: x64, cfg: Release} 16 | - {gen: Visual Studio 17 2022, arch: x64, cfg: Debug} 17 | steps: 18 | - name: checkout 19 | uses: actions/checkout@v4 20 | - name: Configure 21 | run: | 22 | cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DFASTFLOAT_BENCHMARKS=ON -T ClangCL -DFASTFLOAT_TEST=ON 23 | - name: Build 24 | run: cmake --build build --config ${{matrix.cfg}} --parallel --verbose 25 | - name: Run basic tests 26 | run: | 27 | cd build 28 | ctest -C ${{matrix.cfg}} --output-on-failure -R basictest 29 | -------------------------------------------------------------------------------- /.github/workflows/vs17-cxx20.yml: -------------------------------------------------------------------------------- 1 | name: VS17-CI C++20 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ci: 7 | name: vs17/${{matrix.arch}}/${{matrix.cfg}} 8 | runs-on: windows-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | include: 13 | - {gen: Visual Studio 17 2022, arch: Win32, cfg: Release} 14 | - {gen: Visual Studio 17 2022, arch: Win32, cfg: Debug} 15 | - {gen: Visual Studio 17 2022, arch: x64, cfg: Release} 16 | - {gen: Visual Studio 17 2022, arch: x64, cfg: Debug} 17 | steps: 18 | - name: checkout 19 | uses: actions/checkout@v4 20 | - name: configure 21 | run: >- 22 | cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} 23 | -DFASTFLOAT_CXX_STANDARD=20 24 | -DFASTFLOAT_TEST=ON 25 | -DFASTFLOAT_CONSTEXPR_TESTS=ON 26 | -DCMAKE_INSTALL_PREFIX:PATH=destination 27 | - name: build 28 | run: | 29 | cmake --build build --verbose --config ${{matrix.cfg}} --parallel 30 | - name: test 31 | run: | 32 | cd build && 33 | ctest --output-on-failure -C ${{matrix.cfg}} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | Testing/* 3 | .cache/ 4 | compile_commands.json 5 | bazel-* 6 | 7 | # Visual studio 8 | .vs/ 9 | Debug/ 10 | Release/ 11 | /out/ 12 | *.sln 13 | *.vcxproj 14 | *.vcxproj.filters 15 | *.vcxproj.user 16 | *.psess 17 | *.vspx 18 | *.vsp 19 | *.diagsession 20 | *.hint 21 | 22 | # VS CMake 23 | /out/ 24 | /CMakeSettings.json 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | dist: bionic 4 | 5 | cache: 6 | directories: 7 | - $HOME/.dep_cache 8 | 9 | env: 10 | global: 11 | - fastfloat_DEPENDENCY_CACHE_DIR=$HOME/.dep_cache 12 | 13 | services: 14 | - docker 15 | 16 | # the ppc64le and s390x images use cmake 3.10, but fast_float requires 3.11. 17 | # so we compile cmake from source in those images. 18 | # - tried the kitware ppa but that is using 3.10 as well 19 | # - tried also using snap to get a more recent version but that failed with 20 | # udev errors. 21 | 22 | matrix: 23 | include: 24 | - arch: ppc64le 25 | os: linux 26 | env: 27 | - CMAKE_SRC="https://github.com/Kitware/CMake/releases/download/v3.11.4/cmake-3.11.4.tar.gz" 28 | 29 | - arch: s390x 30 | os: linux 31 | env: 32 | - CMAKE_SRC="https://github.com/Kitware/CMake/releases/download/v3.11.4/cmake-3.11.4.tar.gz" 33 | 34 | - arch: amd64 35 | os: linux 36 | 37 | - arch: amd64 38 | os: linux 39 | addons: 40 | apt: 41 | sources: 42 | - ubuntu-toolchain-r-test 43 | packages: 44 | - g++-8 45 | env: 46 | - COMPILER="CC=gcc-8 && CXX=g++-8" 47 | compiler: gcc-8 48 | 49 | - arch: amd64 50 | os: linux 51 | addons: 52 | apt: 53 | sources: 54 | - ubuntu-toolchain-r-test 55 | packages: 56 | - g++-9 57 | env: 58 | - COMPILER="CC=gcc-9 && CXX=g++-9" 59 | compiler: gcc-9 60 | 61 | - arch: amd64 62 | os: linux 63 | addons: 64 | apt: 65 | sources: 66 | - ubuntu-toolchain-r-test 67 | packages: 68 | - g++-10 69 | env: 70 | - COMPILER="CC=gcc-10 && CXX=g++-10" 71 | compiler: gcc-10 72 | 73 | - arch: amd64 74 | os: linux 75 | addons: 76 | apt: 77 | sources: 78 | - ubuntu-toolchain-r-test 79 | packages: 80 | - g++-10 81 | env: 82 | - COMPILER="CC=gcc-10 && CXX=g++-10" 83 | - SANITIZE="on" 84 | compiler: gcc-10-sanitize 85 | 86 | - arch: amd64 87 | os: linux 88 | addons: 89 | apt: 90 | sources: 91 | - ubuntu-toolchain-r-test 92 | packages: 93 | - g++-10 94 | env: 95 | - COMPILER="CC=gcc-10 && CXX=g++-10" 96 | - STATIC="on" 97 | acompiler: gcc-10-static 98 | 99 | - arch: amd64 100 | os: linux 101 | addons: 102 | apt: 103 | sources: 104 | - llvm-toolchain-bionic-6.0 105 | packages: 106 | - clang-6.0 107 | env: 108 | - COMPILER="CC=clang-6.0 && CXX=clang++-6.0" 109 | compiler: clang-6 110 | 111 | - arch: amd64 112 | os: linux 113 | addons: 114 | apt: 115 | sources: 116 | - llvm-toolchain-bionic-7 117 | packages: 118 | - clang-7 119 | env: 120 | - COMPILER="CC=clang-7 && CXX=clang++-7" 121 | compiler: clang-7 122 | 123 | - arch: amd64 124 | os: linux 125 | addons: 126 | apt: 127 | sources: 128 | - llvm-toolchain-bionic-8 129 | packages: 130 | - clang-8 131 | env: 132 | - COMPILER="CC=clang-8 && CXX=clang++-8" 133 | compiler: clang-8 134 | 135 | - arch: amd64 136 | os: linux 137 | addons: 138 | apt: 139 | sources: 140 | - llvm-toolchain-bionic-9 141 | packages: 142 | - clang-9 143 | env: 144 | - COMPILER="CC=clang-9 && CXX=clang++-9" 145 | compiler: clang-9 146 | 147 | - arch: amd64 148 | os: linux 149 | addons: 150 | apt: 151 | packages: 152 | - clang-10 153 | sources: 154 | - ubuntu-toolchain-r-test 155 | - sourceline: 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-10 main' 156 | key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' 157 | env: 158 | - COMPILER="CC=clang-10 && CXX=clang++-10" 159 | compiler: clang-10 160 | 161 | - arch: amd64 162 | os: linux 163 | addons: 164 | apt: 165 | packages: 166 | - clang-10 167 | sources: 168 | - ubuntu-toolchain-r-test 169 | - sourceline: 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-10 main' 170 | key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' 171 | env: 172 | - COMPILER="CC=clang-10 && CXX=clang++-10" 173 | - STATIC="on" 174 | compiler: clang-10-static 175 | 176 | - arch: amd64 177 | os: linux 178 | addons: 179 | apt: 180 | packages: 181 | - clang-10 182 | sources: 183 | - ubuntu-toolchain-r-test 184 | - sourceline: 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-10 main' 185 | key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' 186 | env: 187 | - COMPILER="CC=clang-10 && CXX=clang++-10" 188 | - SANITIZE="on" 189 | compiler: clang-10-sanitize 190 | 191 | - arch: amd64 192 | os: linux 193 | env: 194 | - TOOLCHAIN="mips64" 195 | 196 | - arch: amd64 197 | os: linux 198 | env: 199 | - TOOLCHAIN="riscv64" 200 | 201 | before_install: 202 | - eval "${COMPILER}" 203 | - | 204 | if [ "$TOOLCHAIN" != "" ] ; then 205 | docker pull ahuszagh/cross:"$TOOLCHAIN" 206 | fi 207 | 208 | install: 209 | - | 210 | if [ "$CMAKE_SRC" != "" ] ; then 211 | set -x 212 | set -e 213 | sudo -E apt remove --purge cmake 214 | sudo -E apt-get update 215 | sudo -E apt-get install -y build-essential libssl-dev 216 | mkdir cmake_src 217 | pushd cmake_src 218 | wget "$CMAKE_SRC" 219 | tar xfz $(basename "$CMAKE_SRC") 220 | pushd $(basename "$CMAKE_SRC" | sed "s:.tar.gz::") 221 | ./bootstrap 222 | make -j2 223 | sudo make install 224 | popd 225 | popd 226 | set +x 227 | fi 228 | - echo ${PATH} 229 | - which cmake 230 | - cmake --version 231 | - which ${CC} 232 | - ${CC} --version 233 | - which ${CXX} 234 | - ${CXX} --version 235 | 236 | script: 237 | - | 238 | if [ "$TOOLCHAIN" != "" ] ; then 239 | docker run -v "$(pwd)":/ff ahuszagh/cross:"$TOOLCHAIN" /bin/bash -c "cd ff && ci/script.sh $TOOLCHAIN" 240 | else 241 | ci/script.sh 242 | fi 243 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Daniel Lemire 2 | João Paulo Magalhaes 3 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | cc_library( 2 | name = "fast_float", 3 | hdrs = glob(["include/fast_float/*.h"]), 4 | strip_include_prefix = "include", 5 | visibility = ["//visibility:public"], 6 | ) 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9) 2 | 3 | project(fast_float VERSION 8.0.2 LANGUAGES CXX) 4 | set(FASTFLOAT_CXX_STANDARD 11 CACHE STRING "the C++ standard to use for fastfloat") 5 | set(CMAKE_CXX_STANDARD ${FASTFLOAT_CXX_STANDARD}) 6 | option(FASTFLOAT_TEST "Enable tests" OFF) 7 | 8 | if(FASTFLOAT_TEST) 9 | enable_testing() 10 | add_subdirectory(tests) 11 | else(FASTFLOAT_TEST) 12 | message(STATUS "Tests are disabled. Set FASTFLOAT_TEST to ON to run tests.") 13 | endif(FASTFLOAT_TEST) 14 | 15 | option(FASTFLOAT_SANITIZE "Sanitize addresses" OFF) 16 | 17 | if (NOT CMAKE_BUILD_TYPE) 18 | if(FASTFLOAT_SANITIZE) 19 | set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) 20 | else() 21 | message(STATUS "No build type selected, default to Release") 22 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) 23 | endif() 24 | endif() 25 | 26 | option(FASTFLOAT_INSTALL "Enable install" ON) 27 | 28 | if(FASTFLOAT_INSTALL) 29 | include(GNUInstallDirs) 30 | endif() 31 | 32 | add_library(fast_float INTERFACE) 33 | 34 | 35 | option(FASTFLOAT_BENCHMARKS "Enable benchmarks" OFF) 36 | if(FASTFLOAT_BENCHMARKS) 37 | add_subdirectory(benchmarks) 38 | else(FASTFLOAT_BENCHMARKS) 39 | message(STATUS "Benchmarks are disabled. Set FASTFLOAT_BENCHMARKS to ON to build benchmarks (assumes C++17).") 40 | endif(FASTFLOAT_BENCHMARKS) 41 | 42 | 43 | add_library(FastFloat::fast_float ALIAS fast_float) 44 | target_include_directories( 45 | fast_float 46 | INTERFACE 47 | $ 48 | $ 49 | ) 50 | target_compile_features(fast_float INTERFACE cxx_std_11) 51 | if(FASTFLOAT_SANITIZE) 52 | target_compile_options(fast_float INTERFACE -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=all) 53 | target_link_libraries(fast_float INTERFACE -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=all) 54 | if (CMAKE_COMPILER_IS_GNUCC) 55 | target_link_libraries(fast_float INTERFACE -fuse-ld=gold) 56 | endif() 57 | endif() 58 | 59 | include(CheckCXXCompilerFlag) 60 | unset(FASTFLOAT_COMPILER_SUPPORTS_PERMISSIVE) 61 | CHECK_CXX_COMPILER_FLAG(/permissive- FASTFLOAT_COMPILER_SUPPORTS_PERMISSIVE) 62 | 63 | if(FASTFLOAT_COMPILER_SUPPORTS_PERMISSIVE) 64 | target_compile_options(fast_float INTERFACE /permissive-) 65 | endif() 66 | 67 | if(FASTFLOAT_INSTALL) 68 | include(CMakePackageConfigHelpers) 69 | 70 | set(FASTFLOAT_VERSION_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/module/FastFloatConfigVersion.cmake") 71 | set(FASTFLOAT_PROJECT_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/module/FastFloatConfig.cmake") 72 | set(FASTFLOAT_CONFIG_INSTALL_DIR "${CMAKE_INSTALL_DATAROOTDIR}/cmake/FastFloat") 73 | 74 | if(${CMAKE_VERSION} VERSION_LESS "3.14") 75 | write_basic_package_version_file("${FASTFLOAT_VERSION_CONFIG}" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion) 76 | else() 77 | write_basic_package_version_file("${FASTFLOAT_VERSION_CONFIG}" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ARCH_INDEPENDENT) 78 | endif() 79 | configure_package_config_file("cmake/config.cmake.in" 80 | "${FASTFLOAT_PROJECT_CONFIG}" 81 | INSTALL_DESTINATION "${FASTFLOAT_CONFIG_INSTALL_DIR}") 82 | 83 | install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/fast_float" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 84 | install(FILES "${FASTFLOAT_PROJECT_CONFIG}" "${FASTFLOAT_VERSION_CONFIG}" DESTINATION "${FASTFLOAT_CONFIG_INSTALL_DIR}") 85 | install(EXPORT ${PROJECT_NAME}-targets NAMESPACE FastFloat:: DESTINATION "${FASTFLOAT_CONFIG_INSTALL_DIR}") 86 | 87 | install(TARGETS fast_float 88 | EXPORT ${PROJECT_NAME}-targets 89 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 90 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 91 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 92 | ) 93 | endif() 94 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Eugene Golushkov 2 | Maksim Kita 3 | Marcin Wojdyr 4 | Neal Richardson 5 | Tim Paine 6 | Fabio Pellacini 7 | Lénárd Szolnoki 8 | Jan Pharago 9 | Maya Warrier 10 | Taha Khokhar 11 | Anders Dalvander 12 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2021 The fast_float authors 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /LICENSE-BOOST: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 The fast_float authors 4 | 5 | Permission is hereby granted, free of charge, to any 6 | person obtaining a copy of this software and associated 7 | documentation files (the "Software"), to deal in the 8 | Software without restriction, including without 9 | limitation the rights to use, copy, modify, merge, 10 | publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software 12 | is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice 16 | shall be included in all copies or substantial portions 17 | of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 20 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 21 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 22 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 23 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 26 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | DEALINGS IN THE SOFTWARE. 28 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | """fast_float number parsing library: 4x faster than strtod""" 2 | 3 | module( 4 | name = "fast_float", 5 | version = "6.1.6", 6 | compatibility_level = 6, 7 | ) 8 | 9 | bazel_dep(name = "doctest", version = "2.4.11", dev_dependency = True) 10 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Please use the following contact information for reporting a vulnerability: 6 | 7 | - [Daniel Lemire](https://github.com/lemire) - daniel@lemire.me 8 | -------------------------------------------------------------------------------- /benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(realbenchmark benchmark.cpp) 2 | set_property( 3 | TARGET realbenchmark 4 | PROPERTY CXX_STANDARD 17) 5 | 6 | target_link_libraries(realbenchmark PUBLIC fast_float) 7 | include(ExternalProject) 8 | 9 | # Define the external project 10 | ExternalProject_Add(simple_fastfloat_benchmark 11 | GIT_REPOSITORY https://github.com/lemire/simple_fastfloat_benchmark.git 12 | GIT_TAG master # or specify a particular commit/tag/branch 13 | SOURCE_DIR ${CMAKE_BINARY_DIR}/simple_fastfloat_benchmark 14 | BINARY_DIR ${CMAKE_BINARY_DIR}/simple_fastfloat_benchmark-build 15 | CONFIGURE_COMMAND "" 16 | BUILD_COMMAND "" 17 | INSTALL_COMMAND "" 18 | ) 19 | set(DATA_DIR ${CMAKE_BINARY_DIR}/simple_fastfloat_benchmark/data) 20 | 21 | add_custom_target(CopyData ALL 22 | COMMAND ${CMAKE_COMMAND} -E copy_directory ${DATA_DIR} ${CMAKE_CURRENT_BINARY_DIR}/data 23 | DEPENDS simple_fastfloat_benchmark 24 | ) 25 | add_dependencies(realbenchmark CopyData) 26 | target_compile_definitions(realbenchmark PUBLIC BENCHMARK_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data") 27 | -------------------------------------------------------------------------------- /benchmarks/benchmark.cpp: -------------------------------------------------------------------------------- 1 | #if defined(__linux__) || (__APPLE__ && __aarch64__) 2 | #define USING_COUNTERS 3 | #endif 4 | #include "event_counter.h" 5 | #include 6 | #include "fast_float/fast_float.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | template 26 | double findmax_fastfloat64(std::vector> &s) { 27 | double answer = 0; 28 | double x = 0; 29 | for (auto &st : s) { 30 | auto [p, ec] = fast_float::from_chars(st.data(), st.data() + st.size(), x); 31 | if (p == st.data()) { 32 | throw std::runtime_error("bug in findmax_fastfloat"); 33 | } 34 | answer = answer > x ? answer : x; 35 | } 36 | return answer; 37 | } 38 | 39 | template 40 | double findmax_fastfloat32(std::vector> &s) { 41 | float answer = 0; 42 | float x = 0; 43 | for (auto &st : s) { 44 | auto [p, ec] = fast_float::from_chars(st.data(), st.data() + st.size(), x); 45 | if (p == st.data()) { 46 | throw std::runtime_error("bug in findmax_fastfloat"); 47 | } 48 | answer = answer > x ? answer : x; 49 | } 50 | return answer; 51 | } 52 | 53 | event_collector collector{}; 54 | 55 | #ifdef USING_COUNTERS 56 | template 57 | std::vector 58 | time_it_ns(std::vector> &lines, T const &function, 59 | size_t repeat) { 60 | std::vector aggregate; 61 | bool printed_bug = false; 62 | for (size_t i = 0; i < repeat; i++) { 63 | collector.start(); 64 | double ts = function(lines); 65 | if (ts == 0 && !printed_bug) { 66 | printf("bug\n"); 67 | printed_bug = true; 68 | } 69 | aggregate.push_back(collector.end()); 70 | } 71 | return aggregate; 72 | } 73 | 74 | void pretty_print(double volume, size_t number_of_floats, std::string name, 75 | std::vector events) { 76 | double volumeMB = volume / (1024. * 1024.); 77 | double average_ns{0}; 78 | double min_ns{DBL_MAX}; 79 | double cycles_min{DBL_MAX}; 80 | double instructions_min{DBL_MAX}; 81 | double cycles_avg{0}; 82 | double instructions_avg{0}; 83 | double branches_min{0}; 84 | double branches_avg{0}; 85 | double branch_misses_min{0}; 86 | double branch_misses_avg{0}; 87 | for (event_count e : events) { 88 | double ns = e.elapsed_ns(); 89 | average_ns += ns; 90 | min_ns = min_ns < ns ? min_ns : ns; 91 | 92 | double cycles = e.cycles(); 93 | cycles_avg += cycles; 94 | cycles_min = cycles_min < cycles ? cycles_min : cycles; 95 | 96 | double instructions = e.instructions(); 97 | instructions_avg += instructions; 98 | instructions_min = 99 | instructions_min < instructions ? instructions_min : instructions; 100 | 101 | double branches = e.branches(); 102 | branches_avg += branches; 103 | branches_min = branches_min < branches ? branches_min : branches; 104 | 105 | double branch_misses = e.missed_branches(); 106 | branch_misses_avg += branch_misses; 107 | branch_misses_min = 108 | branch_misses_min < branch_misses ? branch_misses_min : branch_misses; 109 | } 110 | cycles_avg /= events.size(); 111 | instructions_avg /= events.size(); 112 | average_ns /= events.size(); 113 | branches_avg /= events.size(); 114 | printf("%-40s: %8.2f MB/s (+/- %.1f %%) ", name.data(), 115 | volumeMB * 1000000000 / min_ns, 116 | (average_ns - min_ns) * 100.0 / average_ns); 117 | printf("%8.2f Mfloat/s ", number_of_floats * 1000 / min_ns); 118 | if (instructions_min > 0) { 119 | printf(" %8.2f i/B %8.2f i/f (+/- %.1f %%) ", instructions_min / volume, 120 | instructions_min / number_of_floats, 121 | (instructions_avg - instructions_min) * 100.0 / instructions_avg); 122 | 123 | printf(" %8.2f c/B %8.2f c/f (+/- %.1f %%) ", cycles_min / volume, 124 | cycles_min / number_of_floats, 125 | (cycles_avg - cycles_min) * 100.0 / cycles_avg); 126 | printf(" %8.2f i/c ", instructions_min / cycles_min); 127 | printf(" %8.2f b/f ", branches_avg / number_of_floats); 128 | printf(" %8.2f bm/f ", branch_misses_avg / number_of_floats); 129 | printf(" %8.2f GHz ", cycles_min / min_ns); 130 | } 131 | printf("\n"); 132 | } 133 | #else 134 | template 135 | std::pair 136 | time_it_ns(std::vector> &lines, T const &function, 137 | size_t repeat) { 138 | std::chrono::high_resolution_clock::time_point t1, t2; 139 | double average = 0; 140 | double min_value = DBL_MAX; 141 | bool printed_bug = false; 142 | for (size_t i = 0; i < repeat; i++) { 143 | t1 = std::chrono::high_resolution_clock::now(); 144 | double ts = function(lines); 145 | if (ts == 0 && !printed_bug) { 146 | printf("bug\n"); 147 | printed_bug = true; 148 | } 149 | t2 = std::chrono::high_resolution_clock::now(); 150 | double dif = 151 | std::chrono::duration_cast(t2 - t1).count(); 152 | average += dif; 153 | min_value = min_value < dif ? min_value : dif; 154 | } 155 | average /= repeat; 156 | return std::make_pair(min_value, average); 157 | } 158 | 159 | void pretty_print(double volume, size_t number_of_floats, std::string name, 160 | std::pair result) { 161 | double volumeMB = volume / (1024. * 1024.); 162 | printf("%-40s: %8.2f MB/s (+/- %.1f %%) ", name.data(), 163 | volumeMB * 1000000000 / result.first, 164 | (result.second - result.first) * 100.0 / result.second); 165 | printf("%8.2f Mfloat/s ", number_of_floats * 1000 / result.first); 166 | printf(" %8.2f ns/f \n", double(result.first) / number_of_floats); 167 | } 168 | #endif 169 | 170 | // this is okay, all chars are ASCII 171 | inline std::u16string widen(std::string line) { 172 | std::u16string u16line; 173 | u16line.resize(line.size()); 174 | for (size_t i = 0; i < line.size(); ++i) { 175 | u16line[i] = char16_t(line[i]); 176 | } 177 | return u16line; 178 | } 179 | 180 | std::vector widen(const std::vector &lines) { 181 | std::vector u16lines; 182 | u16lines.reserve(lines.size()); 183 | for (auto const &line : lines) { 184 | u16lines.push_back(widen(line)); 185 | } 186 | return u16lines; 187 | } 188 | 189 | void process(std::vector &lines, size_t volume) { 190 | size_t repeat = 1000; 191 | double volumeMB = volume / (1024. * 1024.); 192 | std::cout << "ASCII volume = " << volumeMB << " MB " << std::endl; 193 | pretty_print(volume, lines.size(), "fastfloat (64)", 194 | time_it_ns(lines, findmax_fastfloat64, repeat)); 195 | pretty_print(volume, lines.size(), "fastfloat (32)", 196 | time_it_ns(lines, findmax_fastfloat32, repeat)); 197 | 198 | std::vector lines16 = widen(lines); 199 | volume = 2 * volume; 200 | volumeMB = volume / (1024. * 1024.); 201 | std::cout << "UTF-16 volume = " << volumeMB << " MB " << std::endl; 202 | pretty_print(volume, lines.size(), "fastfloat (64)", 203 | time_it_ns(lines16, findmax_fastfloat64, repeat)); 204 | pretty_print(volume, lines.size(), "fastfloat (32)", 205 | time_it_ns(lines16, findmax_fastfloat32, repeat)); 206 | } 207 | 208 | void fileload(std::string filename) { 209 | std::ifstream inputfile(filename); 210 | if (!inputfile) { 211 | std::cerr << "can't open " << filename << std::endl; 212 | return; 213 | } 214 | std::cout << "#### " << std::endl; 215 | std::cout << "# reading " << filename << std::endl; 216 | std::cout << "#### " << std::endl; 217 | std::string line; 218 | std::vector lines; 219 | lines.reserve(10000); // let us reserve plenty of memory. 220 | size_t volume = 0; 221 | while (getline(inputfile, line)) { 222 | volume += line.size(); 223 | lines.push_back(line); 224 | } 225 | std::cout << "# read " << lines.size() << " lines " << std::endl; 226 | process(lines, volume); 227 | } 228 | 229 | int main(int argc, char **argv) { 230 | if (collector.has_events()) { 231 | std::cout << "# Using hardware counters" << std::endl; 232 | } else { 233 | #if defined(__linux__) || (__APPLE__ && __aarch64__) 234 | std::cout << "# Hardware counters not available, try to run in privileged " 235 | "mode (e.g., sudo)." 236 | << std::endl; 237 | #endif 238 | } 239 | if (argc > 1) { 240 | fileload(argv[1]); 241 | return EXIT_SUCCESS; 242 | } 243 | fileload(std::string(BENCHMARK_DATA_DIR) + "/canada.txt"); 244 | fileload(std::string(BENCHMARK_DATA_DIR) + "/mesh.txt"); 245 | return EXIT_SUCCESS; 246 | } 247 | -------------------------------------------------------------------------------- /benchmarks/event_counter.h: -------------------------------------------------------------------------------- 1 | #ifndef __EVENT_COUNTER_H 2 | #define __EVENT_COUNTER_H 3 | 4 | #include 5 | #ifndef _MSC_VER 6 | #include 7 | #endif 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "linux-perf-events.h" 16 | #ifdef __linux__ 17 | #include 18 | #endif 19 | 20 | #if (defined(__APPLE__) && __APPLE__) && (defined(__aarch64__) && __aarch64__) 21 | #include "apple_arm_events.h" 22 | #endif 23 | 24 | struct event_count { 25 | std::chrono::duration elapsed; 26 | std::vector event_counts; 27 | 28 | event_count() : elapsed(0), event_counts{0, 0, 0, 0, 0} {} 29 | 30 | event_count(const std::chrono::duration _elapsed, 31 | const std::vector _event_counts) 32 | : elapsed(_elapsed), event_counts(_event_counts) {} 33 | 34 | event_count(const event_count &other) 35 | : elapsed(other.elapsed), event_counts(other.event_counts) {} 36 | 37 | // The types of counters (so we can read the getter more easily) 38 | enum event_counter_types { 39 | CPU_CYCLES = 0, 40 | INSTRUCTIONS = 1, 41 | BRANCHES = 2, 42 | MISSED_BRANCHES = 3 43 | }; 44 | 45 | double elapsed_sec() const { 46 | return std::chrono::duration(elapsed).count(); 47 | } 48 | 49 | double elapsed_ns() const { 50 | return std::chrono::duration(elapsed).count(); 51 | } 52 | 53 | double cycles() const { 54 | return static_cast(event_counts[CPU_CYCLES]); 55 | } 56 | 57 | double instructions() const { 58 | return static_cast(event_counts[INSTRUCTIONS]); 59 | } 60 | 61 | double branches() const { 62 | return static_cast(event_counts[BRANCHES]); 63 | } 64 | 65 | double missed_branches() const { 66 | return static_cast(event_counts[MISSED_BRANCHES]); 67 | } 68 | 69 | event_count &operator=(const event_count &other) { 70 | this->elapsed = other.elapsed; 71 | this->event_counts = other.event_counts; 72 | return *this; 73 | } 74 | 75 | event_count operator+(const event_count &other) const { 76 | return event_count(elapsed + other.elapsed, 77 | { 78 | event_counts[0] + other.event_counts[0], 79 | event_counts[1] + other.event_counts[1], 80 | event_counts[2] + other.event_counts[2], 81 | event_counts[3] + other.event_counts[3], 82 | event_counts[4] + other.event_counts[4], 83 | }); 84 | } 85 | 86 | void operator+=(const event_count &other) { *this = *this + other; } 87 | }; 88 | 89 | struct event_aggregate { 90 | bool has_events = false; 91 | int iterations = 0; 92 | event_count total{}; 93 | event_count best{}; 94 | event_count worst{}; 95 | 96 | event_aggregate() = default; 97 | 98 | void operator<<(const event_count &other) { 99 | if (iterations == 0 || other.elapsed < best.elapsed) { 100 | best = other; 101 | } 102 | if (iterations == 0 || other.elapsed > worst.elapsed) { 103 | worst = other; 104 | } 105 | iterations++; 106 | total += other; 107 | } 108 | 109 | double elapsed_sec() const { return total.elapsed_sec() / iterations; } 110 | 111 | double elapsed_ns() const { return total.elapsed_ns() / iterations; } 112 | 113 | double cycles() const { return total.cycles() / iterations; } 114 | 115 | double instructions() const { return total.instructions() / iterations; } 116 | 117 | double branches() const { return total.branches() / iterations; } 118 | 119 | double missed_branches() const { 120 | return total.missed_branches() / iterations; 121 | } 122 | }; 123 | 124 | struct event_collector { 125 | event_count count{}; 126 | std::chrono::time_point start_clock{}; 127 | 128 | #if defined(__linux__) 129 | LinuxEvents linux_events; 130 | 131 | event_collector() 132 | : linux_events(std::vector{ 133 | PERF_COUNT_HW_CPU_CYCLES, PERF_COUNT_HW_INSTRUCTIONS, 134 | PERF_COUNT_HW_BRANCH_INSTRUCTIONS, // Retired branch instructions 135 | PERF_COUNT_HW_BRANCH_MISSES}) {} 136 | 137 | bool has_events() { return linux_events.is_working(); } 138 | #elif __APPLE__ && __aarch64__ 139 | performance_counters diff; 140 | 141 | event_collector() : diff(0) { setup_performance_counters(); } 142 | 143 | bool has_events() { return setup_performance_counters(); } 144 | #else 145 | event_collector() {} 146 | 147 | bool has_events() { return false; } 148 | #endif 149 | 150 | inline void start() { 151 | #if defined(__linux) 152 | linux_events.start(); 153 | #elif __APPLE__ && __aarch64__ 154 | if (has_events()) { 155 | diff = get_counters(); 156 | } 157 | #endif 158 | start_clock = std::chrono::steady_clock::now(); 159 | } 160 | 161 | inline event_count &end() { 162 | const auto end_clock = std::chrono::steady_clock::now(); 163 | #if defined(__linux) 164 | linux_events.end(count.event_counts); 165 | #elif __APPLE__ && __aarch64__ 166 | if (has_events()) { 167 | performance_counters end = get_counters(); 168 | diff = end - diff; 169 | } 170 | count.event_counts[0] = diff.cycles; 171 | count.event_counts[1] = diff.instructions; 172 | count.event_counts[2] = diff.branches; 173 | count.event_counts[3] = diff.missed_branches; 174 | count.event_counts[4] = 0; 175 | #endif 176 | count.elapsed = end_clock - start_clock; 177 | return count; 178 | } 179 | }; 180 | 181 | #endif 182 | -------------------------------------------------------------------------------- /benchmarks/linux-perf-events.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef __linux__ 3 | 4 | #include // for __NR_perf_event_open 5 | #include // for perf event constants 6 | #include // for ioctl 7 | #include // for syscall 8 | 9 | #include // for errno 10 | #include // for memset 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | template class LinuxEvents { 17 | int fd; 18 | bool working; 19 | perf_event_attr attribs{}; 20 | size_t num_events{}; 21 | std::vector temp_result_vec{}; 22 | std::vector ids{}; 23 | 24 | public: 25 | explicit LinuxEvents(std::vector config_vec) : fd(0), working(true) { 26 | memset(&attribs, 0, sizeof(attribs)); 27 | attribs.type = TYPE; 28 | attribs.size = sizeof(attribs); 29 | attribs.disabled = 1; 30 | attribs.exclude_kernel = 1; 31 | attribs.exclude_hv = 1; 32 | 33 | attribs.sample_period = 0; 34 | attribs.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID; 35 | const int pid = 0; // the current process 36 | const int cpu = -1; // all CPUs 37 | const unsigned long flags = 0; 38 | 39 | int group = -1; // no group 40 | num_events = config_vec.size(); 41 | ids.resize(config_vec.size()); 42 | uint32_t i = 0; 43 | for (auto config : config_vec) { 44 | attribs.config = config; 45 | int _fd = static_cast( 46 | syscall(__NR_perf_event_open, &attribs, pid, cpu, group, flags)); 47 | if (_fd == -1) { 48 | report_error("perf_event_open"); 49 | } 50 | ioctl(_fd, PERF_EVENT_IOC_ID, &ids[i++]); 51 | if (group == -1) { 52 | group = _fd; 53 | fd = _fd; 54 | } 55 | } 56 | 57 | temp_result_vec.resize(num_events * 2 + 1); 58 | } 59 | 60 | ~LinuxEvents() { 61 | if (fd != -1) { 62 | close(fd); 63 | } 64 | } 65 | 66 | inline void start() { 67 | if (fd != -1) { 68 | if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) { 69 | report_error("ioctl(PERF_EVENT_IOC_RESET)"); 70 | } 71 | 72 | if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) { 73 | report_error("ioctl(PERF_EVENT_IOC_ENABLE)"); 74 | } 75 | } 76 | } 77 | 78 | inline void end(std::vector &results) { 79 | if (fd != -1) { 80 | if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) { 81 | report_error("ioctl(PERF_EVENT_IOC_DISABLE)"); 82 | } 83 | 84 | if (read(fd, temp_result_vec.data(), temp_result_vec.size() * 8) == -1) { 85 | report_error("read"); 86 | } 87 | } 88 | // our actual results are in slots 1,3,5, ... of this structure 89 | for (uint32_t i = 1; i < temp_result_vec.size(); i += 2) { 90 | results[i / 2] = temp_result_vec[i]; 91 | } 92 | for (uint32_t i = 2; i < temp_result_vec.size(); i += 2) { 93 | if (ids[i / 2 - 1] != temp_result_vec[i]) { 94 | report_error("event mismatch"); 95 | } 96 | } 97 | } 98 | 99 | bool is_working() { return working; } 100 | 101 | private: 102 | void report_error(const std::string &) { working = false; } 103 | }; 104 | #endif -------------------------------------------------------------------------------- /ci/script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TOOLCHAIN="$1" 4 | 5 | mkdir build 6 | cd build 7 | 8 | if [ "$TOOLCHAIN" != "" ] ; then 9 | cmake -DFASTFLOAT_TEST=ON .. -DCMAKE_TOOLCHAIN_FILE=/toolchains/"$TOOLCHAIN".cmake 10 | else 11 | cmake -DFASTFLOAT_TEST=ON .. 12 | fi 13 | make -j 2 14 | if [ "$TOOLCHAIN" != "" ] ; then 15 | qemu-"$TOOLCHAIN" tests/basictest 16 | else 17 | ctest --output-on-failure -R basictest 18 | fi 19 | -------------------------------------------------------------------------------- /clang-format-ignore.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fastfloat/fast_float/b8085ba3634018ad6e5618cf87dd5a04a0e3f146/clang-format-ignore.txt -------------------------------------------------------------------------------- /cmake/config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") 4 | check_required_components("@PROJECT_NAME@") 5 | -------------------------------------------------------------------------------- /fuzz/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | $CXX $CFLAGS $CXXFLAGS \ 4 | -I $SRC/fast_float/include \ 5 | -c $SRC/fast_float/fuzz/from_chars.cc -o from_chars.o 6 | 7 | $CXX $CFLAGS $CXXFLAGS $LIB_FUZZING_ENGINE from_chars.o \ 8 | -o $OUT/from_chars -------------------------------------------------------------------------------- /fuzz/from_chars.cc: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | #include 3 | #include 4 | #include 5 | 6 | fast_float::chars_format arbitrary_format(FuzzedDataProvider &fdp) { 7 | using fast_float::chars_format; 8 | switch (fdp.ConsumeIntegralInRange(0, 3)) { 9 | case 0: 10 | return chars_format::scientific; 11 | break; 12 | case 1: 13 | return chars_format::fixed; 14 | break; 15 | case 2: 16 | return chars_format::fixed; 17 | break; 18 | } 19 | return chars_format::general; 20 | } 21 | 22 | extern "C" int LLVMFuzzerTestOneInput(uint8_t const *data, size_t size) { 23 | FuzzedDataProvider fdp(data, size); 24 | fast_float::chars_format format = arbitrary_format(fdp); 25 | double result_d = 0.0; 26 | std::string input_d = fdp.ConsumeRandomLengthString(128); 27 | auto answer = fast_float::from_chars( 28 | input_d.data(), input_d.data() + input_d.size(), result_d, format); 29 | std::string input_f = fdp.ConsumeRandomLengthString(128); 30 | float result_f = 0.0; 31 | answer = fast_float::from_chars( 32 | input_f.data(), input_f.data() + input_f.size(), result_f, format); 33 | int result_i = 0; 34 | std::string input_i = fdp.ConsumeRandomLengthString(128); 35 | answer = fast_float::from_chars(input_i.data(), 36 | input_i.data() + input_i.size(), result_i); 37 | return 0; 38 | } -------------------------------------------------------------------------------- /include/fast_float/constexpr_feature_detect.h: -------------------------------------------------------------------------------- 1 | #ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H 2 | #define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H 3 | 4 | #ifdef __has_include 5 | #if __has_include() 6 | #include 7 | #endif 8 | #endif 9 | 10 | // Testing for https://wg21.link/N3652, adopted in C++14 11 | #if defined(__cpp_constexpr) && __cpp_constexpr >= 201304 12 | #define FASTFLOAT_CONSTEXPR14 constexpr 13 | #else 14 | #define FASTFLOAT_CONSTEXPR14 15 | #endif 16 | 17 | #if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L 18 | #define FASTFLOAT_HAS_BIT_CAST 1 19 | #else 20 | #define FASTFLOAT_HAS_BIT_CAST 0 21 | #endif 22 | 23 | #if defined(__cpp_lib_is_constant_evaluated) && \ 24 | __cpp_lib_is_constant_evaluated >= 201811L 25 | #define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 26 | #else 27 | #define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 28 | #endif 29 | 30 | #if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L 31 | #define FASTFLOAT_IF_CONSTEXPR17(x) if constexpr (x) 32 | #else 33 | #define FASTFLOAT_IF_CONSTEXPR17(x) if (x) 34 | #endif 35 | 36 | // Testing for relevant C++20 constexpr library features 37 | #if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST && \ 38 | defined(__cpp_lib_constexpr_algorithms) && \ 39 | __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ 40 | #define FASTFLOAT_CONSTEXPR20 constexpr 41 | #define FASTFLOAT_IS_CONSTEXPR 1 42 | #else 43 | #define FASTFLOAT_CONSTEXPR20 44 | #define FASTFLOAT_IS_CONSTEXPR 0 45 | #endif 46 | 47 | #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) 48 | #define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 0 49 | #else 50 | #define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 1 51 | #endif 52 | 53 | #endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H 54 | -------------------------------------------------------------------------------- /include/fast_float/decimal_to_binary.h: -------------------------------------------------------------------------------- 1 | #ifndef FASTFLOAT_DECIMAL_TO_BINARY_H 2 | #define FASTFLOAT_DECIMAL_TO_BINARY_H 3 | 4 | #include "float_common.h" 5 | #include "fast_table.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace fast_float { 14 | 15 | // This will compute or rather approximate w * 5**q and return a pair of 64-bit 16 | // words approximating the result, with the "high" part corresponding to the 17 | // most significant bits and the low part corresponding to the least significant 18 | // bits. 19 | // 20 | template 21 | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 22 | compute_product_approximation(int64_t q, uint64_t w) { 23 | int const index = 2 * int(q - powers::smallest_power_of_five); 24 | // For small values of q, e.g., q in [0,27], the answer is always exact 25 | // because The line value128 firstproduct = full_multiplication(w, 26 | // power_of_five_128[index]); gives the exact answer. 27 | value128 firstproduct = 28 | full_multiplication(w, powers::power_of_five_128[index]); 29 | static_assert((bit_precision >= 0) && (bit_precision <= 64), 30 | " precision should be in (0,64]"); 31 | constexpr uint64_t precision_mask = 32 | (bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) 33 | : uint64_t(0xFFFFFFFFFFFFFFFF); 34 | if ((firstproduct.high & precision_mask) == 35 | precision_mask) { // could further guard with (lower + w < lower) 36 | // regarding the second product, we only need secondproduct.high, but our 37 | // expectation is that the compiler will optimize this extra work away if 38 | // needed. 39 | value128 secondproduct = 40 | full_multiplication(w, powers::power_of_five_128[index + 1]); 41 | firstproduct.low += secondproduct.high; 42 | if (secondproduct.high > firstproduct.low) { 43 | firstproduct.high++; 44 | } 45 | } 46 | return firstproduct; 47 | } 48 | 49 | namespace detail { 50 | /** 51 | * For q in (0,350), we have that 52 | * f = (((152170 + 65536) * q ) >> 16); 53 | * is equal to 54 | * floor(p) + q 55 | * where 56 | * p = log(5**q)/log(2) = q * log(5)/log(2) 57 | * 58 | * For negative values of q in (-400,0), we have that 59 | * f = (((152170 + 65536) * q ) >> 16); 60 | * is equal to 61 | * -ceil(p) + q 62 | * where 63 | * p = log(5**-q)/log(2) = -q * log(5)/log(2) 64 | */ 65 | constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { 66 | return (((152170 + 65536) * q) >> 16) + 63; 67 | } 68 | } // namespace detail 69 | 70 | // create an adjusted mantissa, biased by the invalid power2 71 | // for significant digits already multiplied by 10 ** q. 72 | template 73 | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 adjusted_mantissa 74 | compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { 75 | int hilz = int(w >> 63) ^ 1; 76 | adjusted_mantissa answer; 77 | answer.mantissa = w << hilz; 78 | int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); 79 | answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + 80 | invalid_am_bias); 81 | return answer; 82 | } 83 | 84 | // w * 10 ** q, without rounding the representation up. 85 | // the power2 in the exponent will be adjusted by invalid_am_bias. 86 | template 87 | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa 88 | compute_error(int64_t q, uint64_t w) noexcept { 89 | int lz = leading_zeroes(w); 90 | w <<= lz; 91 | value128 product = 92 | compute_product_approximation(q, w); 93 | return compute_error_scaled(q, product.high, lz); 94 | } 95 | 96 | // Computers w * 10 ** q. 97 | // The returned value should be a valid number that simply needs to be 98 | // packed. However, in some very rare cases, the computation will fail. In such 99 | // cases, we return an adjusted_mantissa with a negative power of 2: the caller 100 | // should recompute in such cases. 101 | template 102 | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa 103 | compute_float(int64_t q, uint64_t w) noexcept { 104 | adjusted_mantissa answer; 105 | if ((w == 0) || (q < binary::smallest_power_of_ten())) { 106 | answer.power2 = 0; 107 | answer.mantissa = 0; 108 | // result should be zero 109 | return answer; 110 | } 111 | if (q > binary::largest_power_of_ten()) { 112 | // we want to get infinity: 113 | answer.power2 = binary::infinite_power(); 114 | answer.mantissa = 0; 115 | return answer; 116 | } 117 | // At this point in time q is in [powers::smallest_power_of_five, 118 | // powers::largest_power_of_five]. 119 | 120 | // We want the most significant bit of i to be 1. Shift if needed. 121 | int lz = leading_zeroes(w); 122 | w <<= lz; 123 | 124 | // The required precision is binary::mantissa_explicit_bits() + 3 because 125 | // 1. We need the implicit bit 126 | // 2. We need an extra bit for rounding purposes 127 | // 3. We might lose a bit due to the "upperbit" routine (result too small, 128 | // requiring a shift) 129 | 130 | value128 product = 131 | compute_product_approximation(q, w); 132 | // The computed 'product' is always sufficient. 133 | // Mathematical proof: 134 | // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to 135 | // appear) See script/mushtak_lemire.py 136 | 137 | // The "compute_product_approximation" function can be slightly slower than a 138 | // branchless approach: value128 product = compute_product(q, w); but in 139 | // practice, we can win big with the compute_product_approximation if its 140 | // additional branch is easily predicted. Which is best is data specific. 141 | int upperbit = int(product.high >> 63); 142 | int shift = upperbit + 64 - binary::mantissa_explicit_bits() - 3; 143 | 144 | answer.mantissa = product.high >> shift; 145 | 146 | answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - 147 | binary::minimum_exponent()); 148 | if (answer.power2 <= 0) { // we have a subnormal? 149 | // Here have that answer.power2 <= 0 so -answer.power2 >= 0 150 | if (-answer.power2 + 1 >= 151 | 64) { // if we have more than 64 bits below the minimum exponent, you 152 | // have a zero for sure. 153 | answer.power2 = 0; 154 | answer.mantissa = 0; 155 | // result should be zero 156 | return answer; 157 | } 158 | // next line is safe because -answer.power2 + 1 < 64 159 | answer.mantissa >>= -answer.power2 + 1; 160 | // Thankfully, we can't have both "round-to-even" and subnormals because 161 | // "round-to-even" only occurs for powers close to 0 in the 32-bit and 162 | // and 64-bit case (with no more than 19 digits). 163 | answer.mantissa += (answer.mantissa & 1); // round up 164 | answer.mantissa >>= 1; 165 | // There is a weird scenario where we don't have a subnormal but just. 166 | // Suppose we start with 2.2250738585072013e-308, we end up 167 | // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal 168 | // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round 169 | // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer 170 | // subnormal, but we can only know this after rounding. 171 | // So we only declare a subnormal if we are smaller than the threshold. 172 | answer.power2 = 173 | (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) 174 | ? 0 175 | : 1; 176 | return answer; 177 | } 178 | 179 | // usually, we round *up*, but if we fall right in between and and we have an 180 | // even basis, we need to round down 181 | // We are only concerned with the cases where 5**q fits in single 64-bit word. 182 | if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && 183 | (q <= binary::max_exponent_round_to_even()) && 184 | ((answer.mantissa & 3) == 1)) { // we may fall between two floats! 185 | // To be in-between two floats we need that in doing 186 | // answer.mantissa = product.high >> (upperbit + 64 - 187 | // binary::mantissa_explicit_bits() - 3); 188 | // ... we dropped out only zeroes. But if this happened, then we can go 189 | // back!!! 190 | if ((answer.mantissa << shift) == product.high) { 191 | answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up 192 | } 193 | } 194 | 195 | answer.mantissa += (answer.mantissa & 1); // round up 196 | answer.mantissa >>= 1; 197 | if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { 198 | answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); 199 | answer.power2++; // undo previous addition 200 | } 201 | 202 | answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); 203 | if (answer.power2 >= binary::infinite_power()) { // infinity 204 | answer.power2 = binary::infinite_power(); 205 | answer.mantissa = 0; 206 | } 207 | return answer; 208 | } 209 | 210 | } // namespace fast_float 211 | 212 | #endif 213 | -------------------------------------------------------------------------------- /include/fast_float/digit_comparison.h: -------------------------------------------------------------------------------- 1 | #ifndef FASTFLOAT_DIGIT_COMPARISON_H 2 | #define FASTFLOAT_DIGIT_COMPARISON_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "float_common.h" 10 | #include "bigint.h" 11 | #include "ascii_number.h" 12 | 13 | namespace fast_float { 14 | 15 | // 1e0 to 1e19 16 | constexpr static uint64_t powers_of_ten_uint64[] = {1UL, 17 | 10UL, 18 | 100UL, 19 | 1000UL, 20 | 10000UL, 21 | 100000UL, 22 | 1000000UL, 23 | 10000000UL, 24 | 100000000UL, 25 | 1000000000UL, 26 | 10000000000UL, 27 | 100000000000UL, 28 | 1000000000000UL, 29 | 10000000000000UL, 30 | 100000000000000UL, 31 | 1000000000000000UL, 32 | 10000000000000000UL, 33 | 100000000000000000UL, 34 | 1000000000000000000UL, 35 | 10000000000000000000UL}; 36 | 37 | // calculate the exponent, in scientific notation, of the number. 38 | // this algorithm is not even close to optimized, but it has no practical 39 | // effect on performance: in order to have a faster algorithm, we'd need 40 | // to slow down performance for faster algorithms, and this is still fast. 41 | template 42 | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int32_t 43 | scientific_exponent(parsed_number_string_t &num) noexcept { 44 | uint64_t mantissa = num.mantissa; 45 | int32_t exponent = int32_t(num.exponent); 46 | while (mantissa >= 10000) { 47 | mantissa /= 10000; 48 | exponent += 4; 49 | } 50 | while (mantissa >= 100) { 51 | mantissa /= 100; 52 | exponent += 2; 53 | } 54 | while (mantissa >= 10) { 55 | mantissa /= 10; 56 | exponent += 1; 57 | } 58 | return exponent; 59 | } 60 | 61 | // this converts a native floating-point number to an extended-precision float. 62 | template 63 | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa 64 | to_extended(T value) noexcept { 65 | using equiv_uint = equiv_uint_t; 66 | constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); 67 | constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); 68 | constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); 69 | 70 | adjusted_mantissa am; 71 | int32_t bias = binary_format::mantissa_explicit_bits() - 72 | binary_format::minimum_exponent(); 73 | equiv_uint bits; 74 | #if FASTFLOAT_HAS_BIT_CAST 75 | bits = std::bit_cast(value); 76 | #else 77 | ::memcpy(&bits, &value, sizeof(T)); 78 | #endif 79 | if ((bits & exponent_mask) == 0) { 80 | // denormal 81 | am.power2 = 1 - bias; 82 | am.mantissa = bits & mantissa_mask; 83 | } else { 84 | // normal 85 | am.power2 = int32_t((bits & exponent_mask) >> 86 | binary_format::mantissa_explicit_bits()); 87 | am.power2 -= bias; 88 | am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; 89 | } 90 | 91 | return am; 92 | } 93 | 94 | // get the extended precision value of the halfway point between b and b+u. 95 | // we are given a native float that represents b, so we need to adjust it 96 | // halfway between b and b+u. 97 | template 98 | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa 99 | to_extended_halfway(T value) noexcept { 100 | adjusted_mantissa am = to_extended(value); 101 | am.mantissa <<= 1; 102 | am.mantissa += 1; 103 | am.power2 -= 1; 104 | return am; 105 | } 106 | 107 | // round an extended-precision float to the nearest machine float. 108 | template 109 | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am, 110 | callback cb) noexcept { 111 | int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; 112 | if (-am.power2 >= mantissa_shift) { 113 | // have a denormal float 114 | int32_t shift = -am.power2 + 1; 115 | cb(am, std::min(shift, 64)); 116 | // check for round-up: if rounding-nearest carried us to the hidden bit. 117 | am.power2 = (am.mantissa < 118 | (uint64_t(1) << binary_format::mantissa_explicit_bits())) 119 | ? 0 120 | : 1; 121 | return; 122 | } 123 | 124 | // have a normal float, use the default shift. 125 | cb(am, mantissa_shift); 126 | 127 | // check for carry 128 | if (am.mantissa >= 129 | (uint64_t(2) << binary_format::mantissa_explicit_bits())) { 130 | am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); 131 | am.power2++; 132 | } 133 | 134 | // check for infinite: we could have carried to an infinite power 135 | am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); 136 | if (am.power2 >= binary_format::infinite_power()) { 137 | am.power2 = binary_format::infinite_power(); 138 | am.mantissa = 0; 139 | } 140 | } 141 | 142 | template 143 | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void 144 | round_nearest_tie_even(adjusted_mantissa &am, int32_t shift, 145 | callback cb) noexcept { 146 | uint64_t const mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1; 147 | uint64_t const halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1); 148 | uint64_t truncated_bits = am.mantissa & mask; 149 | bool is_above = truncated_bits > halfway; 150 | bool is_halfway = truncated_bits == halfway; 151 | 152 | // shift digits into position 153 | if (shift == 64) { 154 | am.mantissa = 0; 155 | } else { 156 | am.mantissa >>= shift; 157 | } 158 | am.power2 += shift; 159 | 160 | bool is_odd = (am.mantissa & 1) == 1; 161 | am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); 162 | } 163 | 164 | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void 165 | round_down(adjusted_mantissa &am, int32_t shift) noexcept { 166 | if (shift == 64) { 167 | am.mantissa = 0; 168 | } else { 169 | am.mantissa >>= shift; 170 | } 171 | am.power2 += shift; 172 | } 173 | 174 | template 175 | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void 176 | skip_zeros(UC const *&first, UC const *last) noexcept { 177 | uint64_t val; 178 | while (!cpp20_and_in_constexpr() && 179 | std::distance(first, last) >= int_cmp_len()) { 180 | ::memcpy(&val, first, sizeof(uint64_t)); 181 | if (val != int_cmp_zeros()) { 182 | break; 183 | } 184 | first += int_cmp_len(); 185 | } 186 | while (first != last) { 187 | if (*first != UC('0')) { 188 | break; 189 | } 190 | first++; 191 | } 192 | } 193 | 194 | // determine if any non-zero digits were truncated. 195 | // all characters must be valid digits. 196 | template 197 | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool 198 | is_truncated(UC const *first, UC const *last) noexcept { 199 | // do 8-bit optimizations, can just compare to 8 literal 0s. 200 | uint64_t val; 201 | while (!cpp20_and_in_constexpr() && 202 | std::distance(first, last) >= int_cmp_len()) { 203 | ::memcpy(&val, first, sizeof(uint64_t)); 204 | if (val != int_cmp_zeros()) { 205 | return true; 206 | } 207 | first += int_cmp_len(); 208 | } 209 | while (first != last) { 210 | if (*first != UC('0')) { 211 | return true; 212 | } 213 | ++first; 214 | } 215 | return false; 216 | } 217 | 218 | template 219 | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool 220 | is_truncated(span s) noexcept { 221 | return is_truncated(s.ptr, s.ptr + s.len()); 222 | } 223 | 224 | template 225 | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void 226 | parse_eight_digits(UC const *&p, limb &value, size_t &counter, 227 | size_t &count) noexcept { 228 | value = value * 100000000 + parse_eight_digits_unrolled(p); 229 | p += 8; 230 | counter += 8; 231 | count += 8; 232 | } 233 | 234 | template 235 | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void 236 | parse_one_digit(UC const *&p, limb &value, size_t &counter, 237 | size_t &count) noexcept { 238 | value = value * 10 + limb(*p - UC('0')); 239 | p++; 240 | counter++; 241 | count++; 242 | } 243 | 244 | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void 245 | add_native(bigint &big, limb power, limb value) noexcept { 246 | big.mul(power); 247 | big.add(value); 248 | } 249 | 250 | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void 251 | round_up_bigint(bigint &big, size_t &count) noexcept { 252 | // need to round-up the digits, but need to avoid rounding 253 | // ....9999 to ...10000, which could cause a false halfway point. 254 | add_native(big, 10, 1); 255 | count++; 256 | } 257 | 258 | // parse the significant digits into a big integer 259 | template 260 | inline FASTFLOAT_CONSTEXPR20 void 261 | parse_mantissa(bigint &result, parsed_number_string_t &num, 262 | size_t max_digits, size_t &digits) noexcept { 263 | // try to minimize the number of big integer and scalar multiplication. 264 | // therefore, try to parse 8 digits at a time, and multiply by the largest 265 | // scalar value (9 or 19 digits) for each step. 266 | size_t counter = 0; 267 | digits = 0; 268 | limb value = 0; 269 | #ifdef FASTFLOAT_64BIT_LIMB 270 | size_t step = 19; 271 | #else 272 | size_t step = 9; 273 | #endif 274 | 275 | // process all integer digits. 276 | UC const *p = num.integer.ptr; 277 | UC const *pend = p + num.integer.len(); 278 | skip_zeros(p, pend); 279 | // process all digits, in increments of step per loop 280 | while (p != pend) { 281 | while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && 282 | (max_digits - digits >= 8)) { 283 | parse_eight_digits(p, value, counter, digits); 284 | } 285 | while (counter < step && p != pend && digits < max_digits) { 286 | parse_one_digit(p, value, counter, digits); 287 | } 288 | if (digits == max_digits) { 289 | // add the temporary value, then check if we've truncated any digits 290 | add_native(result, limb(powers_of_ten_uint64[counter]), value); 291 | bool truncated = is_truncated(p, pend); 292 | if (num.fraction.ptr != nullptr) { 293 | truncated |= is_truncated(num.fraction); 294 | } 295 | if (truncated) { 296 | round_up_bigint(result, digits); 297 | } 298 | return; 299 | } else { 300 | add_native(result, limb(powers_of_ten_uint64[counter]), value); 301 | counter = 0; 302 | value = 0; 303 | } 304 | } 305 | 306 | // add our fraction digits, if they're available. 307 | if (num.fraction.ptr != nullptr) { 308 | p = num.fraction.ptr; 309 | pend = p + num.fraction.len(); 310 | if (digits == 0) { 311 | skip_zeros(p, pend); 312 | } 313 | // process all digits, in increments of step per loop 314 | while (p != pend) { 315 | while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && 316 | (max_digits - digits >= 8)) { 317 | parse_eight_digits(p, value, counter, digits); 318 | } 319 | while (counter < step && p != pend && digits < max_digits) { 320 | parse_one_digit(p, value, counter, digits); 321 | } 322 | if (digits == max_digits) { 323 | // add the temporary value, then check if we've truncated any digits 324 | add_native(result, limb(powers_of_ten_uint64[counter]), value); 325 | bool truncated = is_truncated(p, pend); 326 | if (truncated) { 327 | round_up_bigint(result, digits); 328 | } 329 | return; 330 | } else { 331 | add_native(result, limb(powers_of_ten_uint64[counter]), value); 332 | counter = 0; 333 | value = 0; 334 | } 335 | } 336 | } 337 | 338 | if (counter != 0) { 339 | add_native(result, limb(powers_of_ten_uint64[counter]), value); 340 | } 341 | } 342 | 343 | template 344 | inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa 345 | positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept { 346 | FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); 347 | adjusted_mantissa answer; 348 | bool truncated; 349 | answer.mantissa = bigmant.hi64(truncated); 350 | int bias = binary_format::mantissa_explicit_bits() - 351 | binary_format::minimum_exponent(); 352 | answer.power2 = bigmant.bit_length() - 64 + bias; 353 | 354 | round(answer, [truncated](adjusted_mantissa &a, int32_t shift) { 355 | round_nearest_tie_even( 356 | a, shift, 357 | [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { 358 | return is_above || (is_halfway && truncated) || 359 | (is_odd && is_halfway); 360 | }); 361 | }); 362 | 363 | return answer; 364 | } 365 | 366 | // the scaling here is quite simple: we have, for the real digits `m * 10^e`, 367 | // and for the theoretical digits `n * 2^f`. Since `e` is always negative, 368 | // to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. 369 | // we then need to scale by `2^(f- e)`, and then the two significant digits 370 | // are of the same magnitude. 371 | template 372 | inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp( 373 | bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept { 374 | bigint &real_digits = bigmant; 375 | int32_t real_exp = exponent; 376 | 377 | // get the value of `b`, rounded down, and get a bigint representation of b+h 378 | adjusted_mantissa am_b = am; 379 | // gcc7 buf: use a lambda to remove the noexcept qualifier bug with 380 | // -Wnoexcept-type. 381 | round(am_b, 382 | [](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); }); 383 | T b; 384 | to_float(false, am_b, b); 385 | adjusted_mantissa theor = to_extended_halfway(b); 386 | bigint theor_digits(theor.mantissa); 387 | int32_t theor_exp = theor.power2; 388 | 389 | // scale real digits and theor digits to be same power. 390 | int32_t pow2_exp = theor_exp - real_exp; 391 | uint32_t pow5_exp = uint32_t(-real_exp); 392 | if (pow5_exp != 0) { 393 | FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); 394 | } 395 | if (pow2_exp > 0) { 396 | FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); 397 | } else if (pow2_exp < 0) { 398 | FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); 399 | } 400 | 401 | // compare digits, and use it to director rounding 402 | int ord = real_digits.compare(theor_digits); 403 | adjusted_mantissa answer = am; 404 | round(answer, [ord](adjusted_mantissa &a, int32_t shift) { 405 | round_nearest_tie_even( 406 | a, shift, [ord](bool is_odd, bool _, bool __) -> bool { 407 | (void)_; // not needed, since we've done our comparison 408 | (void)__; // not needed, since we've done our comparison 409 | if (ord > 0) { 410 | return true; 411 | } else if (ord < 0) { 412 | return false; 413 | } else { 414 | return is_odd; 415 | } 416 | }); 417 | }); 418 | 419 | return answer; 420 | } 421 | 422 | // parse the significant digits as a big integer to unambiguously round the 423 | // the significant digits. here, we are trying to determine how to round 424 | // an extended float representation close to `b+h`, halfway between `b` 425 | // (the float rounded-down) and `b+u`, the next positive float. this 426 | // algorithm is always correct, and uses one of two approaches. when 427 | // the exponent is positive relative to the significant digits (such as 428 | // 1234), we create a big-integer representation, get the high 64-bits, 429 | // determine if any lower bits are truncated, and use that to direct 430 | // rounding. in case of a negative exponent relative to the significant 431 | // digits (such as 1.2345), we create a theoretical representation of 432 | // `b` as a big-integer type, scaled to the same binary exponent as 433 | // the actual digits. we then compare the big integer representations 434 | // of both, and use that to direct rounding. 435 | template 436 | inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa 437 | digit_comp(parsed_number_string_t &num, adjusted_mantissa am) noexcept { 438 | // remove the invalid exponent bias 439 | am.power2 -= invalid_am_bias; 440 | 441 | int32_t sci_exp = scientific_exponent(num); 442 | size_t max_digits = binary_format::max_digits(); 443 | size_t digits = 0; 444 | bigint bigmant; 445 | parse_mantissa(bigmant, num, max_digits, digits); 446 | // can't underflow, since digits is at most max_digits. 447 | int32_t exponent = sci_exp + 1 - int32_t(digits); 448 | if (exponent >= 0) { 449 | return positive_digit_comp(bigmant, exponent); 450 | } else { 451 | return negative_digit_comp(bigmant, am, exponent); 452 | } 453 | } 454 | 455 | } // namespace fast_float 456 | 457 | #endif 458 | -------------------------------------------------------------------------------- /include/fast_float/fast_float.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef FASTFLOAT_FAST_FLOAT_H 3 | #define FASTFLOAT_FAST_FLOAT_H 4 | 5 | #include "float_common.h" 6 | 7 | namespace fast_float { 8 | /** 9 | * This function parses the character sequence [first,last) for a number. It 10 | * parses floating-point numbers expecting a locale-indepent format equivalent 11 | * to what is used by std::strtod in the default ("C") locale. The resulting 12 | * floating-point value is the closest floating-point values (using either float 13 | * or double), using the "round to even" convention for values that would 14 | * otherwise fall right in-between two values. That is, we provide exact parsing 15 | * according to the IEEE standard. 16 | * 17 | * Given a successful parse, the pointer (`ptr`) in the returned value is set to 18 | * point right after the parsed number, and the `value` referenced is set to the 19 | * parsed value. In case of error, the returned `ec` contains a representative 20 | * error, otherwise the default (`std::errc()`) value is stored. 21 | * 22 | * The implementation does not throw and does not allocate memory (e.g., with 23 | * `new` or `malloc`). 24 | * 25 | * Like the C++17 standard, the `fast_float::from_chars` functions take an 26 | * optional last argument of the type `fast_float::chars_format`. It is a bitset 27 | * value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt & 28 | * fast_float::chars_format::scientific` are set to determine whether we allow 29 | * the fixed point and scientific notation respectively. The default is 30 | * `fast_float::chars_format::general` which allows both `fixed` and 31 | * `scientific`. 32 | */ 33 | template ::value)> 35 | FASTFLOAT_CONSTEXPR20 from_chars_result_t 36 | from_chars(UC const *first, UC const *last, T &value, 37 | chars_format fmt = chars_format::general) noexcept; 38 | 39 | /** 40 | * Like from_chars, but accepts an `options` argument to govern number parsing. 41 | * Both for floating-point types and integer types. 42 | */ 43 | template 44 | FASTFLOAT_CONSTEXPR20 from_chars_result_t 45 | from_chars_advanced(UC const *first, UC const *last, T &value, 46 | parse_options_t options) noexcept; 47 | 48 | /** 49 | * from_chars for integer types. 50 | */ 51 | template ::value)> 53 | FASTFLOAT_CONSTEXPR20 from_chars_result_t 54 | from_chars(UC const *first, UC const *last, T &value, int base = 10) noexcept; 55 | 56 | } // namespace fast_float 57 | 58 | #include "parse_number.h" 59 | #endif // FASTFLOAT_FAST_FLOAT_H 60 | -------------------------------------------------------------------------------- /include/fast_float/parse_number.h: -------------------------------------------------------------------------------- 1 | #ifndef FASTFLOAT_PARSE_NUMBER_H 2 | #define FASTFLOAT_PARSE_NUMBER_H 3 | 4 | #include "ascii_number.h" 5 | #include "decimal_to_binary.h" 6 | #include "digit_comparison.h" 7 | #include "float_common.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace fast_float { 15 | 16 | namespace detail { 17 | /** 18 | * Special case +inf, -inf, nan, infinity, -infinity. 19 | * The case comparisons could be made much faster given that we know that the 20 | * strings a null-free and fixed. 21 | **/ 22 | template 23 | from_chars_result_t 24 | FASTFLOAT_CONSTEXPR14 parse_infnan(UC const *first, UC const *last, 25 | T &value, chars_format fmt) noexcept { 26 | from_chars_result_t answer{}; 27 | answer.ptr = first; 28 | answer.ec = std::errc(); // be optimistic 29 | // assume first < last, so dereference without checks; 30 | bool const minusSign = (*first == UC('-')); 31 | // C++17 20.19.3.(7.1) explicitly forbids '+' sign here 32 | if ((*first == UC('-')) || 33 | (uint64_t(fmt & chars_format::allow_leading_plus) && 34 | (*first == UC('+')))) { 35 | ++first; 36 | } 37 | if (last - first >= 3) { 38 | if (fastfloat_strncasecmp(first, str_const_nan(), 3)) { 39 | answer.ptr = (first += 3); 40 | value = minusSign ? -std::numeric_limits::quiet_NaN() 41 | : std::numeric_limits::quiet_NaN(); 42 | // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, 43 | // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). 44 | if (first != last && *first == UC('(')) { 45 | for (UC const *ptr = first + 1; ptr != last; ++ptr) { 46 | if (*ptr == UC(')')) { 47 | answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) 48 | break; 49 | } else if (!((UC('a') <= *ptr && *ptr <= UC('z')) || 50 | (UC('A') <= *ptr && *ptr <= UC('Z')) || 51 | (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) 52 | break; // forbidden char, not nan(n-char-seq-opt) 53 | } 54 | } 55 | return answer; 56 | } 57 | if (fastfloat_strncasecmp(first, str_const_inf(), 3)) { 58 | if ((last - first >= 8) && 59 | fastfloat_strncasecmp(first + 3, str_const_inf() + 3, 5)) { 60 | answer.ptr = first + 8; 61 | } else { 62 | answer.ptr = first + 3; 63 | } 64 | value = minusSign ? -std::numeric_limits::infinity() 65 | : std::numeric_limits::infinity(); 66 | return answer; 67 | } 68 | } 69 | answer.ec = std::errc::invalid_argument; 70 | return answer; 71 | } 72 | 73 | /** 74 | * Returns true if the floating-pointing rounding mode is to 'nearest'. 75 | * It is the default on most system. This function is meant to be inexpensive. 76 | * Credit : @mwalcott3 77 | */ 78 | fastfloat_really_inline bool rounds_to_nearest() noexcept { 79 | // https://lemire.me/blog/2020/06/26/gcc-not-nearest/ 80 | #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) 81 | return false; 82 | #endif 83 | // See 84 | // A fast function to check your floating-point rounding mode 85 | // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ 86 | // 87 | // This function is meant to be equivalent to : 88 | // prior: #include 89 | // return fegetround() == FE_TONEAREST; 90 | // However, it is expected to be much faster than the fegetround() 91 | // function call. 92 | // 93 | // The volatile keyword prevents the compiler from computing the function 94 | // at compile-time. 95 | // There might be other ways to prevent compile-time optimizations (e.g., 96 | // asm). The value does not need to be std::numeric_limits::min(), any 97 | // small value so that 1 + x should round to 1 would do (after accounting for 98 | // excess precision, as in 387 instructions). 99 | static float volatile fmin = std::numeric_limits::min(); 100 | float fmini = fmin; // we copy it so that it gets loaded at most once. 101 | // 102 | // Explanation: 103 | // Only when fegetround() == FE_TONEAREST do we have that 104 | // fmin + 1.0f == 1.0f - fmin. 105 | // 106 | // FE_UPWARD: 107 | // fmin + 1.0f > 1 108 | // 1.0f - fmin == 1 109 | // 110 | // FE_DOWNWARD or FE_TOWARDZERO: 111 | // fmin + 1.0f == 1 112 | // 1.0f - fmin < 1 113 | // 114 | // Note: This may fail to be accurate if fast-math has been 115 | // enabled, as rounding conventions may not apply. 116 | #ifdef FASTFLOAT_VISUAL_STUDIO 117 | #pragma warning(push) 118 | // todo: is there a VS warning? 119 | // see 120 | // https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 121 | #elif defined(__clang__) 122 | #pragma clang diagnostic push 123 | #pragma clang diagnostic ignored "-Wfloat-equal" 124 | #elif defined(__GNUC__) 125 | #pragma GCC diagnostic push 126 | #pragma GCC diagnostic ignored "-Wfloat-equal" 127 | #endif 128 | return (fmini + 1.0f == 1.0f - fmini); 129 | #ifdef FASTFLOAT_VISUAL_STUDIO 130 | #pragma warning(pop) 131 | #elif defined(__clang__) 132 | #pragma clang diagnostic pop 133 | #elif defined(__GNUC__) 134 | #pragma GCC diagnostic pop 135 | #endif 136 | } 137 | 138 | } // namespace detail 139 | 140 | template struct from_chars_caller { 141 | template 142 | FASTFLOAT_CONSTEXPR20 static from_chars_result_t 143 | call(UC const *first, UC const *last, T &value, 144 | parse_options_t options) noexcept { 145 | return from_chars_advanced(first, last, value, options); 146 | } 147 | }; 148 | 149 | #ifdef __STDCPP_FLOAT32_T__ 150 | template <> struct from_chars_caller { 151 | template 152 | FASTFLOAT_CONSTEXPR20 static from_chars_result_t 153 | call(UC const *first, UC const *last, std::float32_t &value, 154 | parse_options_t options) noexcept { 155 | // if std::float32_t is defined, and we are in C++23 mode; macro set for 156 | // float32; set value to float due to equivalence between float and 157 | // float32_t 158 | float val; 159 | auto ret = from_chars_advanced(first, last, val, options); 160 | value = val; 161 | return ret; 162 | } 163 | }; 164 | #endif 165 | 166 | #ifdef __STDCPP_FLOAT64_T__ 167 | template <> struct from_chars_caller { 168 | template 169 | FASTFLOAT_CONSTEXPR20 static from_chars_result_t 170 | call(UC const *first, UC const *last, std::float64_t &value, 171 | parse_options_t options) noexcept { 172 | // if std::float64_t is defined, and we are in C++23 mode; macro set for 173 | // float64; set value as double due to equivalence between double and 174 | // float64_t 175 | double val; 176 | auto ret = from_chars_advanced(first, last, val, options); 177 | value = val; 178 | return ret; 179 | } 180 | }; 181 | #endif 182 | 183 | template 184 | FASTFLOAT_CONSTEXPR20 from_chars_result_t 185 | from_chars(UC const *first, UC const *last, T &value, 186 | chars_format fmt /*= chars_format::general*/) noexcept { 187 | return from_chars_caller::call(first, last, value, 188 | parse_options_t(fmt)); 189 | } 190 | 191 | /** 192 | * This function overload takes parsed_number_string_t structure that is created 193 | * and populated either by from_chars_advanced function taking chars range and 194 | * parsing options or other parsing custom function implemented by user. 195 | */ 196 | template 197 | FASTFLOAT_CONSTEXPR20 from_chars_result_t 198 | from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { 199 | 200 | static_assert(is_supported_float_type::value, 201 | "only some floating-point types are supported"); 202 | static_assert(is_supported_char_type::value, 203 | "only char, wchar_t, char16_t and char32_t are supported"); 204 | 205 | from_chars_result_t answer; 206 | 207 | answer.ec = std::errc(); // be optimistic 208 | answer.ptr = pns.lastmatch; 209 | // The implementation of the Clinger's fast path is convoluted because 210 | // we want round-to-nearest in all cases, irrespective of the rounding mode 211 | // selected on the thread. 212 | // We proceed optimistically, assuming that detail::rounds_to_nearest() 213 | // returns true. 214 | if (binary_format::min_exponent_fast_path() <= pns.exponent && 215 | pns.exponent <= binary_format::max_exponent_fast_path() && 216 | !pns.too_many_digits) { 217 | // Unfortunately, the conventional Clinger's fast path is only possible 218 | // when the system rounds to the nearest float. 219 | // 220 | // We expect the next branch to almost always be selected. 221 | // We could check it first (before the previous branch), but 222 | // there might be performance advantages at having the check 223 | // be last. 224 | if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { 225 | // We have that fegetround() == FE_TONEAREST. 226 | // Next is Clinger's fast path. 227 | if (pns.mantissa <= binary_format::max_mantissa_fast_path()) { 228 | value = T(pns.mantissa); 229 | if (pns.exponent < 0) { 230 | value = value / binary_format::exact_power_of_ten(-pns.exponent); 231 | } else { 232 | value = value * binary_format::exact_power_of_ten(pns.exponent); 233 | } 234 | if (pns.negative) { 235 | value = -value; 236 | } 237 | return answer; 238 | } 239 | } else { 240 | // We do not have that fegetround() == FE_TONEAREST. 241 | // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's 242 | // proposal 243 | if (pns.exponent >= 0 && 244 | pns.mantissa <= 245 | binary_format::max_mantissa_fast_path(pns.exponent)) { 246 | #if defined(__clang__) || defined(FASTFLOAT_32BIT) 247 | // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD 248 | if (pns.mantissa == 0) { 249 | value = pns.negative ? T(-0.) : T(0.); 250 | return answer; 251 | } 252 | #endif 253 | value = T(pns.mantissa) * 254 | binary_format::exact_power_of_ten(pns.exponent); 255 | if (pns.negative) { 256 | value = -value; 257 | } 258 | return answer; 259 | } 260 | } 261 | } 262 | adjusted_mantissa am = 263 | compute_float>(pns.exponent, pns.mantissa); 264 | if (pns.too_many_digits && am.power2 >= 0) { 265 | if (am != compute_float>(pns.exponent, pns.mantissa + 1)) { 266 | am = compute_error>(pns.exponent, pns.mantissa); 267 | } 268 | } 269 | // If we called compute_float>(pns.exponent, pns.mantissa) 270 | // and we have an invalid power (am.power2 < 0), then we need to go the long 271 | // way around again. This is very uncommon. 272 | if (am.power2 < 0) { 273 | am = digit_comp(pns, am); 274 | } 275 | to_float(pns.negative, am, value); 276 | // Test for over/underflow. 277 | if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || 278 | am.power2 == binary_format::infinite_power()) { 279 | answer.ec = std::errc::result_out_of_range; 280 | } 281 | return answer; 282 | } 283 | 284 | template 285 | FASTFLOAT_CONSTEXPR20 from_chars_result_t 286 | from_chars_float_advanced(UC const *first, UC const *last, T &value, 287 | parse_options_t options) noexcept { 288 | 289 | static_assert(is_supported_float_type::value, 290 | "only some floating-point types are supported"); 291 | static_assert(is_supported_char_type::value, 292 | "only char, wchar_t, char16_t and char32_t are supported"); 293 | 294 | chars_format const fmt = detail::adjust_for_feature_macros(options.format); 295 | 296 | from_chars_result_t answer; 297 | if (uint64_t(fmt & chars_format::skip_white_space)) { 298 | while ((first != last) && fast_float::is_space(*first)) { 299 | first++; 300 | } 301 | } 302 | if (first == last) { 303 | answer.ec = std::errc::invalid_argument; 304 | answer.ptr = first; 305 | return answer; 306 | } 307 | parsed_number_string_t pns = 308 | uint64_t(fmt & detail::basic_json_fmt) 309 | ? parse_number_string(first, last, options) 310 | : parse_number_string(first, last, options); 311 | if (!pns.valid) { 312 | if (uint64_t(fmt & chars_format::no_infnan)) { 313 | answer.ec = std::errc::invalid_argument; 314 | answer.ptr = first; 315 | return answer; 316 | } else { 317 | return detail::parse_infnan(first, last, value, fmt); 318 | } 319 | } 320 | 321 | // call overload that takes parsed_number_string_t directly. 322 | return from_chars_advanced(pns, value); 323 | } 324 | 325 | template 326 | FASTFLOAT_CONSTEXPR20 from_chars_result_t 327 | from_chars(UC const *first, UC const *last, T &value, int base) noexcept { 328 | 329 | static_assert(is_supported_integer_type::value, 330 | "only integer types are supported"); 331 | static_assert(is_supported_char_type::value, 332 | "only char, wchar_t, char16_t and char32_t are supported"); 333 | 334 | parse_options_t options; 335 | options.base = base; 336 | return from_chars_advanced(first, last, value, options); 337 | } 338 | 339 | template 340 | FASTFLOAT_CONSTEXPR20 from_chars_result_t 341 | from_chars_int_advanced(UC const *first, UC const *last, T &value, 342 | parse_options_t options) noexcept { 343 | 344 | static_assert(is_supported_integer_type::value, 345 | "only integer types are supported"); 346 | static_assert(is_supported_char_type::value, 347 | "only char, wchar_t, char16_t and char32_t are supported"); 348 | 349 | chars_format const fmt = detail::adjust_for_feature_macros(options.format); 350 | int const base = options.base; 351 | 352 | from_chars_result_t answer; 353 | if (uint64_t(fmt & chars_format::skip_white_space)) { 354 | while ((first != last) && fast_float::is_space(*first)) { 355 | first++; 356 | } 357 | } 358 | if (first == last || base < 2 || base > 36) { 359 | answer.ec = std::errc::invalid_argument; 360 | answer.ptr = first; 361 | return answer; 362 | } 363 | 364 | return parse_int_string(first, last, value, options); 365 | } 366 | 367 | template struct from_chars_advanced_caller { 368 | static_assert(TypeIx > 0, "unsupported type"); 369 | }; 370 | 371 | template <> struct from_chars_advanced_caller<1> { 372 | template 373 | FASTFLOAT_CONSTEXPR20 static from_chars_result_t 374 | call(UC const *first, UC const *last, T &value, 375 | parse_options_t options) noexcept { 376 | return from_chars_float_advanced(first, last, value, options); 377 | } 378 | }; 379 | 380 | template <> struct from_chars_advanced_caller<2> { 381 | template 382 | FASTFLOAT_CONSTEXPR20 static from_chars_result_t 383 | call(UC const *first, UC const *last, T &value, 384 | parse_options_t options) noexcept { 385 | return from_chars_int_advanced(first, last, value, options); 386 | } 387 | }; 388 | 389 | template 390 | FASTFLOAT_CONSTEXPR20 from_chars_result_t 391 | from_chars_advanced(UC const *first, UC const *last, T &value, 392 | parse_options_t options) noexcept { 393 | return from_chars_advanced_caller< 394 | size_t(is_supported_float_type::value) + 395 | 2 * size_t(is_supported_integer_type::value)>::call(first, last, value, 396 | options); 397 | } 398 | 399 | } // namespace fast_float 400 | 401 | #endif 402 | -------------------------------------------------------------------------------- /script/amalgamate.py: -------------------------------------------------------------------------------- 1 | # text parts 2 | processed_files = {} 3 | 4 | # authors 5 | for filename in ["AUTHORS", "CONTRIBUTORS"]: 6 | with open(filename, encoding="utf8") as f: 7 | text = "" 8 | for line in f: 9 | if filename == "AUTHORS": 10 | text += "// fast_float by " + line 11 | if filename == "CONTRIBUTORS": 12 | text += "// with contributions from " + line 13 | processed_files[filename] = text + "//\n//\n" 14 | 15 | # licenses 16 | for filename in ["LICENSE-MIT", "LICENSE-APACHE", "LICENSE-BOOST"]: 17 | lines = [] 18 | with open(filename, encoding="utf8") as f: 19 | lines = f.readlines() 20 | 21 | # Retrieve subset required for inclusion in source 22 | if filename == "LICENSE-APACHE": 23 | lines = [" Copyright 2021 The fast_float authors\n", *lines[179:-1]] 24 | 25 | text = "" 26 | for line in lines: 27 | line = line.strip() 28 | if len(line): 29 | line = " " + line 30 | text += "//" + line + "\n" 31 | processed_files[filename] = text 32 | 33 | # code 34 | for filename in [ 35 | "constexpr_feature_detect.h", 36 | "float_common.h", 37 | "fast_float.h", 38 | "ascii_number.h", 39 | "fast_table.h", 40 | "decimal_to_binary.h", 41 | "bigint.h", 42 | "digit_comparison.h", 43 | "parse_number.h", 44 | ]: 45 | with open("include/fast_float/" + filename, encoding="utf8") as f: 46 | text = "" 47 | for line in f: 48 | if line.startswith('#include "'): 49 | continue 50 | text += line 51 | processed_files[filename] = "\n" + text 52 | 53 | # command line 54 | import argparse 55 | 56 | parser = argparse.ArgumentParser(description="Amalgamate fast_float.") 57 | parser.add_argument( 58 | "--license", 59 | default="TRIPLE", 60 | choices=["DUAL", "TRIPLE", "MIT", "APACHE", "BOOST"], 61 | help="choose license", 62 | ) 63 | parser.add_argument("--output", default="", help="output file (stdout if none") 64 | 65 | args = parser.parse_args() 66 | 67 | 68 | def license_content(license_arg): 69 | result = [] 70 | if license_arg == "TRIPLE": 71 | result += [ 72 | "// Licensed under the Apache License, Version 2.0, or the\n", 73 | "// MIT License or the Boost License. This file may not be copied,\n", 74 | "// modified, or distributed except according to those terms.\n", 75 | "//\n", 76 | ] 77 | if license_arg == "DUAL": 78 | result += [ 79 | "// Licensed under the Apache License, Version 2.0, or the\n", 80 | "// MIT License at your option. This file may not be copied,\n", 81 | "// modified, or distributed except according to those terms.\n", 82 | "//\n", 83 | ] 84 | 85 | if license_arg in ("DUAL", "TRIPLE", "MIT"): 86 | result.append("// MIT License Notice\n//\n") 87 | result.append(processed_files["LICENSE-MIT"]) 88 | result.append("//\n") 89 | if license_arg in ("DUAL", "TRIPLE", "APACHE"): 90 | result.append("// Apache License (Version 2.0) Notice\n//\n") 91 | result.append(processed_files["LICENSE-APACHE"]) 92 | result.append("//\n") 93 | if license_arg in ("TRIPLE", "BOOST"): 94 | result.append("// BOOST License Notice\n//\n") 95 | result.append(processed_files["LICENSE-BOOST"]) 96 | result.append("//\n") 97 | 98 | return result 99 | 100 | 101 | text = "".join( 102 | [ 103 | processed_files["AUTHORS"], 104 | processed_files["CONTRIBUTORS"], 105 | *license_content(args.license), 106 | processed_files["constexpr_feature_detect.h"], 107 | processed_files["float_common.h"], 108 | processed_files["fast_float.h"], 109 | processed_files["ascii_number.h"], 110 | processed_files["fast_table.h"], 111 | processed_files["decimal_to_binary.h"], 112 | processed_files["bigint.h"], 113 | processed_files["digit_comparison.h"], 114 | processed_files["parse_number.h"], 115 | ] 116 | ) 117 | 118 | if args.output: 119 | with open(args.output, "wt", encoding="utf8") as f: 120 | f.write(text) 121 | else: 122 | print(text) 123 | -------------------------------------------------------------------------------- /script/analysis.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from math import floor 3 | 4 | 5 | def log2(x): 6 | """returns ceil(log2(x)))""" 7 | y = 0 8 | while (1 << y) < x: 9 | y = y + 1 10 | return y 11 | 12 | 13 | for q in range(1, 17 + 1): 14 | d = 5 ** q 15 | b = 127 + log2(d) 16 | t = 2 ** b 17 | c = t // d + 1 18 | assert c < 2 ** 128 19 | assert c >= 2 ** 127 20 | K = 2 ** 127 21 | if not (c * K * d <= (K + 1) * t): 22 | print(q) 23 | top = floor(t / (c * d - t)) 24 | sys.exit(-1) 25 | 26 | for q in range(18, 344 + 1): 27 | d = 5 ** q 28 | b = 64 + 2 * log2(d) 29 | t = 2 ** b 30 | c = t // d + 1 31 | assert c > 2 ** (64 + log2(d)) 32 | K = 2 ** 64 33 | if not (c * K * d <= (K + 1) * t): 34 | print(q) 35 | top = floor(t / (c * d - t)) 36 | sys.exit(-1) 37 | 38 | print("all good") 39 | -------------------------------------------------------------------------------- /script/mushtak_lemire.py: -------------------------------------------------------------------------------- 1 | # 2 | # Reference : 3 | # Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to appear) 4 | # 5 | 6 | all_tqs = [] 7 | 8 | # Generates all possible values of T[q] 9 | # Appendix B of Number parsing at a gigabyte per second. 10 | # Software: Practice and Experience 2021;51(8):1700–1727. 11 | for q in range(-342, -27): 12 | power5 = 5 ** -q 13 | z = 0 14 | while (1 << z) < power5: 15 | z += 1 16 | b = 2 * z + 2 * 64 17 | c = 2 ** b // power5 + 1 18 | while c >= (1 << 128): 19 | c //= 2 20 | all_tqs.append(c) 21 | for q in range(-27, 0): 22 | power5 = 5 ** -q 23 | z = 0 24 | while (1 << z) < power5: 25 | z += 1 26 | b = z + 127 27 | c = 2 ** b // power5 + 1 28 | all_tqs.append(c) 29 | for q in range(0, 308 + 1): 30 | power5 = 5 ** q 31 | while power5 < (1 << 127): 32 | power5 *= 2 33 | while power5 >= (1 << 128): 34 | power5 //= 2 35 | all_tqs.append(power5) 36 | 37 | # Returns the continued fraction of numer/denom as a list [a0; a1, a2, ..., an] 38 | def continued_fraction(numer, denom): 39 | # (look at page numbers in top-left, not PDF page numbers) 40 | cf = [] 41 | while denom != 0: 42 | quot, rem = divmod(numer, denom) 43 | cf.append(quot) 44 | numer, denom = denom, rem 45 | return cf 46 | 47 | 48 | # Given a continued fraction [a0; a1, a2, ..., an], returns 49 | # all the convergents of that continued fraction 50 | # as pairs of the form (numer, denom), where numer/denom is 51 | # a convergent of the continued fraction in simple form. 52 | def convergents(cf): 53 | p_n_minus_2 = 0 54 | q_n_minus_2 = 1 55 | p_n_minus_1 = 1 56 | q_n_minus_1 = 0 57 | convergents = [] 58 | for a_n in cf: 59 | p_n = a_n * p_n_minus_1 + p_n_minus_2 60 | q_n = a_n * q_n_minus_1 + q_n_minus_2 61 | convergents.append((p_n, q_n)) 62 | p_n_minus_2, q_n_minus_2, p_n_minus_1, q_n_minus_1 = ( 63 | p_n_minus_1, 64 | q_n_minus_1, 65 | p_n, 66 | q_n, 67 | ) 68 | return convergents 69 | 70 | 71 | # Enumerate through all the convergents of T[q] / 2^137 with denominators < 2^64 72 | found_solution = False 73 | for j, tq in enumerate(all_tqs): 74 | for _, w in convergents(continued_fraction(tq, 2 ** 137)): 75 | if w >= 2 ** 64: 76 | break 77 | if (tq * w) % 2 ** 137 > 2 ** 137 - 2 ** 64: 78 | print(f"SOLUTION: q={j-342} T[q]={tq} w={w}") 79 | found_solution = True 80 | if not found_solution: 81 | print("No solutions!") 82 | -------------------------------------------------------------------------------- /script/release.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ######################################################################## 3 | # Generates a new release. 4 | ######################################################################## 5 | import sys 6 | import re 7 | import subprocess 8 | import io 9 | import os 10 | import fileinput 11 | 12 | if sys.version_info < (3, 0): 13 | sys.stdout.write("Sorry, requires Python 3.x or better\n") 14 | sys.exit(1) 15 | 16 | 17 | def colored(r, g, b, text): 18 | return f"\033[38;2;{r};{g};{b}m{text} \033[38;2;255;255;255m" 19 | 20 | 21 | def extractnumbers(s): 22 | return tuple(map(int, re.findall(r"(\d+)\.(\d+)\.(\d+)", str(s))[0])) 23 | 24 | 25 | def toversionstring(major, minor, rev): 26 | return f"{major}.{minor}.{rev}" 27 | 28 | 29 | print("Calling git rev-parse --abbrev-ref HEAD") 30 | pipe = subprocess.Popen( 31 | ["git", "rev-parse", "--abbrev-ref", "HEAD"], 32 | stdout=subprocess.PIPE, 33 | stderr=subprocess.STDOUT, 34 | ) 35 | branchresult = pipe.communicate()[0].decode().strip() 36 | 37 | if branchresult != "main": 38 | print( 39 | colored( 40 | 255, 41 | 0, 42 | 0, 43 | f"We recommend that you release on main, you are on '{branchresult}'", 44 | ) 45 | ) 46 | 47 | ret = subprocess.call(["git", "remote", "update"]) 48 | 49 | if ret != 0: 50 | sys.exit(ret) 51 | print("Calling git log HEAD.. --oneline") 52 | pipe = subprocess.Popen( 53 | ["git", "log", "HEAD..", "--oneline"], 54 | stdout=subprocess.PIPE, 55 | stderr=subprocess.STDOUT, 56 | ) 57 | uptodateresult = pipe.communicate()[0].decode().strip() 58 | 59 | if len(uptodateresult) != 0: 60 | print(uptodateresult) 61 | sys.exit(-1) 62 | 63 | pipe = subprocess.Popen( 64 | ["git", "rev-parse", "--show-toplevel"], 65 | stdout=subprocess.PIPE, 66 | stderr=subprocess.STDOUT, 67 | ) 68 | maindir = pipe.communicate()[0].decode().strip() 69 | scriptlocation = os.path.dirname(os.path.abspath(__file__)) 70 | 71 | print(f"repository: {maindir}") 72 | 73 | pipe = subprocess.Popen( 74 | ["git", "describe", "--abbrev=0", "--tags"], 75 | stdout=subprocess.PIPE, 76 | stderr=subprocess.STDOUT, 77 | ) 78 | versionresult = pipe.communicate()[0].decode().strip() 79 | 80 | print(f"last version: {versionresult}") 81 | try: 82 | currentv = extractnumbers(versionresult) 83 | except: 84 | currentv = [0, 0, 0] 85 | if len(sys.argv) != 2: 86 | nextv = (currentv[0], currentv[1], currentv[2] + 1) 87 | print(f"please specify version number, e.g. {toversionstring(*nextv)}") 88 | sys.exit(-1) 89 | try: 90 | newversion = extractnumbers(sys.argv[1]) 91 | print(newversion) 92 | except: 93 | print(f"can't parse version number {sys.argv[1]}") 94 | sys.exit(-1) 95 | 96 | print("checking that new version is valid") 97 | if newversion[0] != currentv[0]: 98 | assert newversion[0] == currentv[0] + 1 99 | assert newversion[1] == 0 100 | assert newversion[2] == 0 101 | elif newversion[1] != currentv[1]: 102 | assert newversion[1] == currentv[1] + 1 103 | assert newversion[2] == 0 104 | else: 105 | assert newversion[2] == currentv[2] + 1 106 | 107 | atleastminor = (currentv[0] != newversion[0]) or (currentv[1] != newversion[1]) 108 | 109 | newmajorversionstring = str(newversion[0]) 110 | newminorversionstring = str(newversion[1]) 111 | newpatchversionstring = str(newversion[2]) 112 | newversionstring = f"{newversion[0]}.{newversion[1]}.{newversion[2]}" 113 | cmakefile = f"{maindir}{os.sep}CMakeLists.txt" 114 | 115 | for line in fileinput.input(cmakefile, inplace=1, backup=".bak"): 116 | line = re.sub( 117 | r"project\(fast_float VERSION \d+\.\d+\.\d+ LANGUAGES CXX\)", 118 | f"project(fast_float VERSION {newversionstring} LANGUAGES CXX)", 119 | line.rstrip(), 120 | ) 121 | print(line) 122 | 123 | print(f"modified {cmakefile}, a backup was made") 124 | 125 | versionfilerel = f"{os.sep}include{os.sep}fast_float{os.sep}float_common.h" 126 | versionfile = f"{maindir}{versionfilerel}" 127 | 128 | for line in fileinput.input(versionfile, inplace=1, backup=".bak"): 129 | line = re.sub( 130 | r"#define FASTFLOAT_VERSION_MAJOR \d+", 131 | f"#define FASTFLOAT_VERSION_MAJOR {newmajorversionstring}", 132 | line.rstrip(), 133 | ) 134 | line = re.sub( 135 | r"#define FASTFLOAT_VERSION_MINOR \d+", 136 | f"#define FASTFLOAT_VERSION_MINOR {newminorversionstring}", 137 | line.rstrip(), 138 | ) 139 | line = re.sub( 140 | r"#define FASTFLOAT_VERSION_PATCH \d+", 141 | f"#define FASTFLOAT_VERSION_PATCH {newpatchversionstring}", 142 | line.rstrip(), 143 | ) 144 | print(line) 145 | 146 | print(f"{versionfile} modified") 147 | 148 | readmefile = f"{maindir}{os.sep}README.md" 149 | 150 | for line in fileinput.input(readmefile, inplace=1, backup=".bak"): 151 | line = re.sub( 152 | r"https://github.com/fastfloat/fast_float/releases/download/v(\d+\.\d+\.\d+)/fast_float.h", 153 | f"https://github.com/fastfloat/fast_float/releases/download/v{newversionstring}/fast_float.h", 154 | line.rstrip(), 155 | ) 156 | line = re.sub( 157 | r"GIT_TAG tags/v(\d+\.\d+\.\d+)", 158 | f"GIT_TAG tags/v{newversionstring}", 159 | line.rstrip(), 160 | ) 161 | line = re.sub( 162 | r"GIT_TAG v(\d+\.\d+\.\d+)\)", f"GIT_TAG v{newversionstring})", line.rstrip() 163 | ) 164 | print(line) 165 | 166 | print(f"modified {readmefile}, a backup was made") 167 | 168 | print("running amalgamate.py") 169 | with open(f"{maindir}{os.sep}fast_float.h", "w") as outfile: 170 | cp = subprocess.run( 171 | [f"python3", f"{maindir}{os.sep}script{os.sep}amalgamate.py"], stdout=outfile 172 | ) 173 | 174 | if cp.returncode != 0: 175 | print("Failed to run amalgamate") 176 | else: 177 | print("amalgamate.py ran successfully") 178 | print(f"You should upload {maindir}{os.sep}fast_float.h") 179 | 180 | print("Please run the tests before issuing a release.\n") 181 | print( 182 | f'to issue release, enter\n git commit -a && git push && git tag -a v{toversionstring(*newversion)} -m "version {toversionstring(*newversion)}" && git push --tags\n' 183 | ) 184 | -------------------------------------------------------------------------------- /script/run-clangcldocker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | COMMAND=$* 4 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" 5 | MAINSOURCE=$SCRIPTPATH/.. 6 | ALL_FILES=$(cd $MAINSOURCE && git ls-tree --full-tree --name-only -r HEAD | grep -e ".*\.\(c\|h\|cc\|cpp\|hh\)\$" | grep -vFf clang-format-ignore.txt) 7 | 8 | if clang-format-17 --version 2>/dev/null | grep -qF 'version 17.'; then 9 | cd $MAINSOURCE; clang-format-17 --style=file --verbose -i "$@" $ALL_FILES 10 | exit 0 11 | elif clang-format --version 2>/dev/null | grep -qF 'version 17.'; then 12 | cd $MAINSOURCE; clang-format --style=file --verbose -i "$@" $ALL_FILES 13 | exit 0 14 | fi 15 | echo "Trying to use docker" 16 | command -v docker >/dev/null 2>&1 || { echo >&2 "Please install docker. E.g., go to https://www.docker.com/products/docker-desktop Type 'docker' to diagnose the problem."; exit 1; } 17 | docker info >/dev/null 2>&1 || { echo >&2 "Docker server is not running? type 'docker info'."; exit 1; } 18 | 19 | if [ -t 0 ]; then DOCKER_ARGS=-it; fi 20 | docker pull kszonek/clang-format-17 21 | 22 | docker run --rm $DOCKER_ARGS -v "$MAINSOURCE":"$MAINSOURCE":Z -w "$MAINSOURCE" -u "$(id -u $USER):$(id -g $USER)" kszonek/clang-format-17 --style=file --verbose -i "$@" $ALL_FILES 23 | -------------------------------------------------------------------------------- /script/table_generation.py: -------------------------------------------------------------------------------- 1 | def format(number): 2 | upper = number // (1 << 64) 3 | lower = number % (1 << 64) 4 | print("" + hex(upper) + "," + hex(lower) + ",") 5 | 6 | 7 | for q in range(-342, 0): 8 | power5 = 5 ** -q 9 | z = 0 10 | while (1 << z) < power5: 11 | z += 1 12 | if q >= -27: 13 | b = z + 127 14 | c = 2 ** b // power5 + 1 15 | format(c) 16 | else: 17 | b = 2 * z + 2 * 64 18 | c = 2 ** b // power5 + 1 19 | # truncate 20 | while c >= (1 << 128): 21 | c //= 2 22 | format(c) 23 | 24 | for q in range(0, 308 + 1): 25 | power5 = 5 ** q 26 | # move the most significant bit in position 27 | while power5 < (1 << 127): 28 | power5 *= 2 29 | # *truncate* 30 | while power5 >= (1 << 128): 31 | power5 //= 2 32 | format(power5) 33 | -------------------------------------------------------------------------------- /tests/BUILD.bazel: -------------------------------------------------------------------------------- 1 | cc_test( 2 | name = "basictest", 3 | srcs = ["basictest.cpp"], 4 | deps = [ 5 | "//:fast_float", 6 | "@doctest//doctest", 7 | ], 8 | ) 9 | 10 | cc_test( 11 | name = "example_test", 12 | srcs = ["example_test.cpp"], 13 | deps = [ 14 | "//:fast_float", 15 | "@doctest//doctest", 16 | ], 17 | ) 18 | 19 | cc_test( 20 | name = "example_comma_test", 21 | srcs = ["example_comma_test.cpp"], 22 | deps = [ 23 | "//:fast_float", 24 | "@doctest//doctest", 25 | ], 26 | ) 27 | 28 | cc_test( 29 | name = "fast_int", 30 | srcs = ["fast_int.cpp"], 31 | deps = [ 32 | "//:fast_float", 33 | "@doctest//doctest", 34 | ], 35 | ) 36 | 37 | cc_test( 38 | name = "fixedwidthtest", 39 | srcs = ["fixedwidthtest.cpp"], 40 | deps = [ 41 | "//:fast_float", 42 | "@doctest//doctest", 43 | ], 44 | ) 45 | 46 | cc_test( 47 | name = "fortran", 48 | srcs = ["fortran.cpp"], 49 | deps = [ 50 | "//:fast_float", 51 | "@doctest//doctest", 52 | ], 53 | ) 54 | 55 | cc_test( 56 | name = "json_fmt", 57 | srcs = ["json_fmt.cpp"], 58 | deps = [ 59 | "//:fast_float", 60 | "@doctest//doctest", 61 | ], 62 | ) 63 | 64 | cc_test( 65 | name = "long_test", 66 | srcs = ["long_test.cpp"], 67 | deps = [ 68 | "//:fast_float", 69 | "@doctest//doctest", 70 | ], 71 | ) 72 | 73 | cc_test( 74 | name = "powersoffive_hardround", 75 | srcs = ["powersoffive_hardround.cpp"], 76 | deps = [ 77 | "//:fast_float", 78 | "@doctest//doctest", 79 | ], 80 | ) 81 | 82 | cc_test( 83 | name = "rcppfastfloat_test", 84 | srcs = ["rcppfastfloat_test.cpp"], 85 | deps = [ 86 | "//:fast_float", 87 | "@doctest//doctest", 88 | ], 89 | ) 90 | 91 | cc_test( 92 | name = "wide_char_test", 93 | srcs = ["wide_char_test.cpp"], 94 | deps = [ 95 | "//:fast_float", 96 | "@doctest//doctest", 97 | ], 98 | ) 99 | 100 | cc_test( 101 | name = "supported_chars_test", 102 | srcs = ["supported_chars_test.cpp"], 103 | deps = [ 104 | "//:fast_float", 105 | "@doctest//doctest", 106 | ], 107 | ) 108 | 109 | cc_test( 110 | name = "string_test", 111 | srcs = ["string_test.cpp"], 112 | deps = [ 113 | "//:fast_float", 114 | "@doctest//doctest", 115 | ], 116 | ) 117 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # FetchContent requires cmake >=3.11 2 | # see https://cmake.org/cmake/help/v3.11/module/FetchContent.html 3 | cmake_minimum_required(VERSION 3.11 FATAL_ERROR) 4 | 5 | include(FetchContent) 6 | 7 | option(SYSTEM_DOCTEST "Use system copy of doctest" OFF) 8 | option(FASTFLOAT_SUPPLEMENTAL_TESTS "Run supplemental tests" ON) 9 | 10 | if (NOT SYSTEM_DOCTEST) 11 | FetchContent_Declare(doctest 12 | GIT_REPOSITORY https://github.com/onqtam/doctest.git 13 | GIT_TAG v2.4.11) 14 | else () 15 | find_package(doctest REQUIRED) 16 | endif() 17 | if (FASTFLOAT_SUPPLEMENTAL_TESTS) 18 | FetchContent_Declare(supplemental_test_files 19 | GIT_REPOSITORY https://github.com/fastfloat/supplemental_test_files.git 20 | GIT_TAG origin/main) 21 | endif() 22 | 23 | 24 | # FetchContent_MakeAvailable() was only introduced in 3.14 25 | # https://cmake.org/cmake/help/v3.14/release/3.14.html#modules 26 | # FetchContent_MakeAvailable(doctest) 27 | if (NOT SYSTEM_DOCTEST) 28 | FetchContent_GetProperties(doctest) 29 | if(NOT doctest_POPULATED) 30 | FetchContent_Populate(doctest) 31 | add_subdirectory(${doctest_SOURCE_DIR} ${doctest_BINARY_DIR}) 32 | endif() 33 | endif() 34 | 35 | add_library(supplemental-data INTERFACE) 36 | if (FASTFLOAT_SUPPLEMENTAL_TESTS) 37 | FetchContent_GetProperties(supplemental_test_files) 38 | if(NOT supplemental_test_files_POPULATED) 39 | message(STATUS "Supplemental tests enabled. Retrieving test files.") 40 | FetchContent_Populate(supplemental_test_files) 41 | message(STATUS "Supplemental test files retrieved.") 42 | add_subdirectory(${supplemental_test_files_SOURCE_DIR} ${supplemental_test_files_BINARY_DIR}) 43 | endif() 44 | target_compile_definitions(supplemental-data INTERFACE SUPPLEMENTAL_TEST_DATA_DIR="${supplemental_test_files_BINARY_DIR}/data") 45 | endif() 46 | 47 | function(fast_float_add_cpp_test TEST_NAME) 48 | add_executable(${TEST_NAME} ${TEST_NAME}.cpp) 49 | if(CMAKE_CROSSCOMPILING) 50 | set(ccemulator ${CMAKE_CROSSCOMPILING_EMULATOR}) 51 | endif() 52 | add_test(NAME ${TEST_NAME} 53 | COMMAND ${ccemulator} $) 54 | if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 55 | target_compile_options(${TEST_NAME} PUBLIC /EHsc) 56 | endif() 57 | if(NOT WIN32) 58 | target_compile_options(${TEST_NAME} PUBLIC -Werror -Wall -Wextra -Weffc++) 59 | target_compile_options(${TEST_NAME} PUBLIC -Wsign-compare -Wshadow -Wwrite-strings -Wpointer-arith -Winit-self -Wconversion -Wsign-conversion) 60 | endif() 61 | target_link_libraries(${TEST_NAME} PUBLIC fast_float supplemental-data) 62 | if (NOT SYSTEM_DOCTEST) 63 | target_link_libraries(${TEST_NAME} PUBLIC doctest) 64 | else () 65 | target_link_libraries(${TEST_NAME} PUBLIC doctest::doctest) 66 | endif() 67 | endfunction(fast_float_add_cpp_test) 68 | 69 | fast_float_add_cpp_test(rcppfastfloat_test) 70 | fast_float_add_cpp_test(wide_char_test) 71 | fast_float_add_cpp_test(supported_chars_test) 72 | fast_float_add_cpp_test(example_test) 73 | fast_float_add_cpp_test(example_comma_test) 74 | fast_float_add_cpp_test(basictest) 75 | option(FASTFLOAT_CONSTEXPR_TESTS "Require constexpr tests (build will fail if the compiler won't support it)" OFF) 76 | if (FASTFLOAT_CONSTEXPR_TESTS) 77 | target_compile_features(basictest PRIVATE cxx_std_20) 78 | target_compile_definitions(basictest PRIVATE FASTFLOAT_CONSTEXPR_TESTS) 79 | else() 80 | target_compile_features(basictest PRIVATE cxx_std_17) 81 | endif() 82 | if (FASTFLOAT_SUPPLEMENTAL_TESTS) 83 | target_compile_definitions(basictest PRIVATE FASTFLOAT_SUPPLEMENTAL_TESTS) 84 | endif() 85 | 86 | fast_float_add_cpp_test(long_test) 87 | fast_float_add_cpp_test(powersoffive_hardround) 88 | fast_float_add_cpp_test(string_test) 89 | fast_float_add_cpp_test(fast_int) 90 | target_compile_features(fast_int PRIVATE cxx_std_17) 91 | fast_float_add_cpp_test(json_fmt) 92 | fast_float_add_cpp_test(fortran) 93 | if(CMAKE_CXX_STANDARD GREATER_EQUAL 23) 94 | option(FASTFLOAT_FIXEDWIDTH_TESTS "Require fixed width test for C++23 (build will fail if the compiler won't support it)" ON) 95 | else() 96 | option(FASTFLOAT_FIXEDWIDTH_TESTS "Require fixed width test for C++23 (build will fail if the compiler won't support it)" OFF) 97 | endif() 98 | if (FASTFLOAT_FIXEDWIDTH_TESTS) 99 | fast_float_add_cpp_test(fixedwidthtest) 100 | target_compile_features(fixedwidthtest PUBLIC cxx_std_23) 101 | endif() 102 | 103 | option(FASTFLOAT_EXHAUSTIVE "Exhaustive tests" OFF) 104 | 105 | if (FASTFLOAT_EXHAUSTIVE) 106 | fast_float_add_cpp_test(short_random_string) 107 | fast_float_add_cpp_test(exhaustive32_midpoint) 108 | fast_float_add_cpp_test(random_string) 109 | fast_float_add_cpp_test(exhaustive32) 110 | fast_float_add_cpp_test(exhaustive32_64) 111 | fast_float_add_cpp_test(long_exhaustive32) 112 | fast_float_add_cpp_test(long_exhaustive32_64) 113 | fast_float_add_cpp_test(long_random64) 114 | fast_float_add_cpp_test(random64) 115 | endif(FASTFLOAT_EXHAUSTIVE) 116 | 117 | add_subdirectory(build_tests) 118 | add_subdirectory(bloat_analysis) 119 | -------------------------------------------------------------------------------- /tests/bloat_analysis/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(bloaty main.cpp a1.cpp a2.cpp a3.cpp a4.cpp a4.cpp a5.cpp a6.cpp a7.cpp a8.cpp a9.cpp a10.cpp) 2 | target_link_libraries(bloaty PUBLIC fast_float) 3 | 4 | add_executable(bloatyref main_ref.cpp) 5 | target_link_libraries(bloatyref PUBLIC fast_float) -------------------------------------------------------------------------------- /tests/bloat_analysis/a1.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | double get1(char const *input) { 4 | double result_value; 5 | auto result = 6 | fast_float::from_chars(input, input + strlen(input), result_value); 7 | if (result.ec != std::errc()) { 8 | return 1; 9 | } 10 | return result_value; 11 | } -------------------------------------------------------------------------------- /tests/bloat_analysis/a10.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | double get10(char const *input) { 4 | double result_value; 5 | auto result = 6 | fast_float::from_chars(input, input + strlen(input), result_value); 7 | if (result.ec != std::errc()) { 8 | return 10; 9 | } 10 | return result_value; 11 | } -------------------------------------------------------------------------------- /tests/bloat_analysis/a2.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | double get2(char const *input) { 4 | double result_value; 5 | auto result = 6 | fast_float::from_chars(input, input + strlen(input), result_value); 7 | if (result.ec != std::errc()) { 8 | return 2; 9 | } 10 | return result_value; 11 | } -------------------------------------------------------------------------------- /tests/bloat_analysis/a3.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | double get3(char const *input) { 4 | double result_value; 5 | auto result = 6 | fast_float::from_chars(input, input + strlen(input), result_value); 7 | if (result.ec != std::errc()) { 8 | return 3; 9 | } 10 | return result_value; 11 | } -------------------------------------------------------------------------------- /tests/bloat_analysis/a4.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | double get4(char const *input) { 4 | double result_value; 5 | auto result = 6 | fast_float::from_chars(input, input + strlen(input), result_value); 7 | if (result.ec != std::errc()) { 8 | return 4; 9 | } 10 | return result_value; 11 | } -------------------------------------------------------------------------------- /tests/bloat_analysis/a5.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | double get5(char const *input) { 4 | double result_value; 5 | auto result = 6 | fast_float::from_chars(input, input + strlen(input), result_value); 7 | if (result.ec != std::errc()) { 8 | return 5; 9 | } 10 | return result_value; 11 | } -------------------------------------------------------------------------------- /tests/bloat_analysis/a6.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | double get6(char const *input) { 4 | double result_value; 5 | auto result = 6 | fast_float::from_chars(input, input + strlen(input), result_value); 7 | if (result.ec != std::errc()) { 8 | return 6; 9 | } 10 | return result_value; 11 | } -------------------------------------------------------------------------------- /tests/bloat_analysis/a7.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | double get7(char const *input) { 4 | double result_value; 5 | auto result = 6 | fast_float::from_chars(input, input + strlen(input), result_value); 7 | if (result.ec != std::errc()) { 8 | return 7; 9 | } 10 | return result_value; 11 | } -------------------------------------------------------------------------------- /tests/bloat_analysis/a8.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | double get8(char const *input) { 4 | double result_value; 5 | auto result = 6 | fast_float::from_chars(input, input + strlen(input), result_value); 7 | if (result.ec != std::errc()) { 8 | return 8; 9 | } 10 | return result_value; 11 | } -------------------------------------------------------------------------------- /tests/bloat_analysis/a9.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | double get9(char const *input) { 4 | double result_value; 5 | auto result = 6 | fast_float::from_chars(input, input + strlen(input), result_value); 7 | if (result.ec != std::errc()) { 8 | return 9; 9 | } 10 | return result_value; 11 | } -------------------------------------------------------------------------------- /tests/bloat_analysis/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | double get1(char const *input); 4 | double get2(char const *input); 5 | double get3(char const *input); 6 | double get4(char const *input); 7 | double get5(char const *input); 8 | double get6(char const *input); 9 | double get7(char const *input); 10 | double get8(char const *input); 11 | double get9(char const *input); 12 | double get10(char const *input); 13 | 14 | int main(int arg, char **argv) { 15 | double x = get1(argv[0]) + get2(argv[0]) + get3(argv[0]) + get4(argv[0]) + 16 | get5(argv[0]) + get6(argv[0]) + get7(argv[0]) + get8(argv[0]) + 17 | get9(argv[0]) + get10(argv[0]); 18 | 19 | return int(x); 20 | } -------------------------------------------------------------------------------- /tests/bloat_analysis/main_ref.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | double get(char const *input) { 4 | double result_value; 5 | auto result = 6 | fast_float::from_chars(input, input + strlen(input), result_value); 7 | if (result.ec != std::errc()) { 8 | return 10; 9 | } 10 | return result_value; 11 | } 12 | 13 | int main(int arg, char **argv) { 14 | double x = get(argv[0]); 15 | return int(x); 16 | } -------------------------------------------------------------------------------- /tests/build_tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(issue72) -------------------------------------------------------------------------------- /tests/build_tests/issue72/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(issue72 main.cpp foo.cpp) 2 | target_link_libraries(issue72 PUBLIC fast_float) -------------------------------------------------------------------------------- /tests/build_tests/issue72/foo.cpp: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | void foo() {} -------------------------------------------------------------------------------- /tests/build_tests/issue72/main.cpp: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | int main() { return 0; } -------------------------------------------------------------------------------- /tests/build_tests/issue72/test.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "fast_float/fast_float.h" -------------------------------------------------------------------------------- /tests/example_comma_test.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "fast_float/fast_float.h" 3 | #include 4 | #include 5 | #include 6 | 7 | int main() { 8 | std::string const input = "3,1416 xyz "; 9 | double result; 10 | fast_float::parse_options options{fast_float::chars_format::general, ','}; 11 | auto answer = fast_float::from_chars_advanced( 12 | input.data(), input.data() + input.size(), result, options); 13 | if ((answer.ec != std::errc()) || ((result != 3.1416))) { 14 | std::cerr << "parsing failure\n"; 15 | return EXIT_FAILURE; 16 | } 17 | std::cout << "parsed the number " << result << std::endl; 18 | return EXIT_SUCCESS; 19 | } 20 | -------------------------------------------------------------------------------- /tests/example_test.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "fast_float/fast_float.h" 3 | #include 4 | #include 5 | #include 6 | 7 | bool many() { 8 | std::string const input = "234532.3426362,7869234.9823,324562.645"; 9 | double result; 10 | auto answer = 11 | fast_float::from_chars(input.data(), input.data() + input.size(), result); 12 | if (answer.ec != std::errc()) { 13 | return false; 14 | } 15 | if (result != 234532.3426362) { 16 | return false; 17 | } 18 | if (answer.ptr[0] != ',') { 19 | return false; 20 | } 21 | answer = fast_float::from_chars(answer.ptr + 1, input.data() + input.size(), 22 | result); 23 | if (answer.ec != std::errc()) { 24 | return false; 25 | } 26 | if (result != 7869234.9823) { 27 | return false; 28 | } 29 | if (answer.ptr[0] != ',') { 30 | return false; 31 | } 32 | answer = fast_float::from_chars(answer.ptr + 1, input.data() + input.size(), 33 | result); 34 | if (answer.ec != std::errc()) { 35 | return false; 36 | } 37 | if (result != 324562.645) { 38 | return false; 39 | } 40 | return true; 41 | } 42 | 43 | void many_loop() { 44 | std::string const input = "234532.3426362,7869234.9823,324562.645"; 45 | double result; 46 | char const *pointer = input.data(); 47 | char const *end_pointer = input.data() + input.size(); 48 | 49 | while (pointer < end_pointer) { 50 | auto answer = fast_float::from_chars(pointer, end_pointer, result); 51 | if (answer.ec != std::errc()) { 52 | std::cerr << "error while parsing" << std::endl; 53 | break; 54 | } 55 | std::cout << "parsed: " << result << std::endl; 56 | pointer = answer.ptr; 57 | if ((answer.ptr < end_pointer) && (*pointer == ',')) { 58 | pointer++; 59 | } 60 | } 61 | } 62 | 63 | #if FASTFLOAT_IS_CONSTEXPR 64 | // consteval forces compile-time evaluation of the function in C++20. 65 | consteval double parse(std::string_view input) { 66 | double result; 67 | auto answer = 68 | fast_float::from_chars(input.data(), input.data() + input.size(), result); 69 | if (answer.ec != std::errc()) { 70 | return -1.0; 71 | } 72 | return result; 73 | } 74 | 75 | // This function should compile to a function which 76 | // merely returns 3.1415. 77 | constexpr double constexptest() { return parse("3.1415 input"); } 78 | #endif 79 | 80 | bool small() { 81 | double result = -1; 82 | std::string str = "3e-1000"; 83 | auto r = fast_float::from_chars(str.data(), str.data() + str.size(), result); 84 | if (r.ec != std::errc::result_out_of_range) { 85 | return false; 86 | } 87 | if (r.ptr != str.data() + 7) { 88 | return false; 89 | } 90 | if (result != 0) { 91 | return false; 92 | } 93 | printf("small values go to zero\n"); 94 | return true; 95 | } 96 | 97 | bool large() { 98 | double result = -1; 99 | std::string str = "3e1000"; 100 | auto r = fast_float::from_chars(str.data(), str.data() + str.size(), result); 101 | if (r.ec != std::errc::result_out_of_range) { 102 | return false; 103 | } 104 | if (r.ptr != str.data() + 6) { 105 | return false; 106 | } 107 | if (result != std::numeric_limits::infinity()) { 108 | return false; 109 | } 110 | printf("large values go to infinity\n"); 111 | return true; 112 | } 113 | 114 | int main() { 115 | std::string input = "3.1416 xyz "; 116 | double result; 117 | auto answer = 118 | fast_float::from_chars(input.data(), input.data() + input.size(), result); 119 | if ((answer.ec != std::errc()) || ((result != 3.1416))) { 120 | std::cerr << "parsing failure\n"; 121 | return EXIT_FAILURE; 122 | } 123 | std::cout << "parsed the number " << result << std::endl; 124 | #ifdef __STDCPP_FLOAT16_T__ 125 | printf("16-bit float\n"); 126 | // Parse as 16-bit float 127 | std::float16_t parsed_16{}; 128 | input = "10000e-1452"; 129 | auto fast_float_r16 = fast_float::from_chars( 130 | input.data(), input.data() + input.size(), parsed_16); 131 | if (fast_float_r16.ec != std::errc() && 132 | fast_float_r16.ec != std::errc::result_out_of_range) { 133 | std::cerr << "16-bit fast_float parsing failure for: " + input + "\n"; 134 | return false; 135 | } 136 | std::cout << "parsed the 16-bit value " << float(parsed_16) << std::endl; 137 | #endif 138 | if (!small()) { 139 | printf("Bug\n"); 140 | return EXIT_FAILURE; 141 | } 142 | if (!large()) { 143 | printf("Bug\n"); 144 | return EXIT_FAILURE; 145 | } 146 | 147 | if (!many()) { 148 | printf("Bug\n"); 149 | return EXIT_FAILURE; 150 | } 151 | many_loop(); 152 | #if FASTFLOAT_IS_CONSTEXPR 153 | if constexpr (constexptest() != 3.1415) { 154 | return EXIT_FAILURE; 155 | } 156 | #endif 157 | return EXIT_SUCCESS; 158 | } 159 | -------------------------------------------------------------------------------- /tests/exhaustive32.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "fast_float/fast_float.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | template char *to_string(T d, char *buffer) { 13 | auto written = std::snprintf(buffer, 64, "%.*e", 14 | std::numeric_limits::max_digits10 - 1, d); 15 | return buffer + written; 16 | } 17 | 18 | void allvalues() { 19 | char buffer[64]; 20 | for (uint64_t w = 0; w <= 0xFFFFFFFF; w++) { 21 | float v; 22 | if ((w % 1048576) == 0) { 23 | std::cout << "."; 24 | std::cout.flush(); 25 | } 26 | uint32_t word = uint32_t(w); 27 | memcpy(&v, &word, sizeof(v)); 28 | 29 | { 30 | char const *string_end = to_string(v, buffer); 31 | float result_value; 32 | auto result = fast_float::from_chars(buffer, string_end, result_value); 33 | // Starting with version 4.0 for fast_float, we return result_out_of_range 34 | // if the value is either too small (too close to zero) or too large 35 | // (effectively infinity). So std::errc::result_out_of_range is normal for 36 | // well-formed input strings. 37 | if (result.ec != std::errc() && 38 | result.ec != std::errc::result_out_of_range) { 39 | std::cerr << "parsing error ? " << buffer << std::endl; 40 | abort(); 41 | } 42 | if (std::isnan(v)) { 43 | if (!std::isnan(result_value)) { 44 | std::cerr << "not nan" << buffer << std::endl; 45 | abort(); 46 | } 47 | } else if (copysign(1, result_value) != copysign(1, v)) { 48 | std::cerr << "I got " << std::hexfloat << result_value 49 | << " but I was expecting " << v << std::endl; 50 | abort(); 51 | } else if (result_value != v) { 52 | std::cerr << "no match ? " << buffer << std::endl; 53 | std::cout << "started with " << std::hexfloat << v << std::endl; 54 | std::cout << "got back " << std::hexfloat << result_value << std::endl; 55 | std::cout << std::dec; 56 | abort(); 57 | } 58 | } 59 | } 60 | std::cout << std::endl; 61 | } 62 | 63 | int main() { 64 | allvalues(); 65 | std::cout << std::endl; 66 | std::cout << "all ok" << std::endl; 67 | return EXIT_SUCCESS; 68 | } 69 | -------------------------------------------------------------------------------- /tests/exhaustive32_64.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "fast_float/fast_float.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | template char *to_string(T d, char *buffer) { 14 | auto written = std::snprintf(buffer, 64, "%.*e", 15 | std::numeric_limits::max_digits10 - 1, d); 16 | return buffer + written; 17 | } 18 | 19 | bool basic_test_64bit(std::string vals, double val) { 20 | double result_value; 21 | auto result = fast_float::from_chars(vals.data(), vals.data() + vals.size(), 22 | result_value); 23 | if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) { 24 | std::cerr << " I could not parse " << vals << std::endl; 25 | return false; 26 | } 27 | if (std::isnan(val)) { 28 | if (!std::isnan(result_value)) { 29 | std::cerr << vals << std::endl; 30 | std::cerr << "not nan" << result_value << std::endl; 31 | return false; 32 | } 33 | } else if (copysign(1, result_value) != copysign(1, val)) { 34 | std::cerr << "I got " << std::hexfloat << result_value 35 | << " but I was expecting " << val << std::endl; 36 | return false; 37 | } else if (result_value != val) { 38 | std::cerr << vals << std::endl; 39 | std::cerr << "I got " << std::hexfloat << result_value 40 | << " but I was expecting " << val << std::endl; 41 | std::cerr << std::dec; 42 | std::cerr << "string: " << vals << std::endl; 43 | return false; 44 | } 45 | return true; 46 | } 47 | 48 | void all_32bit_values() { 49 | char buffer[64]; 50 | for (uint64_t w = 0; w <= 0xFFFFFFFF; w++) { 51 | float v32; 52 | if ((w % 1048576) == 0) { 53 | std::cout << "."; 54 | std::cout.flush(); 55 | } 56 | uint32_t word = uint32_t(w); 57 | memcpy(&v32, &word, sizeof(v32)); 58 | double v = v32; 59 | 60 | { 61 | char const *string_end = to_string(v, buffer); 62 | std::string s(buffer, size_t(string_end - buffer)); 63 | if (!basic_test_64bit(s, v)) { 64 | return; 65 | } 66 | } 67 | } 68 | std::cout << std::endl; 69 | } 70 | 71 | int main() { 72 | all_32bit_values(); 73 | std::cout << std::endl; 74 | std::cout << "all ok" << std::endl; 75 | return EXIT_SUCCESS; 76 | } 77 | -------------------------------------------------------------------------------- /tests/exhaustive32_midpoint.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) 12 | // Anything at all that is related to cygwin, msys and so forth will 13 | // always use this fallback because we cannot rely on it behaving as normal 14 | // gcc. 15 | #include 16 | #include 17 | 18 | // workaround for CYGWIN 19 | double cygwin_strtod_l(char const *start, char **end) { 20 | double d; 21 | std::stringstream ss; 22 | ss.imbue(std::locale::classic()); 23 | ss << start; 24 | ss >> d; 25 | if (ss.fail()) { 26 | *end = nullptr; 27 | } 28 | if (ss.eof()) { 29 | ss.clear(); 30 | } 31 | auto nread = ss.tellg(); 32 | *end = const_cast(start) + nread; 33 | return d; 34 | } 35 | 36 | float cygwin_strtof_l(char const *start, char **end) { 37 | float d; 38 | std::stringstream ss; 39 | ss.imbue(std::locale::classic()); 40 | ss << start; 41 | ss >> d; 42 | if (ss.fail()) { 43 | *end = nullptr; 44 | } 45 | if (ss.eof()) { 46 | ss.clear(); 47 | } 48 | auto nread = ss.tellg(); 49 | *end = const_cast(start) + nread; 50 | return d; 51 | } 52 | #endif 53 | 54 | template char *to_string(T d, char *buffer) { 55 | auto written = std::snprintf(buffer, 64, "%.*e", 56 | std::numeric_limits::max_digits10 - 1, d); 57 | return buffer + written; 58 | } 59 | 60 | void strtof_from_string(char const *st, float &d) { 61 | char *pr = (char *)st; 62 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 63 | defined(sun) || defined(__sun) 64 | d = cygwin_strtof_l(st, &pr); 65 | #elif defined(_WIN32) 66 | static _locale_t c_locale = _create_locale(LC_ALL, "C"); 67 | d = _strtof_l(st, &pr, c_locale); 68 | #else 69 | static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL); 70 | d = strtof_l(st, &pr, c_locale); 71 | #endif 72 | if (pr == st) { 73 | throw std::runtime_error("bug in strtod_from_string"); 74 | } 75 | } 76 | 77 | bool allvalues() { 78 | char buffer[64]; 79 | for (uint64_t w = 0; w <= 0xFFFFFFFF; w++) { 80 | float v; 81 | if ((w % 1048576) == 0) { 82 | std::cout << "."; 83 | std::cout.flush(); 84 | } 85 | uint32_t word = uint32_t(w); 86 | memcpy(&v, &word, sizeof(v)); 87 | if (std::isfinite(v)) { 88 | float nextf = std::nextafterf(v, INFINITY); 89 | if (copysign(1, v) != copysign(1, nextf)) { 90 | continue; 91 | } 92 | if (!std::isfinite(nextf)) { 93 | continue; 94 | } 95 | double v1{v}; 96 | assert(float(v1) == v); 97 | double v2{nextf}; 98 | assert(float(v2) == nextf); 99 | double midv{v1 + (v2 - v1) / 2}; 100 | float expected_midv = float(midv); 101 | 102 | char const *string_end = to_string(midv, buffer); 103 | float str_answer; 104 | strtof_from_string(buffer, str_answer); 105 | 106 | float result_value; 107 | auto result = fast_float::from_chars(buffer, string_end, result_value); 108 | // Starting with version 4.0 for fast_float, we return result_out_of_range 109 | // if the value is either too small (too close to zero) or too large 110 | // (effectively infinity). So std::errc::result_out_of_range is normal for 111 | // well-formed input strings. 112 | if (result.ec != std::errc() && 113 | result.ec != std::errc::result_out_of_range) { 114 | std::cerr << "parsing error ? " << buffer << std::endl; 115 | return false; 116 | } 117 | if (std::isnan(v)) { 118 | if (!std::isnan(result_value)) { 119 | std::cerr << "not nan" << buffer << std::endl; 120 | std::cerr << "v " << std::hexfloat << v << std::endl; 121 | std::cerr << "v2 " << std::hexfloat << v2 << std::endl; 122 | std::cerr << "midv " << std::hexfloat << midv << std::endl; 123 | std::cerr << "expected_midv " << std::hexfloat << expected_midv 124 | << std::endl; 125 | return false; 126 | } 127 | } else if (copysign(1, result_value) != copysign(1, v)) { 128 | std::cerr << buffer << std::endl; 129 | std::cerr << "v " << std::hexfloat << v << std::endl; 130 | std::cerr << "v2 " << std::hexfloat << v2 << std::endl; 131 | std::cerr << "midv " << std::hexfloat << midv << std::endl; 132 | std::cerr << "expected_midv " << std::hexfloat << expected_midv 133 | << std::endl; 134 | std::cerr << "I got " << std::hexfloat << result_value 135 | << " but I was expecting " << v << std::endl; 136 | return false; 137 | } else if (result_value != str_answer) { 138 | std::cerr << "no match ? " << buffer << std::endl; 139 | std::cerr << "v " << std::hexfloat << v << std::endl; 140 | std::cerr << "v2 " << std::hexfloat << v2 << std::endl; 141 | std::cerr << "midv " << std::hexfloat << midv << std::endl; 142 | std::cerr << "expected_midv " << std::hexfloat << expected_midv 143 | << std::endl; 144 | std::cout << "started with " << std::hexfloat << midv << std::endl; 145 | std::cout << "round down to " << std::hexfloat << str_answer 146 | << std::endl; 147 | std::cout << "got back " << std::hexfloat << result_value << std::endl; 148 | std::cout << std::dec; 149 | return false; 150 | } 151 | } 152 | } 153 | std::cout << std::endl; 154 | return true; 155 | } 156 | 157 | inline void Assert(bool Assertion) { 158 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 159 | defined(sun) || defined(__sun) 160 | if (!Assertion) { 161 | std::cerr << "Omitting hard failure on msys/cygwin/sun systems."; 162 | } 163 | #else 164 | if (!Assertion) { 165 | throw std::runtime_error("bug"); 166 | } 167 | #endif 168 | } 169 | 170 | int main() { 171 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 172 | defined(sun) || defined(__sun) 173 | std::cout << "Warning: msys/cygwin or solaris detected. This particular test " 174 | "is likely to generate false failures due to our reliance on " 175 | "the underlying runtime library as a gold standard." 176 | << std::endl; 177 | #endif 178 | Assert(allvalues()); 179 | std::cout << std::endl; 180 | std::cout << "all ok" << std::endl; 181 | return EXIT_SUCCESS; 182 | } 183 | -------------------------------------------------------------------------------- /tests/fixedwidthtest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "fast_float/fast_float.h" 6 | #include 7 | 8 | #if __cplusplus >= 202300L 9 | #include 10 | #endif 11 | 12 | int main() { 13 | // Write some testcases for the parsing of floating point numbers in the 14 | // float32_t type. We use the from_chars function defined in this library. 15 | #ifdef __STDCPP_FLOAT32_T__ 16 | std::vector const float32_test_expected{ 17 | 123.456f, -78.9f, 0.0001f, 3.40282e+038f}; 18 | std::vector const float32_test{"123.456", "-78.9", "0.0001", 19 | "3.40282e+038"}; 20 | std::cout << "runing float32 test" << std::endl; 21 | for (std::size_t i = 0; i < float32_test.size(); ++i) { 22 | auto const &f = float32_test[i]; 23 | std::float32_t result; 24 | auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); 25 | 26 | if (answer.ec != std::errc()) { 27 | std::cerr << "Failed to parse: \"" << f << "\"" << std::endl; 28 | return EXIT_FAILURE; 29 | } 30 | if (result != float32_test_expected[i]) { 31 | std::cerr << "Test failed for input: \"" << f << "\" expected " 32 | << float32_test_expected[i] << " got " << result << std::endl; 33 | return EXIT_FAILURE; 34 | } 35 | } 36 | #else 37 | std::cout << "No std::float32_t type available." << std::endl; 38 | #endif 39 | 40 | #ifdef __STDCPP_FLOAT64_T__ 41 | // Test cases for std::float64_t 42 | std::vector const float64_test_expected{ 43 | 1.23e4, -5.67e-8, 1.7976931348623157e+308, -1.7976931348623157e+308}; 44 | std::vector const float64_test{"1.23e4", "-5.67e-8", 45 | "1.7976931348623157e+308", 46 | "-1.7976931348623157e+308"}; 47 | std::cout << "runing float64 test" << std::endl; 48 | for (std::size_t i = 0; i < float64_test.size(); ++i) { 49 | auto const &f = float64_test[i]; 50 | std::float64_t result; 51 | auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result); 52 | 53 | if (answer.ec != std::errc()) { 54 | std::cerr << "Failed to parse: \"" << f << "\"" << std::endl; 55 | return EXIT_FAILURE; 56 | } 57 | if (result != float64_test_expected[i]) { 58 | std::cerr << "Test failed for input: \"" << f << "\" expected " 59 | << float64_test_expected[i] << " got " << result << std::endl; 60 | return EXIT_FAILURE; 61 | } 62 | } 63 | #else 64 | std::cout << "No std::float64_t type available." << std::endl; 65 | #endif 66 | std::cout << "All tests passed successfully." << std::endl; 67 | return EXIT_SUCCESS; 68 | 69 | return 0; 70 | } -------------------------------------------------------------------------------- /tests/fortran.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Exercise the Fortran conversion option. 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include "fast_float/fast_float.h" 8 | 9 | int main_readme() { 10 | std::string const input = "1d+4"; 11 | double result; 12 | fast_float::parse_options options{ 13 | fast_float::chars_format::fortran | 14 | fast_float::chars_format::allow_leading_plus}; 15 | auto answer = fast_float::from_chars_advanced( 16 | input.data(), input.data() + input.size(), result, options); 17 | if ((answer.ec != std::errc()) || ((result != 10000))) { 18 | std::cerr << "parsing failure\n" << result << "\n"; 19 | return EXIT_FAILURE; 20 | } 21 | std::cout << "parsed the number " << result << std::endl; 22 | return EXIT_SUCCESS; 23 | } 24 | 25 | int main() { 26 | std::vector const expected{10000, 1000, 100, 10, 1, 27 | .1, .01, .001, .0001}; 28 | std::vector const fmt1{"1+4", "1+3", "1+2", "1+1", "1+0", 29 | "1-1", "1-2", "1-3", "1-4"}; 30 | std::vector const fmt2{"1d+4", "1d+3", "1d+2", "1d+1", "1d+0", 31 | "1d-1", "1d-2", "1d-3", "1d-4"}; 32 | std::vector const fmt3{"+1+4", "+1+3", "+1+2", "+1+1", "+1+0", 33 | "+1-1", "+1-2", "+1-3", "+1-4"}; 34 | fast_float::parse_options const options{ 35 | fast_float::chars_format::fortran | 36 | fast_float::chars_format::allow_leading_plus}; 37 | 38 | for (auto const &f : fmt1) { 39 | auto d{std::distance(&fmt1[0], &f)}; 40 | double result; 41 | auto answer{fast_float::from_chars_advanced(f.data(), f.data() + f.size(), 42 | result, options)}; 43 | if (answer.ec != std::errc() || result != expected[std::size_t(d)]) { 44 | std::cerr << "parsing failure on " << f << std::endl; 45 | return EXIT_FAILURE; 46 | } 47 | } 48 | 49 | for (auto const &f : fmt2) { 50 | auto d{std::distance(&fmt2[0], &f)}; 51 | double result; 52 | auto answer{fast_float::from_chars_advanced(f.data(), f.data() + f.size(), 53 | result, options)}; 54 | if (answer.ec != std::errc() || result != expected[std::size_t(d)]) { 55 | std::cerr << "parsing failure on " << f << std::endl; 56 | return EXIT_FAILURE; 57 | } 58 | } 59 | 60 | for (auto const &f : fmt3) { 61 | auto d{std::distance(&fmt3[0], &f)}; 62 | double result; 63 | auto answer{fast_float::from_chars_advanced(f.data(), f.data() + f.size(), 64 | result, options)}; 65 | if (answer.ec != std::errc() || result != expected[std::size_t(d)]) { 66 | std::cerr << "parsing failure on " << f << std::endl; 67 | return EXIT_FAILURE; 68 | } 69 | } 70 | if (main_readme() != EXIT_SUCCESS) { 71 | return EXIT_FAILURE; 72 | } 73 | 74 | return EXIT_SUCCESS; 75 | } 76 | -------------------------------------------------------------------------------- /tests/installation_tests/find/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | project(test_install VERSION 0.1.0 LANGUAGES CXX) 4 | 5 | set(FASTFLOAT_CXX_STANDARD 17 CACHE STRING "the C++ standard to use for fastfloat") 6 | set(CMAKE_CXX_STANDARD ${FASTFLOAT_CXX_STANDARD}) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | if(MSVC_VERSION GREATER 1910) 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -permissive-") 10 | endif() 11 | find_package(FastFloat REQUIRED) 12 | 13 | 14 | 15 | file(WRITE main.cpp " 16 | #include \"fast_float/fast_float.h\" 17 | #include 18 | 19 | int main() { 20 | std::string input = \"3.1416 xyz \"; 21 | double result; 22 | auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result); 23 | if (answer.ec != std::errc()) { std::cerr << \"parsing failure\\n\"; return EXIT_FAILURE; } 24 | std::cout << \"parsed the number \" << result << std::endl; 25 | return EXIT_SUCCESS; 26 | }") 27 | 28 | 29 | 30 | add_executable(repro main.cpp) 31 | target_link_libraries(repro PUBLIC FastFloat::fast_float) 32 | -------------------------------------------------------------------------------- /tests/installation_tests/issue72_installation/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | project(test_simdjson_install VERSION 0.1.0 LANGUAGES CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | if(MSVC_VERSION GREATER 1910) 8 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -permissive-") 9 | endif() 10 | 11 | find_package(FastFloat REQUIRED) 12 | 13 | 14 | 15 | file(WRITE test.h " 16 | #pragma once 17 | #include \"fast_float/fast_float.h\"") 18 | 19 | file(WRITE main.cpp " 20 | #include \"test.h\" 21 | int main() { return 0; }") 22 | 23 | file(WRITE foo.cpp " 24 | #include \"test.h\" 25 | void foo() { }") 26 | add_executable(issue72 main.cpp main.cpp) 27 | target_link_libraries(issue72 PUBLIC FastFloat::fast_float) -------------------------------------------------------------------------------- /tests/json_fmt.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include "fast_float/fast_float.h" 6 | 7 | int main_readme() { 8 | std::string const input = "+.1"; // not valid 9 | double result; 10 | fast_float::parse_options options{ 11 | fast_float::chars_format::json | 12 | fast_float::chars_format::allow_leading_plus}; // should be ignored 13 | auto answer = fast_float::from_chars_advanced( 14 | input.data(), input.data() + input.size(), result, options); 15 | if (answer.ec == std::errc()) { 16 | std::cerr << "should have failed\n"; 17 | return EXIT_FAILURE; 18 | } 19 | return EXIT_SUCCESS; 20 | } 21 | 22 | int main_readme2() { 23 | std::string const input = "inf"; // not valid in JSON 24 | double result; 25 | fast_float::parse_options options{ 26 | fast_float::chars_format::json | 27 | fast_float::chars_format::allow_leading_plus}; // should be ignored 28 | auto answer = fast_float::from_chars_advanced( 29 | input.data(), input.data() + input.size(), result, options); 30 | if (answer.ec == std::errc()) { 31 | std::cerr << "should have failed\n"; 32 | return EXIT_FAILURE; 33 | } 34 | return EXIT_SUCCESS; 35 | } 36 | 37 | int main_readme3() { 38 | std::string const input = 39 | "inf"; // not valid in JSON but we allow it with json_or_infnan 40 | double result; 41 | fast_float::parse_options options{ 42 | fast_float::chars_format::json_or_infnan | 43 | fast_float::chars_format::allow_leading_plus}; // should be ignored 44 | auto answer = fast_float::from_chars_advanced( 45 | input.data(), input.data() + input.size(), result, options); 46 | if (answer.ec != std::errc() || (!std::isinf(result))) { 47 | std::cerr << "should have parsed infinity\n"; 48 | return EXIT_FAILURE; 49 | } 50 | return EXIT_SUCCESS; 51 | } 52 | 53 | struct ExpectedResult { 54 | double value; 55 | std::string junk_chars; 56 | }; 57 | 58 | struct AcceptedValue { 59 | std::string input; 60 | ExpectedResult expected; 61 | }; 62 | 63 | struct RejectReason { 64 | fast_float::parse_error error; 65 | intptr_t location_offset; 66 | }; 67 | 68 | struct RejectedValue { 69 | std::string input; 70 | RejectReason reason; 71 | }; 72 | 73 | int main() { 74 | std::vector const accept{ 75 | {"-0.2", {-0.2, ""}}, 76 | {"0.02", {0.02, ""}}, 77 | {"0.002", {0.002, ""}}, 78 | {"1e+0000", {1., ""}}, 79 | {"0e-2", {0., ""}}, 80 | {"1e", {1., "e"}}, 81 | {"1e+", {1., "e+"}}, 82 | {"inf", {std::numeric_limits::infinity(), ""}}}; 83 | std::vector const reject{ 84 | {"-.2", {fast_float::parse_error::missing_integer_after_sign, 1}}, 85 | {"00.02", {fast_float::parse_error::leading_zeros_in_integer_part, 0}}, 86 | {"0.e+1", {fast_float::parse_error::no_digits_in_fractional_part, 2}}, 87 | {"00.e+1", {fast_float::parse_error::leading_zeros_in_integer_part, 0}}, 88 | {".25", {fast_float::parse_error::no_digits_in_integer_part, 0}}, 89 | // The following cases already start as invalid JSON, so they are 90 | // handled as trailing junk and the error is for not having digits in the 91 | // empty string before the invalid token. 92 | {"+0.25", {fast_float::parse_error::no_digits_in_integer_part, 0}}, 93 | {"inf", {fast_float::parse_error::no_digits_in_integer_part, 0}}, 94 | {"nan(snan)", {fast_float::parse_error::no_digits_in_integer_part, 0}}}; 95 | 96 | for (std::size_t i = 0; i < accept.size(); ++i) { 97 | auto const &s = accept[i].input; 98 | auto const &expected = accept[i].expected; 99 | double result; 100 | auto answer = 101 | fast_float::from_chars(s.data(), s.data() + s.size(), result, 102 | fast_float::chars_format::json_or_infnan); 103 | if (answer.ec != std::errc()) { 104 | std::cerr << "json fmt rejected valid json " << s << std::endl; 105 | return EXIT_FAILURE; 106 | } 107 | if (result != expected.value) { 108 | std::cerr << "json fmt gave wrong result " << s << " (expected " 109 | << expected.value << " got " << result << ")" << std::endl; 110 | return EXIT_FAILURE; 111 | } 112 | if (std::string(answer.ptr) != expected.junk_chars) { 113 | std::cerr << "json fmt has wrong trailing characters " << s 114 | << " (expected " << expected.junk_chars << " got " << answer.ptr 115 | << ")" << std::endl; 116 | return EXIT_FAILURE; 117 | } 118 | } 119 | 120 | for (std::size_t i = 0; i < reject.size(); ++i) { 121 | auto const &s = reject[i].input; 122 | double result; 123 | auto answer = fast_float::from_chars(s.data(), s.data() + s.size(), result, 124 | fast_float::chars_format::json); 125 | if (answer.ec == std::errc()) { 126 | std::cerr << "json fmt accepted invalid json " << s << std::endl; 127 | return EXIT_FAILURE; 128 | } 129 | } 130 | 131 | for (std::size_t i = 0; i < reject.size(); ++i) { 132 | auto const &f = reject[i].input; 133 | auto const &expected_reason = reject[i].reason; 134 | auto answer = fast_float::parse_number_string( 135 | f.data(), f.data() + f.size(), 136 | fast_float::parse_options( 137 | fast_float::chars_format::json | 138 | fast_float::chars_format::allow_leading_plus)); // should be ignored 139 | if (answer.valid) { 140 | std::cerr << "json parse accepted invalid json " << f << std::endl; 141 | return EXIT_FAILURE; 142 | } 143 | if (answer.error != expected_reason.error) { 144 | std::cerr << "json parse failure had invalid error reason " << f 145 | << std::endl; 146 | return EXIT_FAILURE; 147 | } 148 | intptr_t error_location = answer.lastmatch - f.data(); 149 | if (error_location != expected_reason.location_offset) { 150 | std::cerr << "json parse failure had invalid error location " << f 151 | << " (expected " << expected_reason.location_offset << " got " 152 | << error_location << ")" << std::endl; 153 | return EXIT_FAILURE; 154 | } 155 | } 156 | 157 | if (main_readme() != EXIT_SUCCESS) { 158 | return EXIT_FAILURE; 159 | } 160 | if (main_readme2() != EXIT_SUCCESS) { 161 | return EXIT_FAILURE; 162 | } 163 | #ifndef __FAST_MATH__ 164 | if (main_readme3() != EXIT_SUCCESS) { 165 | return EXIT_FAILURE; 166 | } 167 | #endif 168 | 169 | return EXIT_SUCCESS; 170 | } -------------------------------------------------------------------------------- /tests/long_exhaustive32.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "fast_float/fast_float.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | template char *to_string(T d, char *buffer) { 12 | auto written = std::snprintf(buffer, 128, "%.*e", 64, d); 13 | return buffer + written; 14 | } 15 | 16 | void allvalues() { 17 | char buffer[128]; 18 | for (uint64_t w = 0; w <= 0xFFFFFFFF; w++) { 19 | float v; 20 | if ((w % 1048576) == 0) { 21 | std::cout << "."; 22 | std::cout.flush(); 23 | } 24 | uint32_t word = uint32_t(w); 25 | memcpy(&v, &word, sizeof(v)); 26 | 27 | { 28 | char const *string_end = to_string(v, buffer); 29 | float result_value; 30 | auto result = fast_float::from_chars(buffer, string_end, result_value); 31 | // Starting with version 4.0 for fast_float, we return result_out_of_range 32 | // if the value is either too small (too close to zero) or too large 33 | // (effectively infinity). So std::errc::result_out_of_range is normal for 34 | // well-formed input strings. 35 | if (result.ec != std::errc() && 36 | result.ec != std::errc::result_out_of_range) { 37 | std::cerr << "parsing error ? " << buffer << std::endl; 38 | abort(); 39 | } 40 | if (std::isnan(v)) { 41 | if (!std::isnan(result_value)) { 42 | std::cerr << "not nan" << buffer << std::endl; 43 | abort(); 44 | } 45 | } else if (copysign(1, result_value) != copysign(1, v)) { 46 | std::cerr << buffer << std::endl; 47 | std::cerr << "I got " << std::hexfloat << result_value 48 | << " but I was expecting " << v << std::endl; 49 | abort(); 50 | } else if (result_value != v) { 51 | std::cerr << "no match ? " << buffer << " got " << result_value 52 | << " expected " << v << std::endl; 53 | std::cout << "started with " << std::hexfloat << v << std::endl; 54 | std::cout << "got back " << std::hexfloat << result_value << std::endl; 55 | std::cout << std::dec; 56 | abort(); 57 | } 58 | } 59 | } 60 | std::cout << std::endl; 61 | } 62 | 63 | int main() { 64 | allvalues(); 65 | std::cout << std::endl; 66 | std::cout << "all ok" << std::endl; 67 | return EXIT_SUCCESS; 68 | } 69 | -------------------------------------------------------------------------------- /tests/long_exhaustive32_64.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | template char *to_string(T d, char *buffer) { 10 | auto written = std::snprintf(buffer, 128, "%.*e", 64, d); 11 | return buffer + written; 12 | } 13 | 14 | void all_32bit_values() { 15 | char buffer[128]; 16 | for (uint64_t w = 0; w <= 0xFFFFFFFF; w++) { 17 | float v32; 18 | if ((w % 1048576) == 0) { 19 | std::cout << "."; 20 | std::cout.flush(); 21 | } 22 | uint32_t word = uint32_t(w); 23 | memcpy(&v32, &word, sizeof(v32)); 24 | double v = v32; 25 | 26 | { 27 | char const *string_end = to_string(v, buffer); 28 | double result_value; 29 | auto result = fast_float::from_chars(buffer, string_end, result_value); 30 | // Starting with version 4.0 for fast_float, we return result_out_of_range 31 | // if the value is either too small (too close to zero) or too large 32 | // (effectively infinity). So std::errc::result_out_of_range is normal for 33 | // well-formed input strings. 34 | if (result.ec != std::errc() && 35 | result.ec != std::errc::result_out_of_range) { 36 | std::cerr << "parsing error ? " << buffer << std::endl; 37 | abort(); 38 | } 39 | if (std::isnan(v)) { 40 | if (!std::isnan(result_value)) { 41 | std::cerr << "not nan" << buffer << std::endl; 42 | abort(); 43 | } 44 | } else if (copysign(1, result_value) != copysign(1, v)) { 45 | std::cerr << "I got " << std::hexfloat << result_value 46 | << " but I was expecting " << v << std::endl; 47 | abort(); 48 | } else if (std::isnan(v)) { 49 | if (!std::isnan(result_value)) { 50 | std::cerr << "not nan" << buffer << std::endl; 51 | abort(); 52 | } 53 | } else if (result_value != v) { 54 | std::cerr << "no match ? " << buffer << std::endl; 55 | std::cout << "started with " << std::hexfloat << v << std::endl; 56 | std::cout << "got back " << std::hexfloat << result_value << std::endl; 57 | std::cout << std::dec; 58 | abort(); 59 | } 60 | } 61 | } 62 | std::cout << std::endl; 63 | } 64 | 65 | int main() { 66 | all_32bit_values(); 67 | std::cout << std::endl; 68 | std::cout << "all ok" << std::endl; 69 | return EXIT_SUCCESS; 70 | } 71 | -------------------------------------------------------------------------------- /tests/long_random64.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | template char *to_string(T d, char *buffer) { 11 | auto written = std::snprintf(buffer, 128, "%.*e", 64, d); 12 | return buffer + written; 13 | } 14 | 15 | static fast_float::value128 g_lehmer64_state; 16 | 17 | /** 18 | * D. H. Lehmer, Mathematical methods in large-scale computing units. 19 | * Proceedings of a Second Symposium on Large Scale Digital Calculating 20 | * Machinery; 21 | * Annals of the Computation Laboratory, Harvard Univ. 26 (1951), pp. 141-146. 22 | * 23 | * P L'Ecuyer, Tables of linear congruential generators of different sizes and 24 | * good lattice structure. Mathematics of Computation of the American 25 | * Mathematical 26 | * Society 68.225 (1999): 249-260. 27 | */ 28 | 29 | static inline void lehmer64_seed(uint64_t seed) { 30 | g_lehmer64_state.high = 0; 31 | g_lehmer64_state.low = seed; 32 | } 33 | 34 | static inline uint64_t lehmer64() { 35 | fast_float::value128 v = fast_float::full_multiplication( 36 | g_lehmer64_state.low, UINT64_C(0xda942042e4dd58b5)); 37 | v.high += g_lehmer64_state.high * UINT64_C(0xda942042e4dd58b5); 38 | g_lehmer64_state = v; 39 | return v.high; 40 | } 41 | 42 | size_t errors; 43 | 44 | void random_values(size_t N) { 45 | char buffer[128]; 46 | lehmer64_seed(N); 47 | for (size_t t = 0; t < N; t++) { 48 | if ((t % 1048576) == 0) { 49 | std::cout << "."; 50 | std::cout.flush(); 51 | } 52 | uint64_t word = lehmer64(); 53 | double v; 54 | memcpy(&v, &word, sizeof(v)); 55 | { 56 | char const *string_end = to_string(v, buffer); 57 | double result_value; 58 | auto result = fast_float::from_chars(buffer, string_end, result_value); 59 | // Starting with version 4.0 for fast_float, we return result_out_of_range 60 | // if the value is either too small (too close to zero) or too large 61 | // (effectively infinity). So std::errc::result_out_of_range is normal for 62 | // well-formed input strings. 63 | if (result.ec != std::errc() && 64 | result.ec != std::errc::result_out_of_range) { 65 | std::cerr << "parsing error ? " << buffer << std::endl; 66 | errors++; 67 | if (errors > 10) { 68 | abort(); 69 | } 70 | continue; 71 | } 72 | if (std::isnan(v)) { 73 | if (!std::isnan(result_value)) { 74 | std::cerr << "not nan" << buffer << std::endl; 75 | errors++; 76 | if (errors > 10) { 77 | abort(); 78 | } 79 | } 80 | } else if (copysign(1, result_value) != copysign(1, v)) { 81 | std::cerr << buffer << std::endl; 82 | std::cerr << "I got " << std::hexfloat << result_value 83 | << " but I was expecting " << v << std::endl; 84 | abort(); 85 | } else if (result_value != v) { 86 | std::cerr << "no match ? '" << buffer << "'" << std::endl; 87 | std::cout << "started with " << std::hexfloat << v << std::endl; 88 | std::cout << "got back " << std::hexfloat << result_value << std::endl; 89 | std::cout << std::dec; 90 | errors++; 91 | if (errors > 10) { 92 | abort(); 93 | } 94 | } 95 | } 96 | } 97 | std::cout << std::endl; 98 | } 99 | 100 | int main() { 101 | errors = 0; 102 | size_t N = 103 | size_t(1) << (sizeof(size_t) * 4); // shift: 32 for 64bit, 16 for 32bit 104 | random_values(N); 105 | if (errors == 0) { 106 | std::cout << std::endl; 107 | std::cout << "all ok" << std::endl; 108 | return EXIT_SUCCESS; 109 | } 110 | std::cerr << std::endl; 111 | std::cerr << "errors were encountered" << std::endl; 112 | return EXIT_FAILURE; 113 | } 114 | -------------------------------------------------------------------------------- /tests/long_test.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | inline void Assert(bool Assertion) { 11 | if (!Assertion) { 12 | throw std::runtime_error("bug"); 13 | } 14 | } 15 | 16 | template bool test() { 17 | std::string input = "0.156250000000000000000000000000000000000000 " 18 | "3.14159265358979323846264338327950288419716939937510 " 19 | "2.71828182845904523536028747135266249775724709369995"; 20 | std::vector answers = {T(0.15625), T(3.141592653589793), 21 | T(2.718281828459045)}; 22 | char const *begin = input.data(); 23 | char const *end = input.data() + input.size(); 24 | for (size_t i = 0; i < answers.size(); i++) { 25 | T result_value; 26 | while ((begin < end) && (std::isspace(*begin))) { 27 | begin++; 28 | } 29 | auto result = fast_float::from_chars(begin, end, result_value); 30 | if (result.ec != std::errc() && 31 | result.ec != std::errc::result_out_of_range) { 32 | printf("parsing %.*s\n", int(end - begin), begin); 33 | std::cerr << " I could not parse " << std::endl; 34 | return false; 35 | } 36 | if (result_value != answers[i]) { 37 | printf("parsing %.*s\n", int(end - begin), begin); 38 | std::cerr << " Mismatch " << std::endl; 39 | std::cerr << " Expected " << answers[i] << std::endl; 40 | std::cerr << " Got " << result_value << std::endl; 41 | 42 | return false; 43 | } 44 | begin = result.ptr; 45 | } 46 | if (begin != end) { 47 | std::cerr << " bad ending " << std::endl; 48 | return false; 49 | } 50 | return true; 51 | } 52 | 53 | int main() { 54 | 55 | std::cout << "32 bits checks" << std::endl; 56 | Assert(test()); 57 | 58 | std::cout << "64 bits checks" << std::endl; 59 | Assert(test()); 60 | 61 | std::cout << "All ok" << std::endl; 62 | return EXIT_SUCCESS; 63 | } 64 | -------------------------------------------------------------------------------- /tests/powersoffive_hardround.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 13 | defined(sun) || defined(__sun) 14 | // Anything at all that is related to cygwin, msys and so forth will 15 | // always use this fallback because we cannot rely on it behaving as normal 16 | // gcc. 17 | #include 18 | 19 | // workaround for CYGWIN 20 | double cygwin_strtod_l(char const *start, char **end) { 21 | double d; 22 | std::stringstream ss; 23 | ss.imbue(std::locale::classic()); 24 | ss << start; 25 | ss >> d; 26 | if (ss.fail()) { 27 | *end = nullptr; 28 | } 29 | if (ss.eof()) { 30 | ss.clear(); 31 | } 32 | auto nread = ss.tellg(); 33 | *end = const_cast(start) + nread; 34 | return d; 35 | } 36 | 37 | float cygwin_strtof_l(char const *start, char **end) { 38 | float d; 39 | std::stringstream ss; 40 | ss.imbue(std::locale::classic()); 41 | ss << start; 42 | ss >> d; 43 | if (ss.fail()) { 44 | *end = nullptr; 45 | } 46 | if (ss.eof()) { 47 | ss.clear(); 48 | } 49 | auto nread = ss.tellg(); 50 | *end = const_cast(start) + nread; 51 | return d; 52 | } 53 | #endif 54 | 55 | std::pair strtod_from_string(char const *st) { 56 | double d; 57 | char *pr; 58 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 59 | defined(sun) || defined(__sun) 60 | d = cygwin_strtod_l(st, &pr); 61 | #elif defined(_WIN32) 62 | static _locale_t c_locale = _create_locale(LC_ALL, "C"); 63 | d = _strtod_l(st, &pr, c_locale); 64 | #else 65 | static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL); 66 | d = strtod_l(st, &pr, c_locale); 67 | #endif 68 | if (st == pr) { 69 | std::cerr << "strtod_l could not parse '" << st << std::endl; 70 | return std::make_pair(0, false); 71 | } 72 | return std::make_pair(d, true); 73 | } 74 | 75 | std::pair strtof_from_string(char *st) { 76 | float d; 77 | char *pr; 78 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 79 | defined(sun) || defined(__sun) 80 | d = cygwin_strtof_l(st, &pr); 81 | #elif defined(_WIN32) 82 | static _locale_t c_locale = _create_locale(LC_ALL, "C"); 83 | d = _strtof_l(st, &pr, c_locale); 84 | #else 85 | static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL); 86 | d = strtof_l(st, &pr, c_locale); 87 | #endif 88 | if (st == pr) { 89 | std::cerr << "strtof_l could not parse '" << st << std::endl; 90 | return std::make_pair(0.0f, false); 91 | } 92 | return std::make_pair(d, true); 93 | } 94 | 95 | bool tester() { 96 | std::random_device rd; 97 | std::mt19937 gen(rd()); 98 | for (int q = 18; q <= 27; q++) { 99 | std::cout << "q = " << -q << std::endl; 100 | uint64_t power5 = 1; 101 | for (int k = 0; k < q; k++) { 102 | power5 *= 5; 103 | } 104 | uint64_t low_threshold = 0x20000000000000 / power5 + 1; 105 | uint64_t threshold = 0xFFFFFFFFFFFFFFFF / power5; 106 | std::uniform_int_distribution dis(low_threshold, threshold); 107 | for (size_t i = 0; i < 10000; i++) { 108 | uint64_t mantissa = dis(gen) * power5; 109 | std::stringstream ss; 110 | ss << mantissa; 111 | ss << "e"; 112 | ss << -q; 113 | std::string to_be_parsed = ss.str(); 114 | std::pair expected_double = 115 | strtod_from_string(to_be_parsed.c_str()); 116 | double result_value; 117 | auto result = fast_float::from_chars( 118 | to_be_parsed.data(), to_be_parsed.data() + to_be_parsed.size(), 119 | result_value); 120 | if (result.ec != std::errc() && 121 | result.ec != std::errc::result_out_of_range) { 122 | std::cout << to_be_parsed << std::endl; 123 | std::cerr << " I could not parse " << std::endl; 124 | return false; 125 | } 126 | if (result_value != expected_double.first) { 127 | std::cout << to_be_parsed << std::endl; 128 | std::cerr << std::hexfloat << result_value << std::endl; 129 | std::cerr << std::hexfloat << expected_double.first << std::endl; 130 | std::cerr << " Mismatch " << std::endl; 131 | return false; 132 | } 133 | } 134 | } 135 | return true; 136 | } 137 | 138 | int main() { 139 | if (tester()) { 140 | std::cout << std::endl; 141 | std::cout << "all ok" << std::endl; 142 | return EXIT_SUCCESS; 143 | } 144 | std::cerr << std::endl; 145 | std::cerr << "errors were encountered" << std::endl; 146 | return EXIT_FAILURE; 147 | } 148 | -------------------------------------------------------------------------------- /tests/random64.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | template char *to_string(T d, char *buffer) { 12 | auto written = std::snprintf(buffer, 64, "%.*e", 13 | std::numeric_limits::max_digits10 - 1, d); 14 | return buffer + written; 15 | } 16 | 17 | static fast_float::value128 g_lehmer64_state; 18 | 19 | /** 20 | * D. H. Lehmer, Mathematical methods in large-scale computing units. 21 | * Proceedings of a Second Symposium on Large Scale Digital Calculating 22 | * Machinery; 23 | * Annals of the Computation Laboratory, Harvard Univ. 26 (1951), pp. 141-146. 24 | * 25 | * P L'Ecuyer, Tables of linear congruential generators of different sizes and 26 | * good lattice structure. Mathematics of Computation of the American 27 | * Mathematical 28 | * Society 68.225 (1999): 249-260. 29 | */ 30 | 31 | static inline void lehmer64_seed(uint64_t seed) { 32 | g_lehmer64_state.high = 0; 33 | g_lehmer64_state.low = seed; 34 | } 35 | 36 | static inline uint64_t lehmer64() { 37 | fast_float::value128 v = fast_float::full_multiplication( 38 | g_lehmer64_state.low, UINT64_C(0xda942042e4dd58b5)); 39 | v.high += g_lehmer64_state.high * UINT64_C(0xda942042e4dd58b5); 40 | g_lehmer64_state = v; 41 | return v.high; 42 | } 43 | 44 | size_t errors; 45 | 46 | void random_values(size_t N) { 47 | char buffer[64]; 48 | lehmer64_seed(N); 49 | for (size_t t = 0; t < N; t++) { 50 | if ((t % 1048576) == 0) { 51 | std::cout << "."; 52 | std::cout.flush(); 53 | } 54 | uint64_t word = lehmer64(); 55 | double v; 56 | memcpy(&v, &word, sizeof(v)); 57 | // if (!std::isnormal(v)) 58 | { 59 | char const *string_end = to_string(v, buffer); 60 | double result_value; 61 | auto result = fast_float::from_chars(buffer, string_end, result_value); 62 | // Starting with version 4.0 for fast_float, we return result_out_of_range 63 | // if the value is either too small (too close to zero) or too large 64 | // (effectively infinity). So std::errc::result_out_of_range is normal for 65 | // well-formed input strings. 66 | if (result.ec != std::errc() && 67 | result.ec != std::errc::result_out_of_range) { 68 | std::cerr << "parsing error ? " << buffer << std::endl; 69 | errors++; 70 | if (errors > 10) { 71 | abort(); 72 | } 73 | continue; 74 | } 75 | if (std::isnan(v)) { 76 | if (!std::isnan(result_value)) { 77 | std::cerr << "not nan" << buffer << std::endl; 78 | errors++; 79 | if (errors > 10) { 80 | abort(); 81 | } 82 | } 83 | } else if (copysign(1, result_value) != copysign(1, v)) { 84 | std::cerr << buffer << std::endl; 85 | std::cerr << "I got " << std::hexfloat << result_value 86 | << " but I was expecting " << v << std::endl; 87 | abort(); 88 | } else if (result_value != v) { 89 | std::cerr << "no match ? " << buffer << std::endl; 90 | std::cout << "started with " << std::hexfloat << v << std::endl; 91 | std::cout << "got back " << std::hexfloat << result_value << std::endl; 92 | std::cout << std::dec; 93 | errors++; 94 | if (errors > 10) { 95 | abort(); 96 | } 97 | } 98 | } 99 | } 100 | std::cout << std::endl; 101 | } 102 | 103 | int main() { 104 | errors = 0; 105 | size_t N = 106 | size_t(1) << (sizeof(size_t) * 4); // shift: 32 for 64bit, 16 for 32bit 107 | random_values(N); 108 | if (errors == 0) { 109 | std::cout << std::endl; 110 | std::cout << "all ok" << std::endl; 111 | return EXIT_SUCCESS; 112 | } 113 | std::cerr << std::endl; 114 | std::cerr << "errors were encountered" << std::endl; 115 | return EXIT_FAILURE; 116 | } 117 | -------------------------------------------------------------------------------- /tests/random_string.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 11 | defined(sun) || defined(__sun) 12 | // Anything at all that is related to cygwin, msys and so forth will 13 | // always use this fallback because we cannot rely on it behaving as normal 14 | // gcc. 15 | #include 16 | #include 17 | 18 | // workaround for CYGWIN 19 | double cygwin_strtod_l(char const *start, char **end) { 20 | double d; 21 | std::stringstream ss; 22 | ss.imbue(std::locale::classic()); 23 | ss << start; 24 | ss >> d; 25 | if (ss.fail()) { 26 | *end = nullptr; 27 | } 28 | if (ss.eof()) { 29 | ss.clear(); 30 | } 31 | auto nread = ss.tellg(); 32 | *end = const_cast(start) + nread; 33 | return d; 34 | } 35 | 36 | float cygwin_strtof_l(char const *start, char **end) { 37 | float d; 38 | std::stringstream ss; 39 | ss.imbue(std::locale::classic()); 40 | ss << start; 41 | ss >> d; 42 | if (ss.fail()) { 43 | *end = nullptr; 44 | } 45 | if (ss.eof()) { 46 | ss.clear(); 47 | } 48 | auto nread = ss.tellg(); 49 | *end = const_cast(start) + nread; 50 | return d; 51 | } 52 | #endif 53 | 54 | class RandomEngine { 55 | public: 56 | RandomEngine() = delete; 57 | RandomEngine(uint64_t new_seed) : wyhash64_x_(new_seed){}; 58 | 59 | uint64_t next() { 60 | // Adapted from https://github.com/wangyi-fudan/wyhash/blob/master/wyhash.h 61 | // Inspired from 62 | // https://github.com/lemire/testingRNG/blob/master/source/wyhash.h 63 | wyhash64_x_ += UINT64_C(0x60bee2bee120fc15); 64 | fast_float::value128 tmp = fast_float::full_multiplication( 65 | wyhash64_x_, UINT64_C(0xa3b195354a39b70d)); 66 | uint64_t m1 = (tmp.high) ^ tmp.low; 67 | tmp = fast_float::full_multiplication(m1, UINT64_C(0x1b03738712fad5c9)); 68 | uint64_t m2 = (tmp.high) ^ tmp.low; 69 | return m2; 70 | } 71 | 72 | bool next_bool() { return (next() & 1) == 1; } 73 | 74 | int next_int() { return static_cast(next()); } 75 | 76 | char next_char() { return static_cast(next()); } 77 | 78 | double next_double() { return static_cast(next()); } 79 | 80 | int next_ranged_int(int min, int max) { // min and max are included 81 | // Adapted from 82 | // https://lemire.me/blog/2019/06/06/nearly-divisionless-random-integer-generation-on-various-systems/ 83 | /* if (min == max) { 84 | return min; 85 | }*/ 86 | uint64_t s = uint64_t(max - min + 1); 87 | uint64_t x = next(); 88 | fast_float::value128 m = fast_float::full_multiplication(x, s); 89 | uint64_t l = m.low; 90 | if (l < s) { 91 | uint64_t t = -s % s; 92 | while (l < t) { 93 | x = next(); 94 | m = fast_float::full_multiplication(x, s); 95 | l = m.low; 96 | } 97 | } 98 | return int(m.high) + min; 99 | } 100 | 101 | int next_digit() { return next_ranged_int(0, 9); } 102 | 103 | private: 104 | uint64_t wyhash64_x_; 105 | }; 106 | 107 | size_t build_random_string(RandomEngine &rand, char *buffer) { 108 | size_t pos{0}; 109 | if (rand.next_bool()) { 110 | buffer[pos++] = '-'; 111 | } 112 | int number_of_digits = rand.next_ranged_int(1, 100); 113 | if (number_of_digits == 100) { 114 | // With low probability, we want to allow very long strings just to stress 115 | // the system. 116 | number_of_digits = rand.next_ranged_int(1, 2000); 117 | } 118 | int location_of_decimal_separator = rand.next_ranged_int(1, number_of_digits); 119 | for (size_t i = 0; i < size_t(number_of_digits); i++) { 120 | if (i == size_t(location_of_decimal_separator)) { 121 | buffer[pos++] = '.'; 122 | } 123 | buffer[pos] = char(rand.next_digit() + '0'); 124 | // We can have a leading zero only if location_of_decimal_separator = 1. 125 | while (i == 0 && 1 != size_t(location_of_decimal_separator) && 126 | buffer[pos] == '0') { 127 | buffer[pos] = char(rand.next_digit() + '0'); 128 | } 129 | pos++; 130 | } 131 | if (rand.next_bool()) { 132 | if (rand.next_bool()) { 133 | buffer[pos++] = 'e'; 134 | } else { 135 | buffer[pos++] = 'E'; 136 | } 137 | if (rand.next_bool()) { 138 | buffer[pos++] = '-'; 139 | } else { 140 | if (rand.next_bool()) { 141 | buffer[pos++] = '+'; 142 | } 143 | } 144 | number_of_digits = rand.next_ranged_int(1, 3); 145 | for (size_t i = 0; i < size_t(number_of_digits); i++) { 146 | buffer[pos++] = char(rand.next_digit() + '0'); 147 | } 148 | } 149 | buffer[pos] = '\0'; // null termination 150 | return pos; 151 | } 152 | 153 | std::pair strtod_from_string(char *st) { 154 | double d; 155 | char *pr; 156 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 157 | defined(sun) || defined(__sun) 158 | d = cygwin_strtod_l(st, &pr); 159 | #elif defined(_WIN32) 160 | static _locale_t c_locale = _create_locale(LC_ALL, "C"); 161 | d = _strtod_l(st, &pr, c_locale); 162 | #else 163 | static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL); 164 | d = strtod_l(st, &pr, c_locale); 165 | #endif 166 | if (st == pr) { 167 | std::cerr << "strtod_l could not parse '" << st << std::endl; 168 | return std::make_pair(0, false); 169 | } 170 | return std::make_pair(d, true); 171 | } 172 | 173 | std::pair strtof_from_string(char *st) { 174 | float d; 175 | char *pr; 176 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 177 | defined(sun) || defined(__sun) 178 | d = cygwin_strtof_l(st, &pr); 179 | #elif defined(_WIN32) 180 | static _locale_t c_locale = _create_locale(LC_ALL, "C"); 181 | d = _strtof_l(st, &pr, c_locale); 182 | #else 183 | static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL); 184 | d = strtof_l(st, &pr, c_locale); 185 | #endif 186 | if (st == pr) { 187 | std::cerr << "strtof_l could not parse '" << st << std::endl; 188 | return std::make_pair(0.0f, false); 189 | } 190 | return std::make_pair(d, true); 191 | } 192 | 193 | /** 194 | * We generate random strings and we try to parse them with both strtod/strtof, 195 | * and we verify that we get the same answer with with fast_float::from_chars. 196 | */ 197 | bool tester(uint64_t seed, size_t volume) { 198 | char buffer[4096]; // large buffer (can't overflow) 199 | RandomEngine rand(seed); 200 | for (size_t i = 0; i < volume; i++) { 201 | if ((i % 100000) == 0) { 202 | std::cout << "."; 203 | std::cout.flush(); 204 | } 205 | size_t length = build_random_string(rand, buffer); 206 | std::pair expected_double = strtod_from_string(buffer); 207 | if (expected_double.second) { 208 | double result_value; 209 | auto result = 210 | fast_float::from_chars(buffer, buffer + length, result_value); 211 | if (result.ec != std::errc() && 212 | result.ec != std::errc::result_out_of_range) { 213 | printf("parsing %.*s\n", int(length), buffer); 214 | std::cerr << " I could not parse " << std::endl; 215 | return false; 216 | } 217 | if (result.ptr != buffer + length) { 218 | printf("parsing %.*s\n", int(length), buffer); 219 | std::cerr << " Did not get to the end " << std::endl; 220 | return false; 221 | } 222 | if (result_value != expected_double.first) { 223 | printf("parsing %.*s\n", int(length), buffer); 224 | std::cerr << std::hexfloat << result_value << std::endl; 225 | std::cerr << std::hexfloat << expected_double.first << std::endl; 226 | std::cerr << " Mismatch " << std::endl; 227 | return false; 228 | } 229 | } 230 | std::pair expected_float = strtof_from_string(buffer); 231 | if (expected_float.second) { 232 | float result_value; 233 | auto result = 234 | fast_float::from_chars(buffer, buffer + length, result_value); 235 | if (result.ec != std::errc() && 236 | result.ec != std::errc::result_out_of_range) { 237 | printf("parsing %.*s\n", int(length), buffer); 238 | std::cerr << " I could not parse " << std::endl; 239 | return false; 240 | } 241 | if (result.ptr != buffer + length) { 242 | printf("parsing %.*s\n", int(length), buffer); 243 | std::cerr << " Did not get to the end " << std::endl; 244 | return false; 245 | } 246 | if (result_value != expected_float.first) { 247 | printf("parsing %.*s\n", int(length), buffer); 248 | std::cerr << std::hexfloat << result_value << std::endl; 249 | std::cerr << std::hexfloat << expected_float.first << std::endl; 250 | std::cerr << " Mismatch " << std::endl; 251 | return false; 252 | } 253 | } 254 | } 255 | return true; 256 | } 257 | 258 | int main() { 259 | 260 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 261 | defined(sun) || defined(__sun) 262 | std::cout << "Warning: msys/cygwin or solaris detected." << std::endl; 263 | return EXIT_SUCCESS; 264 | #else 265 | if (tester(1234344, 100000000)) { 266 | std::cout << "All tests ok." << std::endl; 267 | return EXIT_SUCCESS; 268 | } 269 | std::cout << "Failure." << std::endl; 270 | return EXIT_FAILURE; 271 | 272 | #endif 273 | } 274 | -------------------------------------------------------------------------------- /tests/rcppfastfloat_test.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * See https://github.com/eddelbuettel/rcppfastfloat/issues/4 3 | */ 4 | 5 | #include "fast_float/fast_float.h" 6 | #include 7 | #include 8 | #include 9 | 10 | struct test_data { 11 | std::string input; 12 | bool expected_success; 13 | double expected_result; 14 | }; 15 | 16 | bool eddelbuettel() { 17 | std::vector const test_datas = { 18 | {"infinity", true, std::numeric_limits::infinity()}, 19 | {" \r\n\t\f\v3.16227766016838 \r\n\t\f\v", true, 3.16227766016838}, 20 | {" \r\n\t\f\v3 \r\n\t\f\v", true, 3.0}, 21 | {" 1970-01-01", false, 0.0}, 22 | {"-NaN", true, std::numeric_limits::quiet_NaN()}, 23 | {"-inf", true, -std::numeric_limits::infinity()}, 24 | {" \r\n\t\f\v2.82842712474619 \r\n\t\f\v", true, 2.82842712474619}, 25 | {"nan", true, std::numeric_limits::quiet_NaN()}, 26 | {" \r\n\t\f\v2.44948974278318 \r\n\t\f\v", true, 2.44948974278318}, 27 | {"Inf", true, std::numeric_limits::infinity()}, 28 | {" \r\n\t\f\v2 \r\n\t\f\v", true, 2.0}, 29 | {"-infinity", true, -std::numeric_limits::infinity()}, 30 | {" \r\n\t\f\v0 \r\n\t\f\v", true, 0.0}, 31 | {" \r\n\t\f\v1.73205080756888 \r\n\t\f\v", true, 1.73205080756888}, 32 | {" \r\n\t\f\v1 \r\n\t\f\v", true, 1.0}, 33 | {" \r\n\t\f\v1.4142135623731 \r\n\t\f\v", true, 1.4142135623731}, 34 | {" \r\n\t\f\v2.23606797749979 \r\n\t\f\v", true, 2.23606797749979}, 35 | {"1970-01-02 ", false, 0.0}, 36 | {" \r\n\t\f\v2.64575131106459 \r\n\t\f\v", true, 2.64575131106459}, 37 | {"inf", true, std::numeric_limits::infinity()}, 38 | {"-nan", true, std::numeric_limits::quiet_NaN()}, 39 | {"NaN", true, std::numeric_limits::quiet_NaN()}, 40 | {"", false, 0.0}, 41 | {"-Inf", true, -std::numeric_limits::infinity()}, 42 | {"+2.2", true, 2.2}, 43 | {"1d+4", false, 0.0}, 44 | {"1d-1", false, 0.0}, 45 | {"0.", true, 0.0}, 46 | {"-.1", true, -0.1}, 47 | {"+.1", true, 0.1}, 48 | {"1e+1", true, 10.0}, 49 | {"+1e1", true, 10.0}, 50 | {"-+0", false, 0.0}, 51 | {"-+inf", false, 0.0}, 52 | {"-+nan", false, 0.0}, 53 | }; 54 | for (size_t i = 0; i < test_datas.size(); i++) { 55 | auto const &input = test_datas[i].input; 56 | auto const expected_success = test_datas[i].expected_success; 57 | auto const expected_result = test_datas[i].expected_result; 58 | double result; 59 | // answer contains a error code and a pointer to the end of the 60 | // parsed region (on success). 61 | auto const answer = fast_float::from_chars( 62 | input.data(), input.data() + input.size(), result, 63 | fast_float::chars_format::general | 64 | fast_float::chars_format::allow_leading_plus | 65 | fast_float::chars_format::skip_white_space); 66 | if (answer.ec != std::errc()) { 67 | std::cout << "could not parse" << std::endl; 68 | if (expected_success) { 69 | return false; 70 | } 71 | continue; 72 | } 73 | bool non_space_trailing_content = false; 74 | if (answer.ptr != input.data() + input.size()) { 75 | // check that there is no content left 76 | for (char const *leftover = answer.ptr; 77 | leftover != input.data() + input.size(); leftover++) { 78 | if (!fast_float::is_space(*leftover)) { 79 | non_space_trailing_content = true; 80 | break; 81 | } 82 | } 83 | } 84 | if (non_space_trailing_content) { 85 | std::cout << "found trailing content " << std::endl; 86 | if (!expected_success) { 87 | continue; 88 | } else { 89 | return false; 90 | } 91 | } 92 | std::cout << "parsed " << result << std::endl; 93 | if (!expected_success) { 94 | return false; 95 | } 96 | if (result != expected_result && 97 | !(std::isnan(result) && std::isnan(expected_result))) { 98 | std::cout << "results do not match. Expected " << expected_result 99 | << std::endl; 100 | return false; 101 | } 102 | } 103 | return true; 104 | } 105 | 106 | int main() { 107 | if (!eddelbuettel()) { 108 | printf("Bug.\n"); 109 | return EXIT_FAILURE; 110 | } 111 | printf("All ok.\n"); 112 | return EXIT_SUCCESS; 113 | } 114 | -------------------------------------------------------------------------------- /tests/short_random_string.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 11 | defined(sun) || defined(__sun) 12 | // Anything at all that is related to cygwin, msys and so forth will 13 | // always use this fallback because we cannot rely on it behaving as normal 14 | // gcc. 15 | #include 16 | #include 17 | 18 | // workaround for CYGWIN 19 | double cygwin_strtod_l(char const *start, char **end) { 20 | double d; 21 | std::stringstream ss; 22 | ss.imbue(std::locale::classic()); 23 | ss << start; 24 | ss >> d; 25 | if (ss.fail()) { 26 | *end = nullptr; 27 | } 28 | if (ss.eof()) { 29 | ss.clear(); 30 | } 31 | auto nread = ss.tellg(); 32 | *end = const_cast(start) + nread; 33 | return d; 34 | } 35 | 36 | float cygwin_strtof_l(char const *start, char **end) { 37 | float d; 38 | std::stringstream ss; 39 | ss.imbue(std::locale::classic()); 40 | ss << start; 41 | ss >> d; 42 | if (ss.fail()) { 43 | *end = nullptr; 44 | } 45 | if (ss.eof()) { 46 | ss.clear(); 47 | } 48 | auto nread = ss.tellg(); 49 | *end = const_cast(start) + nread; 50 | return d; 51 | } 52 | #endif 53 | 54 | class RandomEngine { 55 | public: 56 | RandomEngine() = delete; 57 | RandomEngine(uint64_t new_seed) : wyhash64_x_(new_seed){}; 58 | 59 | uint64_t next() { 60 | // Adapted from https://github.com/wangyi-fudan/wyhash/blob/master/wyhash.h 61 | // Inspired from 62 | // https://github.com/lemire/testingRNG/blob/master/source/wyhash.h 63 | wyhash64_x_ += UINT64_C(0x60bee2bee120fc15); 64 | fast_float::value128 tmp = fast_float::full_multiplication( 65 | wyhash64_x_, UINT64_C(0xa3b195354a39b70d)); 66 | uint64_t m1 = (tmp.high) ^ tmp.low; 67 | tmp = fast_float::full_multiplication(m1, UINT64_C(0x1b03738712fad5c9)); 68 | uint64_t m2 = (tmp.high) ^ tmp.low; 69 | return m2; 70 | } 71 | 72 | bool next_bool() { return (next() & 1) == 1; } 73 | 74 | int next_int() { return static_cast(next()); } 75 | 76 | char next_char() { return static_cast(next()); } 77 | 78 | double next_double() { return static_cast(next()); } 79 | 80 | int next_ranged_int(int min, int max) { // min and max are included 81 | // Adapted from 82 | // https://lemire.me/blog/2019/06/06/nearly-divisionless-random-integer-generation-on-various-systems/ 83 | /* if (min == max) { 84 | return min; 85 | }*/ 86 | uint64_t s = uint64_t(max - min + 1); 87 | uint64_t x = next(); 88 | fast_float::value128 m = fast_float::full_multiplication(x, s); 89 | uint64_t l = m.low; 90 | if (l < s) { 91 | uint64_t t = -s % s; 92 | while (l < t) { 93 | x = next(); 94 | m = fast_float::full_multiplication(x, s); 95 | l = m.low; 96 | } 97 | } 98 | return int(m.high) + min; 99 | } 100 | 101 | int next_digit() { return next_ranged_int(0, 9); } 102 | 103 | private: 104 | uint64_t wyhash64_x_; 105 | }; 106 | 107 | size_t build_random_string(RandomEngine &rand, char *buffer) { 108 | size_t pos{0}; 109 | if (rand.next_bool()) { 110 | buffer[pos++] = '-'; 111 | } 112 | int number_of_digits = rand.next_ranged_int(1, 19); 113 | int location_of_decimal_separator = rand.next_ranged_int(1, number_of_digits); 114 | for (size_t i = 0; i < size_t(number_of_digits); i++) { 115 | if (i == size_t(location_of_decimal_separator)) { 116 | buffer[pos++] = '.'; 117 | } 118 | buffer[pos] = char(rand.next_digit() + '0'); 119 | // We can have a leading zero only if location_of_decimal_separator = 1. 120 | while (i == 0 && 1 != size_t(location_of_decimal_separator) && 121 | buffer[pos] == '0') { 122 | buffer[pos] = char(rand.next_digit() + '0'); 123 | } 124 | pos++; 125 | } 126 | if (rand.next_bool()) { 127 | if (rand.next_bool()) { 128 | buffer[pos++] = 'e'; 129 | } else { 130 | buffer[pos++] = 'E'; 131 | } 132 | if (rand.next_bool()) { 133 | buffer[pos++] = '-'; 134 | } else { 135 | if (rand.next_bool()) { 136 | buffer[pos++] = '+'; 137 | } 138 | } 139 | number_of_digits = rand.next_ranged_int(1, 3); 140 | for (size_t i = 0; i < size_t(number_of_digits); i++) { 141 | buffer[pos++] = char(rand.next_digit() + '0'); 142 | } 143 | } 144 | buffer[pos] = '\0'; // null termination 145 | return pos; 146 | } 147 | 148 | std::pair strtod_from_string(char *st) { 149 | double d; 150 | char *pr; 151 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 152 | defined(sun) || defined(__sun) 153 | d = cygwin_strtod_l(st, &pr); 154 | #elif defined(_WIN32) 155 | static _locale_t c_locale = _create_locale(LC_ALL, "C"); 156 | d = _strtod_l(st, &pr, c_locale); 157 | #else 158 | static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL); 159 | d = strtod_l(st, &pr, c_locale); 160 | #endif 161 | if (st == pr) { 162 | std::cerr << "strtod_l could not parse '" << st << std::endl; 163 | return std::make_pair(0, false); 164 | } 165 | return std::make_pair(d, true); 166 | } 167 | 168 | std::pair strtof_from_string(char *st) { 169 | float d; 170 | char *pr; 171 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 172 | defined(sun) || defined(__sun) 173 | d = cygwin_strtof_l(st, &pr); 174 | #elif defined(_WIN32) 175 | static _locale_t c_locale = _create_locale(LC_ALL, "C"); 176 | d = _strtof_l(st, &pr, c_locale); 177 | #else 178 | static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL); 179 | d = strtof_l(st, &pr, c_locale); 180 | #endif 181 | if (st == pr) { 182 | std::cerr << "strtof_l could not parse '" << st << std::endl; 183 | return std::make_pair(0.0f, false); 184 | } 185 | return std::make_pair(d, true); 186 | } 187 | 188 | /** 189 | * We generate random strings and we try to parse them with both strtod/strtof, 190 | * and we verify that we get the same answer with with fast_float::from_chars. 191 | */ 192 | bool tester(uint64_t seed, size_t volume) { 193 | char buffer[4096]; // large buffer (can't overflow) 194 | RandomEngine rand(seed); 195 | for (size_t i = 0; i < volume; i++) { 196 | if ((i % 1000000) == 0) { 197 | std::cout << "."; 198 | std::cout.flush(); 199 | } 200 | size_t length = build_random_string(rand, buffer); 201 | std::pair expected_double = strtod_from_string(buffer); 202 | if (expected_double.second) { 203 | double result_value; 204 | auto result = 205 | fast_float::from_chars(buffer, buffer + length, result_value); 206 | if (result.ec != std::errc() && 207 | result.ec != std::errc::result_out_of_range) { 208 | printf("parsing %.*s\n", int(length), buffer); 209 | std::cerr << " I could not parse " << std::endl; 210 | return false; 211 | } 212 | if (result.ptr != buffer + length) { 213 | printf("parsing %.*s\n", int(length), buffer); 214 | std::cerr << " Did not get to the end " << std::endl; 215 | return false; 216 | } 217 | if (result_value != expected_double.first) { 218 | printf("parsing %.*s\n", int(length), buffer); 219 | std::cerr << std::hexfloat << result_value << std::endl; 220 | std::cerr << std::hexfloat << expected_double.first << std::endl; 221 | std::cerr << " Mismatch " << std::endl; 222 | return false; 223 | } 224 | } 225 | std::pair expected_float = strtof_from_string(buffer); 226 | if (expected_float.second) { 227 | float result_value; 228 | auto result = 229 | fast_float::from_chars(buffer, buffer + length, result_value); 230 | if (result.ec != std::errc() && 231 | result.ec != std::errc::result_out_of_range) { 232 | printf("parsing %.*s\n", int(length), buffer); 233 | std::cerr << " I could not parse " << std::endl; 234 | return false; 235 | } 236 | if (result.ptr != buffer + length) { 237 | printf("parsing %.*s\n", int(length), buffer); 238 | std::cerr << " Did not get to the end " << std::endl; 239 | return false; 240 | } 241 | if (result_value != expected_float.first) { 242 | printf("parsing %.*s\n", int(length), buffer); 243 | std::cerr << std::hexfloat << result_value << std::endl; 244 | std::cerr << std::hexfloat << expected_float.first << std::endl; 245 | std::cerr << " Mismatch " << std::endl; 246 | return false; 247 | } 248 | } 249 | } 250 | return true; 251 | } 252 | 253 | int main() { 254 | #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \ 255 | defined(sun) || defined(__sun) 256 | std::cout << "Warning: msys/cygwin detected. This particular test is likely " 257 | "to generate false failures due to our reliance on the " 258 | "underlying runtime library." 259 | << std::endl; 260 | return EXIT_SUCCESS; 261 | #else 262 | if (tester(1234344, 100000000)) { 263 | std::cout << "All tests ok." << std::endl; 264 | return EXIT_SUCCESS; 265 | } 266 | return EXIT_FAILURE; 267 | 268 | #endif 269 | } 270 | -------------------------------------------------------------------------------- /tests/supported_chars_test.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | #include 3 | #include 4 | #include 5 | 6 | template bool test(std::string s, double expected) { 7 | std::basic_string input(s.begin(), s.end()); 8 | double result; 9 | auto answer = 10 | fast_float::from_chars(input.data(), input.data() + input.size(), result); 11 | if (answer.ec != std::errc()) { 12 | std::cerr << "parsing of \"" << s << "\" should succeed\n"; 13 | return false; 14 | } 15 | if (result != expected && !(std::isnan(result) && std::isnan(expected))) { 16 | std::cerr << "parsing of \"" << s << "\" succeeded, expected " << expected 17 | << " got " << result << "\n"; 18 | return false; 19 | } 20 | return true; 21 | } 22 | 23 | int main() { 24 | if (!test("4.2", 4.2)) { 25 | std::cout << "test failure for char" << std::endl; 26 | return EXIT_FAILURE; 27 | } 28 | 29 | if (!test("4.2", 4.2)) { 30 | std::cout << "test failure for wchar_t" << std::endl; 31 | return EXIT_FAILURE; 32 | } 33 | 34 | #ifdef __cpp_char8_t 35 | if (!test("4.2", 4.2)) { 36 | std::cout << "test failure for char8_t" << std::endl; 37 | return EXIT_FAILURE; 38 | } 39 | #endif 40 | 41 | if (!test("4.2", 4.2)) { 42 | std::cout << "test failure for char16_t" << std::endl; 43 | return EXIT_FAILURE; 44 | } 45 | 46 | if (!test("4.2", 4.2)) { 47 | std::cout << "test failure for char32_t" << std::endl; 48 | return EXIT_FAILURE; 49 | } 50 | 51 | std::cout << "all ok" << std::endl; 52 | return EXIT_SUCCESS; 53 | } 54 | -------------------------------------------------------------------------------- /tests/wide_char_test.cpp: -------------------------------------------------------------------------------- 1 | #include "fast_float/fast_float.h" 2 | #include 3 | #include 4 | #include 5 | 6 | bool tester(std::string s, double expected, 7 | fast_float::chars_format fmt = fast_float::chars_format::general) { 8 | std::wstring input(s.begin(), s.end()); 9 | double result; 10 | auto answer = fast_float::from_chars( 11 | input.data(), input.data() + input.size(), result, fmt); 12 | if (answer.ec != std::errc()) { 13 | std::cerr << "parsing of \"" << s << "\" should succeed\n"; 14 | return false; 15 | } 16 | if (result != expected && !(std::isnan(result) && std::isnan(expected))) { 17 | std::cerr << "parsing of \"" << s << "\" succeeded, expected " << expected 18 | << " got " << result << "\n"; 19 | return false; 20 | } 21 | input[0] += 256; 22 | answer = fast_float::from_chars(input.data(), input.data() + input.size(), 23 | result, fmt); 24 | if (answer.ec == std::errc()) { 25 | std::cerr << "parsing of altered \"" << s << "\" should fail\n"; 26 | return false; 27 | } 28 | return true; 29 | } 30 | 31 | bool test_minus() { return tester("-42", -42); } 32 | 33 | bool test_plus() { 34 | return tester("+42", 42, 35 | fast_float::chars_format::general | 36 | fast_float::chars_format::allow_leading_plus); 37 | } 38 | 39 | bool test_space() { 40 | return tester(" 42", 42, 41 | fast_float::chars_format::general | 42 | fast_float::chars_format::skip_white_space); 43 | } 44 | 45 | bool test_nan() { 46 | return tester("nan", std::numeric_limits::quiet_NaN()); 47 | } 48 | 49 | int main() { 50 | if (test_minus() && test_plus() && test_space() && test_nan()) { 51 | std::cout << "all ok" << std::endl; 52 | return EXIT_SUCCESS; 53 | } 54 | 55 | std::cout << "test failure" << std::endl; 56 | return EXIT_FAILURE; 57 | } 58 | --------------------------------------------------------------------------------