mirror of
https://github.com/fastfloat/fast_float.git
synced 2025-12-24 12:34:53 +08:00
Compare commits
294 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5830594b99 | ||
|
|
75d01f02e2 | ||
|
|
bfa7bccea1 | ||
|
|
b5ae54cb21 | ||
|
|
55723db171 | ||
|
|
62ed60e95f | ||
|
|
62fb615be9 | ||
|
|
c54e4a7aba | ||
|
|
5304b3d611 | ||
|
|
a6685b2a98 | ||
|
|
6952ffeec0 | ||
|
|
ca1057e303 | ||
|
|
0fa058eebb | ||
|
|
157deaeba5 | ||
|
|
b2535ce14c | ||
|
|
1cc6cf5a09 | ||
|
|
6b72e26ba7 | ||
|
|
f8c573d741 | ||
|
|
9d78a01ff7 | ||
|
|
409d6215b4 | ||
|
|
7b21183a93 | ||
|
|
8ac72791a9 | ||
|
|
e77e2bca7e | ||
|
|
24fa687d45 | ||
|
|
88f6c5e367 | ||
|
|
83a4f696d7 | ||
|
|
1ea4d2563e | ||
|
|
7262d9454e | ||
|
|
23f16adad4 | ||
|
|
fd98fd6689 | ||
|
|
197c0ffca7 | ||
|
|
e9438e64ba | ||
|
|
7abb574ffc | ||
|
|
04e8e545d8 | ||
|
|
e4b949e55b | ||
|
|
01e505797b | ||
|
|
13345cab65 | ||
|
|
88b1e5321c | ||
|
|
2aa6d0ba72 | ||
|
|
0b6d911220 | ||
|
|
2d6574483d | ||
|
|
7a77227521 | ||
|
|
e20c952456 | ||
|
|
bb956b29db | ||
|
|
48fc5404d4 | ||
|
|
e5612e9f72 | ||
|
|
fb384a4205 | ||
|
|
9d81c71aef | ||
|
|
fec4082f01 | ||
|
|
e89e248bd7 | ||
|
|
96fc38fb5a | ||
|
|
6677924083 | ||
|
|
0a230326ab | ||
|
|
7ae62ee0d5 | ||
|
|
4bb6fd1271 | ||
|
|
e12463583f | ||
|
|
6702cd4244 | ||
|
|
20a7383442 | ||
|
|
763558b9ac | ||
|
|
ce511cb9ee | ||
|
|
42db9ac1de | ||
|
|
6be07d66a8 | ||
|
|
42836b48eb | ||
|
|
a9ac1b40c9 | ||
|
|
c0582c27f5 | ||
|
|
cc90f240ee | ||
|
|
a134561e4b | ||
|
|
7b8f04500a | ||
|
|
0a9257e825 | ||
|
|
e1c2c806ed | ||
|
|
6b10835aa9 | ||
|
|
3d09138b1b | ||
|
|
3ee7eb018d | ||
|
|
2d2b42bb38 | ||
|
|
73b27b7d68 | ||
|
|
fbc85aa706 | ||
|
|
5a4b793818 | ||
|
|
e5d93c993e | ||
|
|
a1e272f515 | ||
|
|
447ee0bc82 | ||
|
|
0458c20061 | ||
|
|
81b8306c5f | ||
|
|
b8085ba363 | ||
|
|
c5a3ca37c4 | ||
|
|
d42004eca2 | ||
|
|
3f7ff422bf | ||
|
|
b3fe1e63cf | ||
|
|
50a80a73ab | ||
|
|
f9bed3f7b1 | ||
|
|
c0affad8b4 | ||
|
|
7597ca61aa | ||
|
|
1bf7010153 | ||
|
|
e019768d49 | ||
|
|
c6732cd28b | ||
|
|
b29208f93d | ||
|
|
ba92c88901 | ||
|
|
0305a7d336 | ||
|
|
e720395b61 | ||
|
|
8e5e92b6e2 | ||
|
|
6f0049a2e7 | ||
|
|
95dedd0aed | ||
|
|
1504792a9f | ||
|
|
864e790d22 | ||
|
|
3c5b166f6c | ||
|
|
c584342aa5 | ||
|
|
b7b17e6cac | ||
|
|
810a750306 | ||
|
|
d28a3320c2 | ||
|
|
3e9dc96eb4 | ||
|
|
96067ad107 | ||
|
|
9cfa5ff4a0 | ||
|
|
77cc847c84 | ||
|
|
717112d257 | ||
|
|
f0c709e3e4 | ||
|
|
7a5ee5af60 | ||
|
|
1d50f57dd9 | ||
|
|
3a74bfb500 | ||
|
|
babfa8b85c | ||
|
|
482cc1f251 | ||
|
|
d4d5748ec8 | ||
|
|
be9a8453c4 | ||
|
|
87aae63ed7 | ||
|
|
dc39efa49d | ||
|
|
ce274f7051 | ||
|
|
7226c00490 | ||
|
|
2a6c2604b5 | ||
|
|
f23ced2e4e | ||
|
|
baaf58d2dd | ||
|
|
62f526dfd3 | ||
|
|
63bbefad6b | ||
|
|
ac453a091a | ||
|
|
da819feb74 | ||
|
|
3b9ff76143 | ||
|
|
c62b853648 | ||
|
|
b3acae22ea | ||
|
|
74e00e1401 | ||
|
|
558bec8b9b | ||
|
|
8cee025082 | ||
|
|
6f8fd6728d | ||
|
|
c526899951 | ||
|
|
bfcff49c83 | ||
|
|
31cc0d1a0e | ||
|
|
832307e7b6 | ||
|
|
49aed7782e | ||
|
|
3775a81ced | ||
|
|
0a1bf11560 | ||
|
|
3146e686d0 | ||
|
|
82865ad475 | ||
|
|
0a17150096 | ||
|
|
7178b7dddd | ||
|
|
8832c532b8 | ||
|
|
3f2cd66c1c | ||
|
|
ac33f96a60 | ||
|
|
396feb6353 | ||
|
|
2c4b48d4e0 | ||
|
|
321d3a78b8 | ||
|
|
49068fbfda | ||
|
|
b9793e1cd4 | ||
|
|
6801b0ca20 | ||
|
|
d3f7113d6e | ||
|
|
65911af27c | ||
|
|
1a15c66fb9 | ||
|
|
7f476cd259 | ||
|
|
54782eb30c | ||
|
|
f436996266 | ||
|
|
cf771eaa83 | ||
|
|
3d33762eed | ||
|
|
35d0c8c179 | ||
|
|
74829bb77d | ||
|
|
cb1d42aaa1 | ||
|
|
b9661b41af | ||
|
|
4ed0177782 | ||
|
|
dead62d440 | ||
|
|
50ee38af65 | ||
|
|
7ff885d45c | ||
|
|
3e26cf4cea | ||
|
|
1e188d9715 | ||
|
|
43a428d658 | ||
|
|
cd28b563fc | ||
|
|
d80be1a5b0 | ||
|
|
48252a6483 | ||
|
|
0bbba960f4 | ||
|
|
724834fe8e | ||
|
|
b3526da935 | ||
|
|
b8b5da75a5 | ||
|
|
42fded19ae | ||
|
|
8e5f76ce73 | ||
|
|
b635dec11a | ||
|
|
23787fc71a | ||
|
|
72b2a7382a | ||
|
|
1df71f1e9d | ||
|
|
269867fa43 | ||
|
|
e800cabe48 | ||
|
|
d65285a48c | ||
|
|
7665574628 | ||
|
|
a60ef3b2e1 | ||
|
|
9ab35254a8 | ||
|
|
4266c34620 | ||
|
|
00c8c7b0d5 | ||
|
|
fe53e769e4 | ||
|
|
a7ed4e89c7 | ||
|
|
564af6d074 | ||
|
|
8c911d9b71 | ||
|
|
c9f8339668 | ||
|
|
2609d5fd4b | ||
|
|
9117ec4f69 | ||
|
|
c8abf94560 | ||
|
|
159589d356 | ||
|
|
f03b76f0e6 | ||
|
|
6385dca660 | ||
|
|
cba388b1dc | ||
|
|
bc042d6318 | ||
|
|
56153ba9d8 | ||
|
|
4c303f4759 | ||
|
|
ce0eb58dac | ||
|
|
202965c7e7 | ||
|
|
63b75cf1dd | ||
|
|
00f25932b0 | ||
|
|
dab0827f97 | ||
|
|
3642e92a36 | ||
|
|
3e57d8dcfb | ||
|
|
5ad6aae0b1 | ||
|
|
8f3dae6b9f | ||
|
|
74e3c29215 | ||
|
|
d57ca3da1f | ||
|
|
0e7a10ad80 | ||
|
|
3838b00751 | ||
|
|
207d66162a | ||
|
|
b6ce2c4de6 | ||
|
|
77b8aa0a33 | ||
|
|
d7417618f9 | ||
|
|
97036e362c | ||
|
|
e6777f2eef | ||
|
|
129f0e807e | ||
|
|
0460ab8bd6 | ||
|
|
28e7560c23 | ||
|
|
d65638bf0f | ||
|
|
ef9a60d516 | ||
|
|
9468d50c89 | ||
|
|
b57207c811 | ||
|
|
92c79d078b | ||
|
|
c8b3ca0d12 | ||
|
|
85911abb59 | ||
|
|
1f9b1a6706 | ||
|
|
1fc3ac3932 | ||
|
|
56a0092895 | ||
|
|
7c19cafa93 | ||
|
|
f476bc713f | ||
|
|
3b54a78805 | ||
|
|
9ab4ac837b | ||
|
|
bafd9d9c5f | ||
|
|
9befad4626 | ||
|
|
2f3ed44e06 | ||
|
|
131b1f9768 | ||
|
|
ff7cb44263 | ||
|
|
4b035185e7 | ||
|
|
55a5b3c8e1 | ||
|
|
8378916ed8 | ||
|
|
797e3e0bc2 | ||
|
|
12ece3c84f | ||
|
|
a0ea962bf5 | ||
|
|
4dcbd30d3f | ||
|
|
eb584f748e | ||
|
|
9da74ca5fb | ||
|
|
3ab68b69d1 | ||
|
|
fade2357ba | ||
|
|
c7e45fea9f | ||
|
|
a642af5235 | ||
|
|
7977ec6054 | ||
|
|
9be7de5998 | ||
|
|
5334e2ba94 | ||
|
|
1d97749791 | ||
|
|
620376ad39 | ||
|
|
f320619216 | ||
|
|
7cbbcee6ed | ||
|
|
68b9475585 | ||
|
|
b43f808190 | ||
|
|
b90ba259ef | ||
|
|
5c2a4a026a | ||
|
|
7f46adc19c | ||
|
|
9c8891eed1 | ||
|
|
96c0059fe9 | ||
|
|
ae99db48e6 | ||
|
|
4d8a2f4000 | ||
|
|
f330ec1e9d | ||
|
|
d10980bc9f | ||
|
|
62a4129a1c | ||
|
|
eb844a1ad9 | ||
|
|
b0f4535781 | ||
|
|
cbbbc75c88 | ||
|
|
d8dbbc54ab | ||
|
|
f74e338e0a | ||
|
|
604424b624 | ||
|
|
5167a5029d |
4
.clang-format
Normal file
4
.clang-format
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
BasedOnStyle: LLVM
|
||||||
|
SortIncludes: false
|
||||||
|
SeparateDefinitionBlocks: Always
|
||||||
|
MaxEmptyLinesToKeep: 1
|
||||||
13
.github/dependabot.yml
vendored
Normal file
13
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Keep GitHub Actions up to date with GitHub's Dependabot...
|
||||||
|
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
|
||||||
|
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
groups:
|
||||||
|
github-actions:
|
||||||
|
patterns:
|
||||||
|
- "*" # Group all Actions updates into a single larger pull request
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
12
.github/pull_request_template.md
vendored
Normal file
12
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
Our CI tests check formatting automating. If such a test fails, please consider running the bash script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash script/run-clangcldocker.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
If you are unable to format the code, we may format it for you.
|
||||||
2
.github/workflows/alpine.yml
vendored
2
.github/workflows/alpine.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
- riscv64
|
- riscv64
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install latest Alpine Linux for ${{ matrix.arch }}
|
- name: Install latest Alpine Linux for ${{ matrix.arch }}
|
||||||
uses: jirutka/setup-alpine@v1
|
uses: jirutka/setup-alpine@v1
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
name: Amalgamate Ubuntu 20.04 CI (GCC 9)
|
name: Amalgamate Ubuntu 24.04 CI
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ubuntu-build:
|
ubuntu-build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Compile with amalgamation
|
- name: Compile with amalgamation
|
||||||
run: |
|
run: |
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
4
.github/workflows/cifuzz.yml
vendored
4
.github/workflows/cifuzz.yml
vendored
@ -20,14 +20,14 @@ jobs:
|
|||||||
fuzz-seconds: 300
|
fuzz-seconds: 300
|
||||||
output-sarif: true
|
output-sarif: true
|
||||||
- name: Upload Crash
|
- name: Upload Crash
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v5
|
||||||
if: failure() && steps.build.outcome == 'success'
|
if: failure() && steps.build.outcome == 'success'
|
||||||
with:
|
with:
|
||||||
name: artifacts
|
name: artifacts
|
||||||
path: ./out/artifacts
|
path: ./out/artifacts
|
||||||
- name: Upload Sarif
|
- name: Upload Sarif
|
||||||
if: always() && steps.build.outcome == 'success'
|
if: always() && steps.build.outcome == 'success'
|
||||||
uses: github/codeql-action/upload-sarif@v2
|
uses: github/codeql-action/upload-sarif@v4
|
||||||
with:
|
with:
|
||||||
# Path to SARIF file relative to the root of the repository
|
# Path to SARIF file relative to the root of the repository
|
||||||
sarif_file: cifuzz-sarif/results.sarif
|
sarif_file: cifuzz-sarif/results.sarif
|
||||||
|
|||||||
17
.github/workflows/emscripten.yml
vendored
Normal file
17
.github/workflows/emscripten.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.2.2
|
||||||
|
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
|
- uses: mymindstorm/setup-emsdk@6ab9eb1bda2574c4ddb79809fc9247783eaf9021 # v14
|
||||||
|
- name: Verify
|
||||||
|
run: emcc -v
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v3.6.0
|
||||||
|
- name: Configure
|
||||||
|
run: emcmake cmake -B build
|
||||||
|
- name: Build # We build but do not test
|
||||||
|
run: cmake --build build
|
||||||
33
.github/workflows/lint_and_format_check.yml
vendored
Normal file
33
.github/workflows/lint_and_format_check.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
name: Lint and format
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, ready_for_review]
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
- 'docs/**'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
- 'docs/**'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-and-format:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.1.7
|
||||||
|
|
||||||
|
- name: Run clang-format
|
||||||
|
uses: jidicula/clang-format-action@4726374d1aa3c6aecf132e5197e498979588ebc8 # v4.15.0
|
||||||
|
with:
|
||||||
|
clang-format-version: '17'
|
||||||
|
|
||||||
2
.github/workflows/msys2-clang.yml
vendored
2
.github/workflows/msys2-clang.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
CMAKE_GENERATOR: Ninja
|
CMAKE_GENERATOR: Ninja
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- uses: msys2/setup-msys2@v2
|
- uses: msys2/setup-msys2@v2
|
||||||
with:
|
with:
|
||||||
update: true
|
update: true
|
||||||
|
|||||||
2
.github/workflows/msys2.yml
vendored
2
.github/workflows/msys2.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
|||||||
CMAKE_GENERATOR: Ninja
|
CMAKE_GENERATOR: Ninja
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- uses: msys2/setup-msys2@v2
|
- uses: msys2/setup-msys2@v2
|
||||||
with:
|
with:
|
||||||
update: true
|
update: true
|
||||||
|
|||||||
2
.github/workflows/on-release.yml
vendored
2
.github/workflows/on-release.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Amalgamate fast_float.h
|
- name: Amalgamate fast_float.h
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
23
.github/workflows/risc.yml
vendored
Normal file
23
.github/workflows/risc.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
name: Ubuntu RISC-V rvv VLEN=128 (clang 17)
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- name: Install packages
|
||||||
|
run: |
|
||||||
|
sudo apt-get update -q -y
|
||||||
|
sudo apt-get install -y cmake make g++-riscv64-linux-gnu qemu-user-static clang-17
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
CXX=clang++-17 CXXFLAGS="--target=riscv64-linux-gnu -march=rv64gcv" \
|
||||||
|
cmake --toolchain=cmake/toolchains-ci/riscv64-linux-gnu.cmake -DCMAKE_BUILD_TYPE=Release -B build
|
||||||
|
cmake --build build/ -j$(nproc)
|
||||||
|
- name: Test VLEN=128
|
||||||
|
run: |
|
||||||
|
export QEMU_LD_PREFIX="/usr/riscv64-linux-gnu"
|
||||||
|
export QEMU_CPU="rv64,v=on,vlen=128,rvv_ta_all_1s=on,rvv_ma_all_1s=on"
|
||||||
|
ctest --timeout 1800 --output-on-failure --test-dir build -j $(nproc)
|
||||||
4
.github/workflows/s390x.yml
vendored
4
.github/workflows/s390x.yml
vendored
@ -12,8 +12,8 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- uses: uraimo/run-on-arch-action@v2
|
- uses: uraimo/run-on-arch-action@v3
|
||||||
name: Test
|
name: Test
|
||||||
id: runcmd
|
id: runcmd
|
||||||
with:
|
with:
|
||||||
|
|||||||
16
.github/workflows/ubuntu20-fastmath.yml
vendored
16
.github/workflows/ubuntu20-fastmath.yml
vendored
@ -1,16 +0,0 @@
|
|||||||
name: Ubuntu 20.04 CI (GCC 9, fast-math)
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ubuntu-build:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Use cmake
|
|
||||||
run: |
|
|
||||||
mkdir build &&
|
|
||||||
cd build &&
|
|
||||||
cmake -DCMAKE_CXX_FLAGS="-ffast-math" -DFASTFLOAT_TEST=ON .. &&
|
|
||||||
cmake --build . &&
|
|
||||||
ctest --output-on-failure
|
|
||||||
21
.github/workflows/ubuntu20.yml
vendored
21
.github/workflows/ubuntu20.yml
vendored
@ -1,21 +0,0 @@
|
|||||||
name: Ubuntu 20.04 CI (GCC 9)
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ubuntu-build:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Use cmake
|
|
||||||
run: |
|
|
||||||
mkdir build &&
|
|
||||||
cd build &&
|
|
||||||
cmake ${{matrix.cxx}} ${{matrix.arch}} -DFASTFLOAT_TEST=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
|
|
||||||
cmake --build . &&
|
|
||||||
ctest --output-on-failure &&
|
|
||||||
cmake --install . &&
|
|
||||||
cd ../tests/installation_tests/find &&
|
|
||||||
mkdir build && cd build && cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../build/destination .. && cmake --build . &&
|
|
||||||
cd ../../issue72_installation &&
|
|
||||||
mkdir build && cd build && cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../build/destination .. && cmake --build .
|
|
||||||
2
.github/workflows/ubuntu22-clang.yml
vendored
2
.github/workflows/ubuntu22-clang.yml
vendored
@ -6,7 +6,7 @@ jobs:
|
|||||||
ubuntu-build:
|
ubuntu-build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Install clang++-14
|
- name: Install clang++-14
|
||||||
run: sudo apt-get install -y clang++-14
|
run: sudo apt-get install -y clang++-14
|
||||||
- name: Use cmake
|
- name: Use cmake
|
||||||
|
|||||||
2
.github/workflows/ubuntu22-gcc12.yml
vendored
2
.github/workflows/ubuntu22-gcc12.yml
vendored
@ -6,7 +6,7 @@ jobs:
|
|||||||
ubuntu-build:
|
ubuntu-build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Use cmake
|
- name: Use cmake
|
||||||
run: |
|
run: |
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
|
|||||||
16
.github/workflows/ubuntu22-sanitize.yml
vendored
Normal file
16
.github/workflows/ubuntu22-sanitize.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
name: Ubuntu 22.04 CI Sanitized (GCC 11)
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ubuntu-build:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- name: Use cmake
|
||||||
|
run: |
|
||||||
|
mkdir build &&
|
||||||
|
cd build &&
|
||||||
|
cmake -DFASTFLOAT_TEST=ON -D FASTFLOAT_SANITIZE=ON .. &&
|
||||||
|
cmake --build . &&
|
||||||
|
ctest --output-on-failure
|
||||||
2
.github/workflows/ubuntu22.yml
vendored
2
.github/workflows/ubuntu22.yml
vendored
@ -6,7 +6,7 @@ jobs:
|
|||||||
ubuntu-build:
|
ubuntu-build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Use cmake
|
- name: Use cmake
|
||||||
run: |
|
run: |
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
name: Ubuntu 20.04 CI (C++20)
|
name: Ubuntu 24.04 CI
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ubuntu-build:
|
ubuntu-build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Use cmake
|
- name: Use cmake
|
||||||
run: |
|
run: |
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
29
.github/workflows/ubuntu24.yml
vendored
Normal file
29
.github/workflows/ubuntu24.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
name: Ubuntu 24.04 CI (GCC 13)
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ubuntu-build:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- name: Use cmake
|
||||||
|
run: |
|
||||||
|
set -xe
|
||||||
|
cmake -B build \
|
||||||
|
-DFASTFLOAT_TEST=ON \
|
||||||
|
-DFASTFLOAT_BENCHMARKS=ON \
|
||||||
|
-DCMAKE_CXX_FLAGS=' -Werror -Wundef '
|
||||||
|
cmake --build build --parallel
|
||||||
|
( cd build ; ctest --output-on-failure )
|
||||||
|
- name: Use cmake CXX23
|
||||||
|
run: |
|
||||||
|
set -xe
|
||||||
|
cmake -B build20 \
|
||||||
|
-DFASTFLOAT_TEST=ON \
|
||||||
|
-DFASTFLOAT_CONSTEXPR_TESTS=ON \
|
||||||
|
-DFASTFLOAT_FIXEDWIDTH_TESTS=ON \
|
||||||
|
-DFASTFLOAT_CXX_STANDARD=23 \
|
||||||
|
-DCMAKE_CXX_FLAGS=' -Werror -Wundef '
|
||||||
|
cmake --build build20 --parallel
|
||||||
|
( cd build20 ; ctest --output-on-failure )
|
||||||
4
.github/workflows/vs17-arm-ci.yml
vendored
4
.github/workflows/vs17-arm-ci.yml
vendored
@ -10,13 +10,11 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- {gen: Visual Studio 17 2022, arch: ARM, cfg: Release}
|
|
||||||
- {gen: Visual Studio 17 2022, arch: ARM, cfg: Debug}
|
|
||||||
- {gen: Visual Studio 17 2022, arch: ARM64, cfg: Release}
|
- {gen: Visual Studio 17 2022, arch: ARM64, cfg: Release}
|
||||||
- {gen: Visual Studio 17 2022, arch: ARM64, cfg: Debug}
|
- {gen: Visual Studio 17 2022, arch: ARM64, cfg: Debug}
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v5
|
||||||
- name: configure
|
- name: configure
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DCMAKE_CROSSCOMPILING=1 -DFASTFLOAT_TEST=ON
|
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DCMAKE_CROSSCOMPILING=1 -DFASTFLOAT_TEST=ON
|
||||||
|
|||||||
4
.github/workflows/vs17-ci.yml
vendored
4
.github/workflows/vs17-ci.yml
vendored
@ -16,10 +16,10 @@ jobs:
|
|||||||
- {gen: Visual Studio 17 2022, arch: x64, cfg: Debug}
|
- {gen: Visual Studio 17 2022, arch: x64, cfg: Debug}
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v5
|
||||||
- name: configure
|
- name: configure
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DFASTFLOAT_TEST=ON -DCMAKE_INSTALL_PREFIX:PATH=destination
|
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DFASTFLOAT_BENCHMARKS=ON -DFASTFLOAT_TEST=ON -DCMAKE_INSTALL_PREFIX:PATH=destination
|
||||||
- name: build
|
- name: build
|
||||||
run: |
|
run: |
|
||||||
cmake --build build --verbose --config ${{matrix.cfg}} --parallel
|
cmake --build build --verbose --config ${{matrix.cfg}} --parallel
|
||||||
|
|||||||
4
.github/workflows/vs17-clang-ci.yml
vendored
4
.github/workflows/vs17-clang-ci.yml
vendored
@ -16,10 +16,10 @@ jobs:
|
|||||||
- {gen: Visual Studio 17 2022, arch: x64, cfg: Debug}
|
- {gen: Visual Studio 17 2022, arch: x64, cfg: Debug}
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v5
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -T ClangCL -DFASTFLOAT_TEST=ON
|
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DFASTFLOAT_BENCHMARKS=ON -T ClangCL -DFASTFLOAT_TEST=ON
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --config ${{matrix.cfg}} --parallel --verbose
|
run: cmake --build build --config ${{matrix.cfg}} --parallel --verbose
|
||||||
- name: Run basic tests
|
- name: Run basic tests
|
||||||
|
|||||||
2
.github/workflows/vs17-cxx20.yml
vendored
2
.github/workflows/vs17-cxx20.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
- {gen: Visual Studio 17 2022, arch: x64, cfg: Debug}
|
- {gen: Visual Studio 17 2022, arch: x64, cfg: Debug}
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v5
|
||||||
- name: configure
|
- name: configure
|
||||||
run: >-
|
run: >-
|
||||||
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}}
|
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@ build/*
|
|||||||
Testing/*
|
Testing/*
|
||||||
.cache/
|
.cache/
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
bazel-*
|
||||||
|
|
||||||
# Visual studio
|
# Visual studio
|
||||||
.vs/
|
.vs/
|
||||||
|
|||||||
6
BUILD.bazel
Normal file
6
BUILD.bazel
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
cc_library(
|
||||||
|
name = "fast_float",
|
||||||
|
hdrs = glob(["include/fast_float/*.h"]),
|
||||||
|
strip_include_prefix = "include",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@ -1,7 +1,11 @@
|
|||||||
cmake_minimum_required(VERSION 3.9)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
|
|
||||||
project(fast_float VERSION 6.0.0 LANGUAGES CXX)
|
|
||||||
|
project(fast_float VERSION 8.1.0 LANGUAGES CXX)
|
||||||
|
set(FASTFLOAT_CXX_STANDARD 11 CACHE STRING "the C++ standard to use for fastfloat")
|
||||||
|
set(CMAKE_CXX_STANDARD ${FASTFLOAT_CXX_STANDARD})
|
||||||
option(FASTFLOAT_TEST "Enable tests" OFF)
|
option(FASTFLOAT_TEST "Enable tests" OFF)
|
||||||
|
|
||||||
if(FASTFLOAT_TEST)
|
if(FASTFLOAT_TEST)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
@ -27,6 +31,16 @@ if(FASTFLOAT_INSTALL)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(fast_float INTERFACE)
|
add_library(fast_float INTERFACE)
|
||||||
|
|
||||||
|
|
||||||
|
option(FASTFLOAT_BENCHMARKS "Enable benchmarks" OFF)
|
||||||
|
if(FASTFLOAT_BENCHMARKS)
|
||||||
|
add_subdirectory(benchmarks)
|
||||||
|
else(FASTFLOAT_BENCHMARKS)
|
||||||
|
message(STATUS "Benchmarks are disabled. Set FASTFLOAT_BENCHMARKS to ON to build benchmarks (assumes C++17).")
|
||||||
|
endif(FASTFLOAT_BENCHMARKS)
|
||||||
|
|
||||||
|
|
||||||
add_library(FastFloat::fast_float ALIAS fast_float)
|
add_library(FastFloat::fast_float ALIAS fast_float)
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
fast_float
|
fast_float
|
||||||
@ -42,11 +56,15 @@ if(FASTFLOAT_SANITIZE)
|
|||||||
target_link_libraries(fast_float INTERFACE -fuse-ld=gold)
|
target_link_libraries(fast_float INTERFACE -fuse-ld=gold)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
if(MSVC_VERSION GREATER 1910)
|
|
||||||
|
include(CheckCXXCompilerFlag)
|
||||||
|
unset(FASTFLOAT_COMPILER_SUPPORTS_PERMISSIVE)
|
||||||
|
CHECK_CXX_COMPILER_FLAG(/permissive- FASTFLOAT_COMPILER_SUPPORTS_PERMISSIVE)
|
||||||
|
|
||||||
|
if(FASTFLOAT_COMPILER_SUPPORTS_PERMISSIVE)
|
||||||
target_compile_options(fast_float INTERFACE /permissive-)
|
target_compile_options(fast_float INTERFACE /permissive-)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
if(FASTFLOAT_INSTALL)
|
if(FASTFLOAT_INSTALL)
|
||||||
include(CMakePackageConfigHelpers)
|
include(CMakePackageConfigHelpers)
|
||||||
|
|
||||||
|
|||||||
@ -7,3 +7,5 @@ Fabio Pellacini
|
|||||||
Lénárd Szolnoki
|
Lénárd Szolnoki
|
||||||
Jan Pharago
|
Jan Pharago
|
||||||
Maya Warrier
|
Maya Warrier
|
||||||
|
Taha Khokhar
|
||||||
|
Anders Dalvander
|
||||||
|
|||||||
9
MODULE.bazel
Normal file
9
MODULE.bazel
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
"""fast_float number parsing library: 4x faster than strtod"""
|
||||||
|
|
||||||
|
module(
|
||||||
|
name = "fast_float",
|
||||||
|
version = "6.1.6",
|
||||||
|
compatibility_level = 6,
|
||||||
|
)
|
||||||
|
|
||||||
|
bazel_dep(name = "doctest", version = "2.4.11", dev_dependency = True)
|
||||||
573
README.md
573
README.md
@ -1,160 +1,253 @@
|
|||||||
|
|
||||||
## fast_float number parsing library: 4x faster than strtod
|
## fast_float number parsing library: 4x faster than strtod
|
||||||
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:fast_float)
|
|
||||||
[](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml)
|
[](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml)
|
||||||
|
|
||||||
The fast_float library provides fast header-only implementations for the C++ from_chars
|
The fast_float library provides fast header-only implementations for the C++
|
||||||
functions for `float` and `double` types as well as integer types. These functions convert ASCII strings representing decimal values (e.g., `1.3e10`) into binary types. We provide exact rounding (including
|
from_chars functions for `float` and `double` types as well as integer types.
|
||||||
round to even). In our experience, these `fast_float` functions many times faster than comparable number-parsing functions from existing C++ standard libraries.
|
These functions convert ASCII strings representing decimal values (e.g.,
|
||||||
|
`1.3e10`) into binary types. We provide exact rounding (including round to
|
||||||
|
even). In our experience, these `fast_float` functions many times faster than
|
||||||
|
comparable number-parsing functions from existing C++ standard libraries.
|
||||||
|
|
||||||
Specifically, `fast_float` provides the following two functions to parse floating-point numbers with a C++17-like syntax (the library itself only requires C++11):
|
Specifically, `fast_float` provides the following two functions to parse
|
||||||
|
floating-point numbers with a C++17-like syntax (the library itself only
|
||||||
|
requires C++11):
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
from_chars_result from_chars(const char* first, const char* last, float& value, ...);
|
from_chars_result from_chars(char const *first, char const *last, float &value, ...);
|
||||||
from_chars_result from_chars(const char* first, const char* last, double& value, ...);
|
from_chars_result from_chars(char const *first, char const *last, double &value, ...);
|
||||||
|
```
|
||||||
|
If they are available on your system, we also support fixed-width floating-point types such as `std::float64_t`, `std::float32_t`, `std::float16_t`, and `std::bfloat16_t`.
|
||||||
|
|
||||||
|
You can also parse integer types such as `char`, `short`, `long`, `long long`, `unsigned char`, `unsigned short`, `unsigned long`, `unsigned long long`, `bool` (0/1), `int8_t`, `int16_t`, `int32_t`, `int64_t`, `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t`.
|
||||||
|
```C++
|
||||||
|
from_chars_result from_chars(char const *first, char const *last, int &value, ...);
|
||||||
|
from_chars_result from_chars(char const *first, char const *last, unsigned &value, ...);
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also parse integer types:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The return type (`from_chars_result`) is defined as the struct:
|
The return type (`from_chars_result`) is defined as the struct:
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
struct from_chars_result {
|
struct from_chars_result {
|
||||||
const char* ptr;
|
char const *ptr;
|
||||||
std::errc ec;
|
std::errc ec;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
It parses the character sequence [first,last) for a number. It parses floating-point numbers expecting
|
It parses the character sequence `[first, last)` for a number. It parses
|
||||||
a locale-independent format equivalent to the C++17 from_chars function.
|
floating-point numbers expecting a locale-independent format equivalent to the
|
||||||
The resulting floating-point value is the closest floating-point values (using either float or double),
|
C++17 from_chars function. The resulting floating-point value is the closest
|
||||||
using the "round to even" convention for values that would otherwise fall right in-between two values.
|
floating-point values (using either `float` or `double`), using the "round to
|
||||||
That is, we provide exact parsing according to the IEEE standard.
|
even" convention for values that would otherwise fall right in-between two
|
||||||
|
values. That is, we provide exact parsing according to the IEEE standard.
|
||||||
|
|
||||||
|
Given a successful parse, the pointer (`ptr`) in the returned value is set to
|
||||||
|
point right after the parsed number, and the `value` referenced is set to the
|
||||||
|
parsed value. In case of error, the returned `ec` contains a representative
|
||||||
|
error, otherwise the default (`std::errc()`) value is stored.
|
||||||
|
|
||||||
Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the
|
The implementation does not throw and does not allocate memory (e.g., with `new`
|
||||||
parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned
|
or `malloc`).
|
||||||
`ec` contains a representative error, otherwise the default (`std::errc()`) value is stored.
|
|
||||||
|
|
||||||
The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`).
|
|
||||||
|
|
||||||
It will parse infinity and nan values.
|
It will parse infinity and nan values.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
``` C++
|
```C++
|
||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
const std::string input = "3.1416 xyz ";
|
std::string input = "3.1416 xyz ";
|
||||||
double result;
|
double result;
|
||||||
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
|
auto answer = fast_float::from_chars(input.data(), input.data() + input.size(), result);
|
||||||
if(answer.ec != std::errc()) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
|
if (answer.ec != std::errc()) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
|
||||||
|
std::cout << "parsed the number " << result << std::endl;
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Though the C++17 standard has you do a comparison with `std::errc()` to check whether the conversion worked, you can avoid it by casting the result to a `bool` like so:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "fast_float/fast_float.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::string input = "3.1416 xyz ";
|
||||||
|
double result;
|
||||||
|
if(auto answer = fast_float::from_chars(input.data(), input.data() + input.size(), result)) {
|
||||||
std::cout << "parsed the number " << result << std::endl;
|
std::cout << "parsed the number " << result << std::endl;
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
std::cerr << "failed to parse " << result << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can parse delimited numbers:
|
You can parse delimited numbers:
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
const std::string input = "234532.3426362,7869234.9823,324562.645";
|
std::string input = "234532.3426362,7869234.9823,324562.645";
|
||||||
double result;
|
double result;
|
||||||
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
|
auto answer = fast_float::from_chars(input.data(), input.data() + input.size(), result);
|
||||||
if(answer.ec != std::errc()) {
|
if (answer.ec != std::errc()) {
|
||||||
// check error
|
// check error
|
||||||
}
|
}
|
||||||
// we have result == 234532.3426362.
|
// we have result == 234532.3426362.
|
||||||
if(answer.ptr[0] != ',') {
|
if (answer.ptr[0] != ',') {
|
||||||
// unexpected delimiter
|
// unexpected delimiter
|
||||||
}
|
}
|
||||||
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
|
answer = fast_float::from_chars(answer.ptr + 1, input.data() + input.size(), result);
|
||||||
if(answer.ec != std::errc()) {
|
if (answer.ec != std::errc()) {
|
||||||
// check error
|
// check error
|
||||||
}
|
}
|
||||||
// we have result == 7869234.9823.
|
// we have result == 7869234.9823.
|
||||||
if(answer.ptr[0] != ',') {
|
if (answer.ptr[0] != ',') {
|
||||||
// unexpected delimiter
|
// unexpected delimiter
|
||||||
}
|
}
|
||||||
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
|
answer = fast_float::from_chars(answer.ptr + 1, input.data() + input.size(), result);
|
||||||
if(answer.ec != std::errc()) {
|
if (answer.ec != std::errc()) {
|
||||||
// check error
|
// check error
|
||||||
}
|
}
|
||||||
// we have result == 324562.645.
|
// we have result == 324562.645.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Like the C++17 standard, the `fast_float::from_chars` functions take an optional
|
||||||
|
last argument of the type `fast_float::chars_format`. It is a bitset value: we
|
||||||
|
check whether `fmt & fast_float::chars_format::fixed` and `fmt &
|
||||||
|
fast_float::chars_format::scientific` are set to determine whether we allow the
|
||||||
|
fixed point and scientific notation respectively. The default is
|
||||||
|
`fast_float::chars_format::general` which allows both `fixed` and `scientific`.
|
||||||
|
|
||||||
|
The library seeks to follow the C++17 (see
|
||||||
|
[28.2.3.(6.1)](https://eel.is/c++draft/charconv.from.chars#6.1)) specification.
|
||||||
|
|
||||||
Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of
|
* The `from_chars` function does not skip leading white-space characters (unless
|
||||||
the type `fast_float::chars_format`. It is a bitset value: we check whether
|
`fast_float::chars_format::skip_white_space` is set).
|
||||||
`fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set
|
* [A leading `+` sign](https://en.cppreference.com/w/cpp/utility/from_chars) is
|
||||||
to determine whether we allow the fixed point and scientific notation respectively.
|
forbidden (unless `fast_float::chars_format::allow_leading_plus` is set).
|
||||||
The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`.
|
* It is generally impossible to represent a decimal value exactly as binary
|
||||||
|
floating-point number (`float` and `double` types). We seek the nearest value.
|
||||||
The library seeks to follow the C++17 (see [20.19.3](http://eel.is/c++draft/charconv.from.chars).(7.1)) specification.
|
We round to an even mantissa when we are in-between two binary floating-point
|
||||||
* The `from_chars` function does not skip leading white-space characters.
|
numbers.
|
||||||
* [A leading `+` sign](https://en.cppreference.com/w/cpp/utility/from_chars) is forbidden.
|
|
||||||
* It is generally impossible to represent a decimal value exactly as binary floating-point number (`float` and `double` types). We seek the nearest value. We round to an even mantissa when we are in-between two binary floating-point numbers.
|
|
||||||
|
|
||||||
Furthermore, we have the following restrictions:
|
Furthermore, we have the following restrictions:
|
||||||
* We only support `float` and `double` types at this time.
|
|
||||||
|
* We support `float` and `double`, but not `long double`. We also support
|
||||||
|
fixed-width floating-point types such as `std::float64_t`, `std::float32_t`,
|
||||||
|
`std::float16_t`, and `std::bfloat16_t`.
|
||||||
* We only support the decimal format: we do not support hexadecimal strings.
|
* We only support the decimal format: we do not support hexadecimal strings.
|
||||||
* For values that are either very large or very small (e.g., `1e9999`), we represent it using the infinity or negative infinity value and the returned `ec` is set to `std::errc::result_out_of_range`.
|
* For values that are either very large or very small (e.g., `1e9999`), we
|
||||||
|
represent it using the infinity or negative infinity value and the returned
|
||||||
|
`ec` is set to `std::errc::result_out_of_range`.
|
||||||
|
|
||||||
We support Visual Studio, macOS, Linux, freeBSD. We support big and little endian. We support 32-bit and 64-bit systems.
|
We support Visual Studio, macOS, Linux, freeBSD. We support big and little
|
||||||
|
endian. We support 32-bit and 64-bit systems.
|
||||||
We assume that the rounding mode is set to nearest (`std::fegetround() == FE_TONEAREST`).
|
|
||||||
|
|
||||||
|
We assume that the rounding mode is set to nearest (`std::fegetround() ==
|
||||||
|
FE_TONEAREST`).
|
||||||
|
|
||||||
## Integer types
|
## Integer types
|
||||||
|
|
||||||
You can also parse integer types using different bases (e.g., 2, 10, 16). The following code will
|
You can also parse integer types using different bases (e.g., 2, 10, 16). The
|
||||||
print the number 22250738585072012 three times:
|
following code will print the number 22250738585072012 three times:
|
||||||
|
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
|
#include "fast_float/fast_float.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main() {
|
||||||
uint64_t i;
|
uint64_t i;
|
||||||
const char str[] = "22250738585072012";
|
std::string str = "22250738585072012";
|
||||||
auto answer = fast_float::from_chars(str, str + strlen(str), i);
|
auto answer = fast_float::from_chars(str.data(), str.data() + str.size(), i);
|
||||||
if (answer.ec != std::errc()) {
|
if (answer.ec != std::errc()) {
|
||||||
std::cerr << "parsing failure\n";
|
std::cerr << "parsing failure\n";
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
std::cout << "parsed the number "<< i << std::endl;
|
std::cout << "parsed the number " << i << std::endl;
|
||||||
|
|
||||||
const char binstr[] = "1001111000011001110110111001001010110100111000110001100";
|
std::string binstr = "1001111000011001110110111001001010110100111000110001100";
|
||||||
|
|
||||||
answer = fast_float::from_chars(binstr, binstr + strlen(binstr), i, 2);
|
answer = fast_float::from_chars(binstr.data(), binstr.data() + binstr.size(), i, 2);
|
||||||
if (answer.ec != std::errc()) {
|
if (answer.ec != std::errc()) {
|
||||||
std::cerr << "parsing failure\n";
|
std::cerr << "parsing failure\n";
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
std::cout << "parsed the number "<< i << std::endl;
|
std::cout << "parsed the number " << i << std::endl;
|
||||||
|
|
||||||
|
std::string hexstr = "4f0cedc95a718c";
|
||||||
|
|
||||||
const char hexstr[] = "4f0cedc95a718c";
|
answer = fast_float::from_chars(hexstr.data(), hexstr.data() + hexstr.size(), i, 16);
|
||||||
|
|
||||||
answer = fast_float::from_chars(hexstr, hexstr + strlen(hexstr), i, 16);
|
|
||||||
if (answer.ec != std::errc()) {
|
if (answer.ec != std::errc()) {
|
||||||
std::cerr << "parsing failure\n";
|
std::cerr << "parsing failure\n";
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
std::cout << "parsed the number "<< i << std::endl;
|
std::cout << "parsed the number " << i << std::endl;
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Behavior of result_out_of_range
|
||||||
|
|
||||||
|
When parsing floating-point values, the numbers can sometimes be too small
|
||||||
|
(e.g., `1e-1000`) or too large (e.g., `1e1000`). The C language established the
|
||||||
|
precedent that these small values are out of range. In such cases, it is
|
||||||
|
customary to parse small values to zero and large values to infinity. That is
|
||||||
|
the behaviour of the C language (e.g., `stdtod`). That is the behaviour followed
|
||||||
|
by the fast_float library.
|
||||||
|
|
||||||
|
Specifically, we follow Jonathan Wakely's interpretation of the standard:
|
||||||
|
|
||||||
|
> In any case, the resulting value is one of at most two floating-point values
|
||||||
|
> closest to the value of the string matching the pattern.
|
||||||
|
|
||||||
|
It is also the approach taken by the [Microsoft C++
|
||||||
|
library](https://github.com/microsoft/STL/blob/62205ab155d093e71dd9588a78f02c5396c3c14b/tests/std/tests/P0067R5_charconv/test.cpp#L943-L946).
|
||||||
|
|
||||||
|
Hence, we have the following examples:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
double result = -1;
|
||||||
|
std::string str = "3e-1000";
|
||||||
|
auto r = fast_float::from_chars(str.data(), str.data() + str.size(), result);
|
||||||
|
// r.ec == std::errc::result_out_of_range
|
||||||
|
// r.ptr == str.data() + 7
|
||||||
|
// result == 0
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
double result = -1;
|
||||||
|
std::string str = "3e1000";
|
||||||
|
auto r = fast_float::from_chars(str.data(), str.data() + str.size(), result);
|
||||||
|
// r.ec == std::errc::result_out_of_range
|
||||||
|
// r.ptr == str.data() + 6
|
||||||
|
// result == std::numeric_limits<double>::infinity()
|
||||||
|
```
|
||||||
|
|
||||||
|
Users who wish for the value to be left unmodified given
|
||||||
|
`std::errc::result_out_of_range` may do so by adding two lines of code:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
double old_result = result; // make copy
|
||||||
|
auto r = fast_float::from_chars(start, end, result);
|
||||||
|
if (r.ec == std::errc::result_out_of_range) { result = old_result; }
|
||||||
```
|
```
|
||||||
|
|
||||||
## C++20: compile-time evaluation (constexpr)
|
## C++20: compile-time evaluation (constexpr)
|
||||||
|
|
||||||
In C++20, you may use `fast_float::from_chars` to parse strings
|
In C++20, you may use `fast_float::from_chars` to parse strings at compile-time,
|
||||||
at compile-time, as in the following example:
|
as in the following example:
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
// consteval forces compile-time evaluation of the function in C++20.
|
// consteval forces compile-time evaluation of the function in C++20.
|
||||||
consteval double parse(std::string_view input) {
|
consteval double parse(std::string_view input) {
|
||||||
double result;
|
double result;
|
||||||
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
|
auto answer = fast_float::from_chars(input.data(), input.data() + input.size(), result);
|
||||||
if(answer.ec != std::errc()) { return -1.0; }
|
if (answer.ec != std::errc()) { return -1.0; }
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,100 +258,109 @@ constexpr double constexptest() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## C++23: Fixed width floating-point types
|
||||||
|
|
||||||
|
The library also supports fixed-width floating-point types such as
|
||||||
|
`std::float64_t`, `std::float32_t`, `std::float16_t`, and `std::bfloat16_t`.
|
||||||
|
E.g., you can write:
|
||||||
|
|
||||||
|
```C++
|
||||||
|
std::float32_t result;
|
||||||
|
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
|
||||||
|
```
|
||||||
|
|
||||||
## Non-ASCII Inputs
|
## Non-ASCII Inputs
|
||||||
|
|
||||||
We also support UTF-16 and UTF-32 inputs, as well as ASCII/UTF-8, as in the following example:
|
We also support UTF-16 and UTF-32 inputs, as well as ASCII/UTF-8, as in the
|
||||||
|
following example:
|
||||||
|
|
||||||
``` C++
|
```C++
|
||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
const std::u16string input = u"3.1416 xyz ";
|
std::u16string input = u"3.1416 xyz ";
|
||||||
double result;
|
double result;
|
||||||
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
|
auto answer = fast_float::from_chars(input.data(), input.data() + input.size(), result);
|
||||||
if(answer.ec != std::errc()) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
|
if (answer.ec != std::errc()) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
|
||||||
std::cout << "parsed the number " << result << std::endl;
|
std::cout << "parsed the number " << result << std::endl;
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Advanced options: using commas as decimal separator, JSON and Fortran
|
## Advanced options: using commas as decimal separator, JSON and Fortran
|
||||||
|
|
||||||
|
|
||||||
The C++ standard stipulate that `from_chars` has to be locale-independent. In
|
The C++ standard stipulate that `from_chars` has to be locale-independent. In
|
||||||
particular, the decimal separator has to be the period (`.`). However,
|
particular, the decimal separator has to be the period (`.`). However, some
|
||||||
some users still want to use the `fast_float` library with in a locale-dependent
|
users still want to use the `fast_float` library with in a locale-dependent
|
||||||
manner. Using a separate function called `from_chars_advanced`, we allow the users
|
manner. Using a separate function called `from_chars_advanced`, we allow the
|
||||||
to pass a `parse_options` instance which contains a custom decimal separator (e.g.,
|
users to pass a `parse_options` instance which contains a custom decimal
|
||||||
the comma). You may use it as follows.
|
separator (e.g., the comma). You may use it as follows.
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
const std::string input = "3,1416 xyz ";
|
std::string input = "3,1416 xyz ";
|
||||||
double result;
|
double result;
|
||||||
fast_float::parse_options options{fast_float::chars_format::general, ','};
|
fast_float::parse_options options{fast_float::chars_format::general, ','};
|
||||||
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
|
auto answer = fast_float::from_chars_advanced(input.data(), input.data() + input.size(), result, options);
|
||||||
if((answer.ec != std::errc()) || ((result != 3.1416))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
|
if ((answer.ec != std::errc()) || ((result != 3.1416))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
|
||||||
std::cout << "parsed the number " << result << std::endl;
|
std::cout << "parsed the number " << result << std::endl;
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also parse Fortran-like inputs:
|
### You can also parse Fortran-like inputs
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
const std::string input = "1d+4";
|
std::string input = "1d+4";
|
||||||
double result;
|
double result;
|
||||||
fast_float::parse_options options{ fast_float::chars_format::fortran };
|
fast_float::parse_options options{fast_float::chars_format::fortran};
|
||||||
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
|
auto answer = fast_float::from_chars_advanced(input.data(), input.data() + input.size(), result, options);
|
||||||
if((answer.ec != std::errc()) || ((result != 10000))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
|
if ((answer.ec != std::errc()) || ((result != 10000))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
|
||||||
std::cout << "parsed the number " << result << std::endl;
|
std::cout << "parsed the number " << result << std::endl;
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You may also enforce the JSON format ([RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259#section-6)):
|
### You may also enforce the JSON format ([RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259#section-6))
|
||||||
|
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
const std::string input = "+.1"; // not valid
|
std::string input = "+.1"; // not valid
|
||||||
double result;
|
double result;
|
||||||
fast_float::parse_options options{ fast_float::chars_format::json };
|
fast_float::parse_options options{fast_float::chars_format::json};
|
||||||
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
|
auto answer = fast_float::from_chars_advanced(input.data(), input.data() + input.size(), result, options);
|
||||||
if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; }
|
if (answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; }
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
By default the JSON format does not allow `inf`:
|
By default the JSON format does not allow `inf`:
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
|
|
||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
const std::string input = "inf"; // not valid in JSON
|
std::string input = "inf"; // not valid in JSON
|
||||||
double result;
|
double result;
|
||||||
fast_float::parse_options options{ fast_float::chars_format::json };
|
fast_float::parse_options options{fast_float::chars_format::json};
|
||||||
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
|
auto answer = fast_float::from_chars_advanced(input.data(), input.data() + input.size(), result, options);
|
||||||
if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; }
|
if (answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; }
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
You can allow it with a non-standard `json_or_infnan` variant:
|
You can allow it with a non-standard `json_or_infnan` variant:
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
@ -266,55 +368,135 @@ You can allow it with a non-standard `json_or_infnan` variant:
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
const std::string input = "inf"; // not valid in JSON but we allow it with json_or_infnan
|
std::string input = "inf"; // not valid in JSON but we allow it with json_or_infnan
|
||||||
double result;
|
double result;
|
||||||
fast_float::parse_options options{ fast_float::chars_format::json_or_infnan };
|
fast_float::parse_options options{fast_float::chars_format::json_or_infnan};
|
||||||
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
|
auto answer = fast_float::from_chars_advanced(input.data(), input.data() + input.size(), result, options);
|
||||||
if(answer.ec != std::errc() || (!std::isinf(result))) { std::cerr << "should have parsed infinity\n"; return EXIT_FAILURE; }
|
if (answer.ec != std::errc() || (!std::isinf(result))) { std::cerr << "should have parsed infinity\n"; return EXIT_FAILURE; }
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
``````
|
```
|
||||||
|
|
||||||
## Relation With Other Work
|
## Multiplication of an integer by a power of 10
|
||||||
|
An integer `W` can be multiplied by a power of ten `10^Q` and
|
||||||
|
converted to `double` with correctly rounded value
|
||||||
|
(in "round to nearest, tie to even" fashion) using
|
||||||
|
`fast_float::integer_times_pow10()`, e.g.:
|
||||||
|
```C++
|
||||||
|
const uint64_t W = 12345678901234567;
|
||||||
|
const int Q = 23;
|
||||||
|
const double result = fast_float::integer_times_pow10(W, Q);
|
||||||
|
std::cout.precision(17);
|
||||||
|
std::cout << W << " * 10^" << Q << " = " << result << " ("
|
||||||
|
<< (result == 12345678901234567e23 ? "==" : "!=") << "expected)\n";
|
||||||
|
```
|
||||||
|
outputs
|
||||||
|
```
|
||||||
|
12345678901234567 * 10^23 = 1.2345678901234567e+39 (==expected)
|
||||||
|
```
|
||||||
|
`fast_float::integer_times_pow10()` gives the same result as
|
||||||
|
using `fast_float::from_chars()` when parsing the string `"WeQ"`
|
||||||
|
(in this example `"12345678901234567e23"`),
|
||||||
|
except `fast_float::integer_times_pow10()` does not report out-of-range errors, and
|
||||||
|
underflows to zero or overflows to infinity when the resulting value is
|
||||||
|
out of range.
|
||||||
|
|
||||||
|
You can use template overloads to get the result converted to different
|
||||||
|
supported floating-point types: `float`, `double`, etc.
|
||||||
|
For example, to get result as `float` use
|
||||||
|
`fast_float::integer_times_pow10<float>()` specialization:
|
||||||
|
```C++
|
||||||
|
const uint64_t W = 12345678;
|
||||||
|
const int Q = 23;
|
||||||
|
const float result = fast_float::integer_times_pow10<float>(W, Q);
|
||||||
|
std::cout.precision(9);
|
||||||
|
std::cout << "float: " << W << " * 10^" << Q << " = " << result << " ("
|
||||||
|
<< (result == 12345678e23f ? "==" : "!=") << "expected)\n";
|
||||||
|
```
|
||||||
|
outputs
|
||||||
|
```
|
||||||
|
float: 12345678 * 10^23 = 1.23456782e+30 (==expected)
|
||||||
|
```
|
||||||
|
|
||||||
|
Overloads of `fast_float::integer_times_pow10()` are provided for
|
||||||
|
signed and unsigned integer types: `int64_t`, `uint64_t`, etc.
|
||||||
|
|
||||||
|
|
||||||
|
## Users and Related Work
|
||||||
|
|
||||||
The fast_float library is part of:
|
The fast_float library is part of:
|
||||||
|
|
||||||
- GCC (as of version 12): the `from_chars` function in GCC relies on fast_float.
|
* GCC (as of version 12): the `from_chars` function in GCC relies on fast_float,
|
||||||
- [WebKit](https://github.com/WebKit/WebKit), the engine behind Safari (Apple's web browser)
|
* [Chromium](https://github.com/Chromium/Chromium), the engine behind Google
|
||||||
|
Chrome, Microsoft Edge, and Opera,
|
||||||
|
* Boost JSON, MySQL, etc.
|
||||||
|
* Blender
|
||||||
|
* [WebKit](https://github.com/WebKit/WebKit), the engine behind Safari (Apple's
|
||||||
|
web browser),
|
||||||
|
* [DuckDB](https://duckdb.org),
|
||||||
|
* [Redis](https://github.com/redis/redis) and [Valkey](https://github.com/valkey-io/valkey),
|
||||||
|
* [Apache Arrow](https://github.com/apache/arrow/pull/8494) where it multiplied
|
||||||
|
the number parsing speed by two or three times,
|
||||||
|
* [Google Jsonnet](https://github.com/google/jsonnet),
|
||||||
|
* [ClickHouse](https://github.com/ClickHouse/ClickHouse).
|
||||||
|
|
||||||
|
The fastfloat algorithm is part of the [LLVM standard
|
||||||
|
libraries](https://github.com/llvm/llvm-project/commit/87c016078ad72c46505461e4ff8bfa04819fe7ba).
|
||||||
|
There is a [derived implementation part of
|
||||||
|
AdaCore](https://github.com/AdaCore/VSS). The [SerenityOS operating
|
||||||
|
system](https://github.com/SerenityOS/serenity/commit/53b7f5e6a11e663c83df8030c3171c5945cb75ec)
|
||||||
|
has a derived implementation that is inherited by the [Ladybird
|
||||||
|
Browser](https://github.com/LadybirdBrowser/ladybird).
|
||||||
|
|
||||||
|
The fast_float library provides a performance similar to that of the
|
||||||
|
[fast_double_parser](https://github.com/lemire/fast_double_parser) library but
|
||||||
|
using an updated algorithm reworked from the ground up, and while offering an
|
||||||
|
API more in line with the expectations of C++ programmers. The
|
||||||
|
fast_double_parser library is part of the [Microsoft LightGBM machine-learning
|
||||||
|
framework](https://github.com/microsoft/LightGBM).
|
||||||
|
|
||||||
|
|
||||||
The fastfloat algorithm is part of the [LLVM standard libraries](https://github.com/llvm/llvm-project/commit/87c016078ad72c46505461e4ff8bfa04819fe7ba).
|
|
||||||
|
|
||||||
There is a [derived implementation part of AdaCore](https://github.com/AdaCore/VSS).
|
Packages
|
||||||
|
------
|
||||||
|
|
||||||
|
[](https://repology.org/project/fast-float/versions)
|
||||||
|
|
||||||
The fast_float library provides a performance similar to that of the [fast_double_parser](https://github.com/lemire/fast_double_parser) library but using an updated algorithm reworked from the ground up, and while offering an API more in line with the expectations of C++ programmers. The fast_double_parser library is part of the [Microsoft LightGBM machine-learning framework](https://github.com/microsoft/LightGBM).
|
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- Daniel Lemire, [Number Parsing at a Gigabyte per Second](https://arxiv.org/abs/2101.11408), Software: Practice and Experience 51 (8), 2021.
|
* Daniel Lemire, [Number Parsing at a Gigabyte per
|
||||||
- Noble Mushtak, Daniel Lemire, [Fast Number Parsing Without Fallback](https://arxiv.org/abs/2212.06644), Software: Practice and Experience 53 (7), 2023.
|
Second](https://arxiv.org/abs/2101.11408), Software: Practice and Experience
|
||||||
|
51 (8), 2021.
|
||||||
|
* Noble Mushtak, Daniel Lemire, [Fast Number Parsing Without
|
||||||
|
Fallback](https://arxiv.org/abs/2212.06644), Software: Practice and Experience
|
||||||
|
53 (7), 2023.
|
||||||
|
|
||||||
## Other programming languages
|
## Other programming languages
|
||||||
|
|
||||||
- [There is an R binding](https://github.com/eddelbuettel/rcppfastfloat) called `rcppfastfloat`.
|
* [There is an R binding](https://github.com/eddelbuettel/rcppfastfloat) called
|
||||||
- [There is a Rust port of the fast_float library](https://github.com/aldanor/fast-float-rust/) called `fast-float-rust`.
|
`rcppfastfloat`.
|
||||||
- [There is a Java port of the fast_float library](https://github.com/wrandelshofer/FastDoubleParser) called `FastDoubleParser`. It used for important systems such as [Jackson](https://github.com/FasterXML/jackson-core).
|
* [There is a Rust port of the fast_float
|
||||||
- [There is a C# port of the fast_float library](https://github.com/CarlVerret/csFastFloat) called `csFastFloat`.
|
library](https://github.com/aldanor/fast-float-rust/) called
|
||||||
|
`fast-float-rust`.
|
||||||
|
* [There is a Java port of the fast_float
|
||||||
## Users
|
library](https://github.com/wrandelshofer/FastDoubleParser) called
|
||||||
|
`FastDoubleParser`. It used for important systems such as
|
||||||
The fast_float library is used by [Apache Arrow](https://github.com/apache/arrow/pull/8494) where it multiplied the number parsing speed by two or three times. It is also used by [Yandex ClickHouse](https://github.com/ClickHouse/ClickHouse) and by [Google Jsonnet](https://github.com/google/jsonnet).
|
[Jackson](https://github.com/FasterXML/jackson-core).
|
||||||
|
* [There is a C# port of the fast_float
|
||||||
|
library](https://github.com/CarlVerret/csFastFloat) called `csFastFloat`.
|
||||||
|
|
||||||
## How fast is it?
|
## How fast is it?
|
||||||
|
|
||||||
It can parse random floating-point numbers at a speed of 1 GB/s on some systems. We find that it is often twice as fast as the best available competitor, and many times faster than many standard-library implementations.
|
It can parse random floating-point numbers at a speed of 1 GB/s on some systems.
|
||||||
|
We find that it is often twice as fast as the best available competitor, and
|
||||||
|
many times faster than many standard-library implementations.
|
||||||
|
|
||||||
<img src="http://lemire.me/blog/wp-content/uploads/2020/11/fastfloat_speed.png" width="400">
|
<img src="https://lemire.me/blog/wp-content/uploads/2020/11/fastfloat_speed.png"
|
||||||
|
width="400" alt="fast_float is many times faster than many standard-library
|
||||||
|
implementations">
|
||||||
|
|
||||||
```
|
```bash
|
||||||
$ ./build/benchmarks/benchmark
|
$ ./build/benchmarks/benchmark
|
||||||
# parsing random integers in the range [0,1)
|
# parsing random integers in the range [0,1)
|
||||||
volume = 2.09808 MB
|
volume = 2.09808 MB
|
||||||
@ -325,71 +507,128 @@ abseil : 430.45 MB/s (+/- 2.2 %) 20.52 Mfl
|
|||||||
fastfloat : 1042.38 MB/s (+/- 9.9 %) 49.68 Mfloat/s
|
fastfloat : 1042.38 MB/s (+/- 9.9 %) 49.68 Mfloat/s
|
||||||
```
|
```
|
||||||
|
|
||||||
See https://github.com/lemire/simple_fastfloat_benchmark for our benchmarking code.
|
See the [Benchmarking](#benchmarking) section for instructions on how to run our benchmarks.
|
||||||
|
|
||||||
|
|
||||||
## Video
|
## Video
|
||||||
|
|
||||||
[](http://www.youtube.com/watch?v=AVXgvlMeIm4)<br />
|
[](https://www.youtube.com/watch?v=AVXgvlMeIm4)
|
||||||
|
|
||||||
## Using as a CMake dependency
|
## Using as a CMake dependency
|
||||||
|
|
||||||
This library is header-only by design. The CMake file provides the `fast_float` target
|
This library is header-only by design. The CMake file provides the `fast_float`
|
||||||
which is merely a pointer to the `include` directory.
|
target which is merely a pointer to the `include` directory.
|
||||||
|
|
||||||
If you drop the `fast_float` repository in your CMake project, you should be able to use
|
If you drop the `fast_float` repository in your CMake project, you should be
|
||||||
it in this manner:
|
able to use it in this manner:
|
||||||
|
|
||||||
```cmake
|
```cmake
|
||||||
add_subdirectory(fast_float)
|
add_subdirectory(fast_float)
|
||||||
target_link_libraries(myprogram PUBLIC fast_float)
|
target_link_libraries(myprogram PUBLIC fast_float)
|
||||||
```
|
```
|
||||||
|
|
||||||
Or you may want to retrieve the dependency automatically if you have a sufficiently recent version of CMake (3.11 or better at least):
|
Or you may want to retrieve the dependency automatically if you have a
|
||||||
|
sufficiently recent version of CMake (3.11 or better at least):
|
||||||
|
|
||||||
```cmake
|
```cmake
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
fast_float
|
fast_float
|
||||||
GIT_REPOSITORY https://github.com/lemire/fast_float.git
|
GIT_REPOSITORY https://github.com/fastfloat/fast_float.git
|
||||||
GIT_TAG tags/v1.1.2
|
GIT_TAG tags/v8.1.0
|
||||||
GIT_SHALLOW TRUE)
|
GIT_SHALLOW TRUE)
|
||||||
|
|
||||||
FetchContent_MakeAvailable(fast_float)
|
FetchContent_MakeAvailable(fast_float)
|
||||||
target_link_libraries(myprogram PUBLIC fast_float)
|
target_link_libraries(myprogram PUBLIC fast_float)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You should change the `GIT_TAG` line so that you recover the version you wish to use.
|
You should change the `GIT_TAG` line so that you recover the version you wish to
|
||||||
|
use.
|
||||||
|
|
||||||
|
You may also use [CPM](https://github.com/cpm-cmake/CPM.cmake), like so:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
CPMAddPackage(
|
||||||
|
NAME fast_float
|
||||||
|
GITHUB_REPOSITORY "fastfloat/fast_float"
|
||||||
|
GIT_TAG v8.1.0)
|
||||||
|
```
|
||||||
|
|
||||||
## Using as single header
|
## Using as single header
|
||||||
|
|
||||||
The script `script/amalgamate.py` may be used to generate a single header
|
The script `script/amalgamate.py` may be used to generate a single header
|
||||||
version of the library if so desired.
|
version of the library if so desired. Just run the script from the root
|
||||||
Just run the script from the root directory of this repository.
|
directory of this repository. You can customize the license type and output file
|
||||||
You can customize the license type and output file if desired as described in
|
if desired as described in the command line help.
|
||||||
the command line help.
|
|
||||||
|
|
||||||
You may directly download automatically generated single-header files:
|
You may directly download automatically generated single-header files:
|
||||||
|
|
||||||
https://github.com/fastfloat/fast_float/releases/download/v6.0.0/fast_float.h
|
<https://github.com/fastfloat/fast_float/releases/download/v8.1.0/fast_float.h>
|
||||||
|
|
||||||
|
## Benchmarking
|
||||||
|
|
||||||
|
The project has its own benchmarks with realistic data inputs. Under Linux or macOS,
|
||||||
|
you can use it as follows if your system supports C++17:
|
||||||
|
|
||||||
|
```
|
||||||
|
cmake -B build -D FASTFLOAT_BENCHMARKS=ON
|
||||||
|
cmake --build build
|
||||||
|
./build/benchmarks/realbenchmark
|
||||||
|
```
|
||||||
|
|
||||||
|
Importantly, by default, the benchmark is built in Release mode.
|
||||||
|
|
||||||
|
The instructions are similar under Windows.
|
||||||
|
|
||||||
|
Under Linux and macOS, it is recommended to run the benchmarks in a privileged manner to get access
|
||||||
|
to hardware performance counters. You may be able to do so with the `sudo` command
|
||||||
|
in some cases:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo ./build/benchmarks/realbenchmark
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have a text file containing one number per line (`myfile.txt`), you can run a benchmark over it like so:
|
||||||
|
```
|
||||||
|
cmake -B build -D FASTFLOAT_BENCHMARKS=ON
|
||||||
|
cmake --build build
|
||||||
|
./build/benchmarks/realbenchmark myfile.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Packages
|
||||||
|
|
||||||
|
* The fast_float library is part of the [Conan package
|
||||||
|
manager](https://conan.io/center/recipes/fast_float).
|
||||||
|
* It is part of the [brew package
|
||||||
|
manager](https://formulae.brew.sh/formula/fast_float).
|
||||||
|
* fast_float is available on [xmake](https://xmake.io) repository.
|
||||||
|
* Some Linux distribution like Fedora include fast_float (e.g., as
|
||||||
|
`fast_float-devel`).
|
||||||
|
|
||||||
## Credit
|
## Credit
|
||||||
|
|
||||||
Though this work is inspired by many different people, this work benefited especially from exchanges with
|
Though this work is inspired by many different people, this work benefited
|
||||||
Michael Eisel, who motivated the original research with his key insights, and with Nigel Tao who provided
|
especially from exchanges with Michael Eisel, who motivated the original
|
||||||
invaluable feedback. Rémy Oudompheng first implemented a fast path we use in the case of long digits.
|
research with his key insights, and with Nigel Tao who provided invaluable
|
||||||
|
feedback. Rémy Oudompheng first implemented a fast path we use in the case of
|
||||||
|
long digits.
|
||||||
|
|
||||||
The library includes code adapted from Google Wuffs (written by Nigel Tao) which was originally published
|
The library includes code adapted from Google Wuffs (written by Nigel Tao) which
|
||||||
under the Apache 2.0 license.
|
was originally published under the Apache 2.0 license.
|
||||||
|
|
||||||
|
## Stars
|
||||||
|
|
||||||
|
|
||||||
|
[](https://www.star-history.com/#fastfloat/fast_float&Date)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
<sup>
|
<sup>
|
||||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> or <a href="LICENSE-BOOST">BOOST license</a> .
|
2.0</a> or <a href="LICENSE-MIT">MIT license</a> or <a
|
||||||
|
href="LICENSE-BOOST">BOOST license</a>.
|
||||||
</sup>
|
</sup>
|
||||||
|
|
||||||
<br>
|
<br/>
|
||||||
|
|
||||||
<sub>
|
<sub>
|
||||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||||
|
|||||||
7
SECURITY.md
Normal file
7
SECURITY.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Please use the following contact information for reporting a vulnerability:
|
||||||
|
|
||||||
|
- [Daniel Lemire](https://github.com/lemire) - daniel@lemire.me
|
||||||
44
benchmarks/CMakeLists.txt
Normal file
44
benchmarks/CMakeLists.txt
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
counters
|
||||||
|
GIT_REPOSITORY https://github.com/lemire/counters.git
|
||||||
|
GIT_TAG v2.1.0
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(counters)
|
||||||
|
|
||||||
|
add_executable(realbenchmark benchmark.cpp)
|
||||||
|
target_link_libraries(realbenchmark PRIVATE counters::counters)
|
||||||
|
add_executable(bench_ip bench_ip.cpp)
|
||||||
|
target_link_libraries(bench_ip PRIVATE counters::counters)
|
||||||
|
|
||||||
|
set_property(
|
||||||
|
TARGET realbenchmark
|
||||||
|
PROPERTY CXX_STANDARD 17)
|
||||||
|
set_property(
|
||||||
|
TARGET bench_ip
|
||||||
|
PROPERTY CXX_STANDARD 17)
|
||||||
|
target_link_libraries(realbenchmark PUBLIC fast_float)
|
||||||
|
target_link_libraries(bench_ip PUBLIC fast_float)
|
||||||
|
|
||||||
|
include(ExternalProject)
|
||||||
|
|
||||||
|
# Define the external project
|
||||||
|
ExternalProject_Add(simple_fastfloat_benchmark
|
||||||
|
GIT_REPOSITORY https://github.com/lemire/simple_fastfloat_benchmark.git
|
||||||
|
GIT_TAG master # or specify a particular commit/tag/branch
|
||||||
|
SOURCE_DIR ${CMAKE_BINARY_DIR}/simple_fastfloat_benchmark
|
||||||
|
BINARY_DIR ${CMAKE_BINARY_DIR}/simple_fastfloat_benchmark-build
|
||||||
|
CONFIGURE_COMMAND ""
|
||||||
|
BUILD_COMMAND ""
|
||||||
|
INSTALL_COMMAND ""
|
||||||
|
)
|
||||||
|
set(DATA_DIR ${CMAKE_BINARY_DIR}/simple_fastfloat_benchmark/data)
|
||||||
|
|
||||||
|
add_custom_target(CopyData ALL
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory ${DATA_DIR} ${CMAKE_CURRENT_BINARY_DIR}/data
|
||||||
|
DEPENDS simple_fastfloat_benchmark
|
||||||
|
)
|
||||||
|
add_dependencies(realbenchmark CopyData)
|
||||||
|
target_compile_definitions(realbenchmark PUBLIC BENCHMARK_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data")
|
||||||
207
benchmarks/bench_ip.cpp
Normal file
207
benchmarks/bench_ip.cpp
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
#include "counters/bench.h"
|
||||||
|
#include "fast_float/fast_float.h"
|
||||||
|
#include <charconv>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <random>
|
||||||
|
#include <atomic>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
void pretty_print(size_t volume, size_t bytes, std::string name,
|
||||||
|
counters::event_aggregate agg) {
|
||||||
|
if (agg.inner_count > 1) {
|
||||||
|
printf("# (inner count: %d)\n", agg.inner_count);
|
||||||
|
}
|
||||||
|
printf("%-40s : ", name.c_str());
|
||||||
|
printf(" %5.2f GB/s ", bytes / agg.fastest_elapsed_ns());
|
||||||
|
printf(" %5.1f Mip/s ", volume * 1000.0 / agg.fastest_elapsed_ns());
|
||||||
|
printf(" %5.2f ns/ip ", agg.fastest_elapsed_ns() / volume);
|
||||||
|
if (counters::event_collector().has_events()) {
|
||||||
|
printf(" %5.2f GHz ", agg.fastest_cycles() / agg.fastest_elapsed_ns());
|
||||||
|
printf(" %5.2f c/ip ", agg.fastest_cycles() / volume);
|
||||||
|
printf(" %5.2f i/ip ", agg.fastest_instructions() / volume);
|
||||||
|
printf(" %5.2f c/b ", agg.fastest_cycles() / bytes);
|
||||||
|
printf(" %5.2f i/b ", agg.fastest_instructions() / bytes);
|
||||||
|
printf(" %5.2f i/c ", agg.fastest_instructions() / agg.fastest_cycles());
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
fastfloat_really_inline const char *seek_ip_end(const char *p,
|
||||||
|
const char *pend) {
|
||||||
|
const char *current = p;
|
||||||
|
size_t count = 0;
|
||||||
|
for (; current != pend; ++current) {
|
||||||
|
if (*current == '.') {
|
||||||
|
count++;
|
||||||
|
if (count == 3) {
|
||||||
|
++current;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (current != pend) {
|
||||||
|
if (*current <= '9' && *current >= '0') {
|
||||||
|
++current;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
fastfloat_really_inline int parse_u8_fastfloat(const char *&p, const char *pend,
|
||||||
|
uint8_t *out) {
|
||||||
|
if (p == pend)
|
||||||
|
return 0;
|
||||||
|
auto r = fast_float::from_chars(p, pend, *out);
|
||||||
|
if (r.ec == std::errc()) {
|
||||||
|
p = r.ptr;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fastfloat_really_inline int parse_u8_fromchars(const char *&p, const char *pend,
|
||||||
|
uint8_t *out) {
|
||||||
|
if (p == pend) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
auto r = std::from_chars(p, pend, *out);
|
||||||
|
if (r.ec == std::errc()) {
|
||||||
|
p = r.ptr;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Parser>
|
||||||
|
fastfloat_really_inline std::pair<bool, uint32_t>
|
||||||
|
simple_parse_ip_line(const char *p, const char *pend, Parser parse_uint8) {
|
||||||
|
uint8_t v1;
|
||||||
|
if (!parse_uint8(p, pend, &v1)) {
|
||||||
|
return {false, 0};
|
||||||
|
}
|
||||||
|
if (p == pend || *p++ != '.') {
|
||||||
|
return {false, 0};
|
||||||
|
}
|
||||||
|
uint8_t v2;
|
||||||
|
if (!parse_uint8(p, pend, &v2)) {
|
||||||
|
return {false, 0};
|
||||||
|
}
|
||||||
|
if (p == pend || *p++ != '.') {
|
||||||
|
return {false, 0};
|
||||||
|
}
|
||||||
|
uint8_t v3;
|
||||||
|
if (!parse_uint8(p, pend, &v3)) {
|
||||||
|
return {false, 0};
|
||||||
|
}
|
||||||
|
if (p == pend || *p++ != '.') {
|
||||||
|
return {false, 0};
|
||||||
|
}
|
||||||
|
uint8_t v4;
|
||||||
|
if (!parse_uint8(p, pend, &v4)) {
|
||||||
|
return {false, 0};
|
||||||
|
}
|
||||||
|
return {true, (uint32_t(v1) << 24) | (uint32_t(v2) << 16) |
|
||||||
|
(uint32_t(v3) << 8) | uint32_t(v4)};
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string make_ip_line(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
|
||||||
|
std::string s;
|
||||||
|
s.reserve(16);
|
||||||
|
s += std::to_string(a);
|
||||||
|
s += '.';
|
||||||
|
s += std::to_string(b);
|
||||||
|
s += '.';
|
||||||
|
s += std::to_string(c);
|
||||||
|
s += '.';
|
||||||
|
s += std::to_string(d);
|
||||||
|
s += '\n';
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
constexpr size_t N = 15000;
|
||||||
|
std::mt19937 rng(1234);
|
||||||
|
std::uniform_int_distribution<int> dist(0, 255);
|
||||||
|
|
||||||
|
std::string buf;
|
||||||
|
constexpr size_t ip_size = 16;
|
||||||
|
buf.reserve(N * ip_size);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < N; ++i) {
|
||||||
|
uint8_t a = (uint8_t)dist(rng);
|
||||||
|
uint8_t b = (uint8_t)dist(rng);
|
||||||
|
uint8_t c = (uint8_t)dist(rng);
|
||||||
|
uint8_t d = (uint8_t)dist(rng);
|
||||||
|
std::string ip_line = make_ip_line(a, b, c, d);
|
||||||
|
ip_line.resize(ip_size, ' '); // pad to fixed size
|
||||||
|
buf.append(ip_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sentinel to allow 4-byte loads at end
|
||||||
|
buf.append(4, '\0');
|
||||||
|
|
||||||
|
const size_t bytes = buf.size() - 4; // exclude sentinel from throughput
|
||||||
|
const size_t volume = N;
|
||||||
|
|
||||||
|
volatile uint32_t sink = 0;
|
||||||
|
std::string buffer(ip_size * N, ' ');
|
||||||
|
|
||||||
|
pretty_print(volume, bytes, "memcpy baseline", counters::bench([&]() {
|
||||||
|
std::memcpy((char *)buffer.data(), buf.data(), bytes);
|
||||||
|
}));
|
||||||
|
|
||||||
|
pretty_print(volume, bytes, "just_seek_ip_end (no parse)",
|
||||||
|
counters::bench([&]() {
|
||||||
|
const char *p = buf.data();
|
||||||
|
const char *pend = buf.data() + bytes;
|
||||||
|
uint32_t sum = 0;
|
||||||
|
int ok = 0;
|
||||||
|
for (size_t i = 0; i < N; ++i) {
|
||||||
|
const char *q = seek_ip_end(p, pend);
|
||||||
|
sum += (uint32_t)(q - p);
|
||||||
|
p += ip_size;
|
||||||
|
}
|
||||||
|
sink += sum;
|
||||||
|
}));
|
||||||
|
|
||||||
|
pretty_print(volume, bytes, "parse_ip_std_fromchars", counters::bench([&]() {
|
||||||
|
const char *p = buf.data();
|
||||||
|
const char *pend = buf.data() + bytes;
|
||||||
|
uint32_t sum = 0;
|
||||||
|
int ok = 0;
|
||||||
|
for (size_t i = 0; i < N; ++i) {
|
||||||
|
auto [ok, ip] =
|
||||||
|
simple_parse_ip_line(p, pend, parse_u8_fromchars);
|
||||||
|
sum += ip;
|
||||||
|
if (!ok) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
p += ip_size;
|
||||||
|
}
|
||||||
|
sink += sum;
|
||||||
|
}));
|
||||||
|
|
||||||
|
pretty_print(volume, bytes, "parse_ip_fastfloat", counters::bench([&]() {
|
||||||
|
const char *p = buf.data();
|
||||||
|
const char *pend = buf.data() + bytes;
|
||||||
|
uint32_t sum = 0;
|
||||||
|
int ok = 0;
|
||||||
|
for (size_t i = 0; i < N; ++i) {
|
||||||
|
auto [ok, ip] =
|
||||||
|
simple_parse_ip_line(p, pend, parse_u8_fastfloat);
|
||||||
|
sum += ip;
|
||||||
|
if (!ok) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
p += ip_size;
|
||||||
|
}
|
||||||
|
sink += sum;
|
||||||
|
}));
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
246
benchmarks/benchmark.cpp
Normal file
246
benchmarks/benchmark.cpp
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
#if defined(__linux__) || (__APPLE__ && __aarch64__)
|
||||||
|
#define USING_COUNTERS
|
||||||
|
#endif
|
||||||
|
#include "counters/event_counter.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include "fast_float/fast_float.h"
|
||||||
|
#include <chrono>
|
||||||
|
#include <climits>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <random>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <locale.h>
|
||||||
|
|
||||||
|
template <typename CharT>
|
||||||
|
double findmax_fastfloat64(std::vector<std::basic_string<CharT>> &s) {
|
||||||
|
double answer = 0;
|
||||||
|
double x = 0;
|
||||||
|
for (auto &st : s) {
|
||||||
|
auto [p, ec] = fast_float::from_chars(st.data(), st.data() + st.size(), x);
|
||||||
|
if (p == st.data()) {
|
||||||
|
throw std::runtime_error("bug in findmax_fastfloat");
|
||||||
|
}
|
||||||
|
answer = answer > x ? answer : x;
|
||||||
|
}
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename CharT>
|
||||||
|
double findmax_fastfloat32(std::vector<std::basic_string<CharT>> &s) {
|
||||||
|
float answer = 0;
|
||||||
|
float x = 0;
|
||||||
|
for (auto &st : s) {
|
||||||
|
auto [p, ec] = fast_float::from_chars(st.data(), st.data() + st.size(), x);
|
||||||
|
if (p == st.data()) {
|
||||||
|
throw std::runtime_error("bug in findmax_fastfloat");
|
||||||
|
}
|
||||||
|
answer = answer > x ? answer : x;
|
||||||
|
}
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
counters::event_collector collector{};
|
||||||
|
|
||||||
|
#ifdef USING_COUNTERS
|
||||||
|
template <class T, class CharT>
|
||||||
|
std::vector<counters::event_count>
|
||||||
|
time_it_ns(std::vector<std::basic_string<CharT>> &lines, T const &function,
|
||||||
|
size_t repeat) {
|
||||||
|
std::vector<counters::event_count> aggregate;
|
||||||
|
bool printed_bug = false;
|
||||||
|
for (size_t i = 0; i < repeat; i++) {
|
||||||
|
collector.start();
|
||||||
|
double ts = function(lines);
|
||||||
|
if (ts == 0 && !printed_bug) {
|
||||||
|
printf("bug\n");
|
||||||
|
printed_bug = true;
|
||||||
|
}
|
||||||
|
aggregate.push_back(collector.end());
|
||||||
|
}
|
||||||
|
return aggregate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pretty_print(double volume, size_t number_of_floats, std::string name,
|
||||||
|
std::vector<counters::event_count> events) {
|
||||||
|
double volumeMB = volume / (1024. * 1024.);
|
||||||
|
double average_ns{0};
|
||||||
|
double min_ns{DBL_MAX};
|
||||||
|
double cycles_min{DBL_MAX};
|
||||||
|
double instructions_min{DBL_MAX};
|
||||||
|
double cycles_avg{0};
|
||||||
|
double instructions_avg{0};
|
||||||
|
double branches_min{0};
|
||||||
|
double branches_avg{0};
|
||||||
|
double branch_misses_min{0};
|
||||||
|
double branch_misses_avg{0};
|
||||||
|
for (counters::event_count e : events) {
|
||||||
|
double ns = e.elapsed_ns();
|
||||||
|
average_ns += ns;
|
||||||
|
min_ns = min_ns < ns ? min_ns : ns;
|
||||||
|
|
||||||
|
double cycles = e.cycles();
|
||||||
|
cycles_avg += cycles;
|
||||||
|
cycles_min = cycles_min < cycles ? cycles_min : cycles;
|
||||||
|
|
||||||
|
double instructions = e.instructions();
|
||||||
|
instructions_avg += instructions;
|
||||||
|
instructions_min =
|
||||||
|
instructions_min < instructions ? instructions_min : instructions;
|
||||||
|
|
||||||
|
double branches = e.branches();
|
||||||
|
branches_avg += branches;
|
||||||
|
branches_min = branches_min < branches ? branches_min : branches;
|
||||||
|
|
||||||
|
double branch_misses = e.branch_misses();
|
||||||
|
branch_misses_avg += branch_misses;
|
||||||
|
branch_misses_min =
|
||||||
|
branch_misses_min < branch_misses ? branch_misses_min : branch_misses;
|
||||||
|
}
|
||||||
|
cycles_avg /= events.size();
|
||||||
|
instructions_avg /= events.size();
|
||||||
|
average_ns /= events.size();
|
||||||
|
branches_avg /= events.size();
|
||||||
|
printf("%-40s: %8.2f MB/s (+/- %.1f %%) ", name.data(),
|
||||||
|
volumeMB * 1000000000 / min_ns,
|
||||||
|
(average_ns - min_ns) * 100.0 / average_ns);
|
||||||
|
printf("%8.2f Mfloat/s ", number_of_floats * 1000 / min_ns);
|
||||||
|
if (instructions_min > 0) {
|
||||||
|
printf(" %8.2f i/B %8.2f i/f (+/- %.1f %%) ", instructions_min / volume,
|
||||||
|
instructions_min / number_of_floats,
|
||||||
|
(instructions_avg - instructions_min) * 100.0 / instructions_avg);
|
||||||
|
|
||||||
|
printf(" %8.2f c/B %8.2f c/f (+/- %.1f %%) ", cycles_min / volume,
|
||||||
|
cycles_min / number_of_floats,
|
||||||
|
(cycles_avg - cycles_min) * 100.0 / cycles_avg);
|
||||||
|
printf(" %8.2f i/c ", instructions_min / cycles_min);
|
||||||
|
printf(" %8.2f b/f ", branches_avg / number_of_floats);
|
||||||
|
printf(" %8.2f bm/f ", branch_misses_avg / number_of_floats);
|
||||||
|
printf(" %8.2f GHz ", cycles_min / min_ns);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
template <class T, class CharT>
|
||||||
|
std::pair<double, double>
|
||||||
|
time_it_ns(std::vector<std::basic_string<CharT>> &lines, T const &function,
|
||||||
|
size_t repeat) {
|
||||||
|
std::chrono::high_resolution_clock::time_point t1, t2;
|
||||||
|
double average = 0;
|
||||||
|
double min_value = DBL_MAX;
|
||||||
|
bool printed_bug = false;
|
||||||
|
for (size_t i = 0; i < repeat; i++) {
|
||||||
|
t1 = std::chrono::high_resolution_clock::now();
|
||||||
|
double ts = function(lines);
|
||||||
|
if (ts == 0 && !printed_bug) {
|
||||||
|
printf("bug\n");
|
||||||
|
printed_bug = true;
|
||||||
|
}
|
||||||
|
t2 = std::chrono::high_resolution_clock::now();
|
||||||
|
double dif =
|
||||||
|
std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count();
|
||||||
|
average += dif;
|
||||||
|
min_value = min_value < dif ? min_value : dif;
|
||||||
|
}
|
||||||
|
average /= repeat;
|
||||||
|
return std::make_pair(min_value, average);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pretty_print(double volume, size_t number_of_floats, std::string name,
|
||||||
|
std::pair<double, double> result) {
|
||||||
|
double volumeMB = volume / (1024. * 1024.);
|
||||||
|
printf("%-40s: %8.2f MB/s (+/- %.1f %%) ", name.data(),
|
||||||
|
volumeMB * 1000000000 / result.first,
|
||||||
|
(result.second - result.first) * 100.0 / result.second);
|
||||||
|
printf("%8.2f Mfloat/s ", number_of_floats * 1000 / result.first);
|
||||||
|
printf(" %8.2f ns/f \n", double(result.first) / number_of_floats);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// this is okay, all chars are ASCII
|
||||||
|
inline std::u16string widen(std::string line) {
|
||||||
|
std::u16string u16line;
|
||||||
|
u16line.resize(line.size());
|
||||||
|
for (size_t i = 0; i < line.size(); ++i) {
|
||||||
|
u16line[i] = char16_t(line[i]);
|
||||||
|
}
|
||||||
|
return u16line;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::u16string> widen(const std::vector<std::string> &lines) {
|
||||||
|
std::vector<std::u16string> u16lines;
|
||||||
|
u16lines.reserve(lines.size());
|
||||||
|
for (auto const &line : lines) {
|
||||||
|
u16lines.push_back(widen(line));
|
||||||
|
}
|
||||||
|
return u16lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
void process(std::vector<std::string> &lines, size_t volume) {
|
||||||
|
size_t repeat = 1000;
|
||||||
|
double volumeMB = volume / (1024. * 1024.);
|
||||||
|
std::cout << "ASCII volume = " << volumeMB << " MB " << std::endl;
|
||||||
|
pretty_print(volume, lines.size(), "fastfloat (64)",
|
||||||
|
time_it_ns(lines, findmax_fastfloat64<char>, repeat));
|
||||||
|
pretty_print(volume, lines.size(), "fastfloat (32)",
|
||||||
|
time_it_ns(lines, findmax_fastfloat32<char>, repeat));
|
||||||
|
|
||||||
|
std::vector<std::u16string> lines16 = widen(lines);
|
||||||
|
volume = 2 * volume;
|
||||||
|
volumeMB = volume / (1024. * 1024.);
|
||||||
|
std::cout << "UTF-16 volume = " << volumeMB << " MB " << std::endl;
|
||||||
|
pretty_print(volume, lines.size(), "fastfloat (64)",
|
||||||
|
time_it_ns(lines16, findmax_fastfloat64<char16_t>, repeat));
|
||||||
|
pretty_print(volume, lines.size(), "fastfloat (32)",
|
||||||
|
time_it_ns(lines16, findmax_fastfloat32<char16_t>, repeat));
|
||||||
|
}
|
||||||
|
|
||||||
|
void fileload(std::string filename) {
|
||||||
|
std::ifstream inputfile(filename);
|
||||||
|
if (!inputfile) {
|
||||||
|
std::cerr << "can't open " << filename << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::cout << "#### " << std::endl;
|
||||||
|
std::cout << "# reading " << filename << std::endl;
|
||||||
|
std::cout << "#### " << std::endl;
|
||||||
|
std::string line;
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
lines.reserve(10000); // let us reserve plenty of memory.
|
||||||
|
size_t volume = 0;
|
||||||
|
while (getline(inputfile, line)) {
|
||||||
|
volume += line.size();
|
||||||
|
lines.push_back(line);
|
||||||
|
}
|
||||||
|
std::cout << "# read " << lines.size() << " lines " << std::endl;
|
||||||
|
process(lines, volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
if (collector.has_events()) {
|
||||||
|
std::cout << "# Using hardware counters" << std::endl;
|
||||||
|
} else {
|
||||||
|
#if defined(__linux__) || (__APPLE__ && __aarch64__)
|
||||||
|
std::cout << "# Hardware counters not available, try to run in privileged "
|
||||||
|
"mode (e.g., sudo)."
|
||||||
|
<< std::endl;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (argc > 1) {
|
||||||
|
fileload(argv[1]);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
fileload(std::string(BENCHMARK_DATA_DIR) + "/canada.txt");
|
||||||
|
fileload(std::string(BENCHMARK_DATA_DIR) + "/mesh.txt");
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
0
clang-format-ignore.txt
Normal file
0
clang-format-ignore.txt
Normal file
4
cmake/toolchains-ci/riscv64-linux-gnu.cmake
Normal file
4
cmake/toolchains-ci/riscv64-linux-gnu.cmake
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
set(CMAKE_SYSTEM_NAME Linux)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR riscv64)
|
||||||
|
|
||||||
|
set(CMAKE_CROSSCOMPILING_EMULATOR "qemu-riscv64-static")
|
||||||
@ -5,4 +5,8 @@ $CXX $CFLAGS $CXXFLAGS \
|
|||||||
-c $SRC/fast_float/fuzz/from_chars.cc -o from_chars.o
|
-c $SRC/fast_float/fuzz/from_chars.cc -o from_chars.o
|
||||||
|
|
||||||
$CXX $CFLAGS $CXXFLAGS $LIB_FUZZING_ENGINE from_chars.o \
|
$CXX $CFLAGS $CXXFLAGS $LIB_FUZZING_ENGINE from_chars.o \
|
||||||
-o $OUT/from_chars
|
-o $OUT/from_chars
|
||||||
|
|
||||||
|
# Build unit tests
|
||||||
|
cmake -DFASTFLOAT_TEST=ON -DCMAKE_EXE_LINKER_FLAGS="-lpthread"
|
||||||
|
make
|
||||||
|
|||||||
@ -4,31 +4,35 @@
|
|||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
|
||||||
fast_float::chars_format arbitrary_format(FuzzedDataProvider &fdp) {
|
fast_float::chars_format arbitrary_format(FuzzedDataProvider &fdp) {
|
||||||
using fast_float::chars_format;
|
using fast_float::chars_format;
|
||||||
switch (fdp.ConsumeIntegralInRange<int>(0,3)) {
|
switch (fdp.ConsumeIntegralInRange<int>(0, 3)) {
|
||||||
case 0:
|
case 0:
|
||||||
return chars_format::scientific;
|
return chars_format::scientific;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
return chars_format::fixed;
|
return chars_format::fixed;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
return chars_format::fixed;
|
return chars_format::fixed;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return chars_format::general;
|
return chars_format::general;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
extern "C" int LLVMFuzzerTestOneInput(uint8_t const *data, size_t size) {
|
||||||
FuzzedDataProvider fdp(data, size);
|
FuzzedDataProvider fdp(data, size);
|
||||||
fast_float::chars_format format = arbitrary_format(fdp);
|
fast_float::chars_format format = arbitrary_format(fdp);
|
||||||
double result_d = 0.0;
|
double result_d = 0.0;
|
||||||
std::string input_d = fdp.ConsumeRandomLengthString(128);
|
std::string input_d = fdp.ConsumeRandomLengthString(128);
|
||||||
auto answer =
|
auto answer = fast_float::from_chars(
|
||||||
fast_float::from_chars(input_d.data(), input_d.data() + input_d.size(), result_d, format);
|
input_d.data(), input_d.data() + input_d.size(), result_d, format);
|
||||||
std::string input_f = fdp.ConsumeRandomLengthString(128);
|
std::string input_f = fdp.ConsumeRandomLengthString(128);
|
||||||
double result_f = 0.0;
|
float result_f = 0.0;
|
||||||
answer =
|
answer = fast_float::from_chars(
|
||||||
fast_float::from_chars(input_f.data(), input_f.data() + input_f.size(), result_f, format);
|
input_f.data(), input_f.data() + input_f.size(), result_f, format);
|
||||||
|
int result_i = 0;
|
||||||
|
std::string input_i = fdp.ConsumeRandomLengthString(128);
|
||||||
|
answer = fast_float::from_chars(input_i.data(),
|
||||||
|
input_i.data() + input_i.size(), result_i);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -20,8 +20,7 @@
|
|||||||
|
|
||||||
namespace fast_float {
|
namespace fast_float {
|
||||||
|
|
||||||
template <typename UC>
|
template <typename UC> fastfloat_really_inline constexpr bool has_simd_opt() {
|
||||||
fastfloat_really_inline constexpr bool has_simd_opt() {
|
|
||||||
#ifdef FASTFLOAT_HAS_SIMD
|
#ifdef FASTFLOAT_HAS_SIMD
|
||||||
return std::is_same<UC, char16_t>::value;
|
return std::is_same<UC, char16_t>::value;
|
||||||
#else
|
#else
|
||||||
@ -37,24 +36,20 @@ fastfloat_really_inline constexpr bool is_integer(UC c) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) {
|
fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) {
|
||||||
return (val & 0xFF00000000000000) >> 56
|
return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 |
|
||||||
| (val & 0x00FF000000000000) >> 40
|
(val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 |
|
||||||
| (val & 0x0000FF0000000000) >> 24
|
(val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 |
|
||||||
| (val & 0x000000FF00000000) >> 8
|
(val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56;
|
||||||
| (val & 0x00000000FF000000) << 8
|
|
||||||
| (val & 0x0000000000FF0000) << 24
|
|
||||||
| (val & 0x000000000000FF00) << 40
|
|
||||||
| (val & 0x00000000000000FF) << 56;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read 8 UC into a u64. Truncates UC if not char.
|
// Read 8 UC into a u64. Truncates UC if not char.
|
||||||
template <typename UC>
|
template <typename UC>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
|
||||||
uint64_t read8_to_u64(const UC *chars) {
|
read8_to_u64(UC const *chars) {
|
||||||
if (cpp20_and_in_constexpr() || !std::is_same<UC, char>::value) {
|
if (cpp20_and_in_constexpr() || !std::is_same<UC, char>::value) {
|
||||||
uint64_t val = 0;
|
uint64_t val = 0;
|
||||||
for(int i = 0; i < 8; ++i) {
|
for (int i = 0; i < 8; ++i) {
|
||||||
val |= uint64_t(uint8_t(*chars)) << (i*8);
|
val |= uint64_t(uint8_t(*chars)) << (i * 8);
|
||||||
++chars;
|
++chars;
|
||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
@ -70,44 +65,41 @@ uint64_t read8_to_u64(const UC *chars) {
|
|||||||
|
|
||||||
#ifdef FASTFLOAT_SSE2
|
#ifdef FASTFLOAT_SSE2
|
||||||
|
|
||||||
fastfloat_really_inline
|
fastfloat_really_inline uint64_t simd_read8_to_u64(__m128i const data) {
|
||||||
uint64_t simd_read8_to_u64(const __m128i data) {
|
FASTFLOAT_SIMD_DISABLE_WARNINGS
|
||||||
FASTFLOAT_SIMD_DISABLE_WARNINGS
|
__m128i const packed = _mm_packus_epi16(data, data);
|
||||||
const __m128i packed = _mm_packus_epi16(data, data);
|
|
||||||
#ifdef FASTFLOAT_64BIT
|
#ifdef FASTFLOAT_64BIT
|
||||||
return uint64_t(_mm_cvtsi128_si64(packed));
|
return uint64_t(_mm_cvtsi128_si64(packed));
|
||||||
#else
|
#else
|
||||||
uint64_t value;
|
uint64_t value;
|
||||||
// Visual Studio + older versions of GCC don't support _mm_storeu_si64
|
// Visual Studio + older versions of GCC don't support _mm_storeu_si64
|
||||||
_mm_storel_epi64(reinterpret_cast<__m128i*>(&value), packed);
|
_mm_storel_epi64(reinterpret_cast<__m128i *>(&value), packed);
|
||||||
return value;
|
return value;
|
||||||
#endif
|
#endif
|
||||||
FASTFLOAT_SIMD_RESTORE_WARNINGS
|
FASTFLOAT_SIMD_RESTORE_WARNINGS
|
||||||
}
|
}
|
||||||
|
|
||||||
fastfloat_really_inline
|
fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) {
|
||||||
uint64_t simd_read8_to_u64(const char16_t* chars) {
|
FASTFLOAT_SIMD_DISABLE_WARNINGS
|
||||||
FASTFLOAT_SIMD_DISABLE_WARNINGS
|
return simd_read8_to_u64(
|
||||||
return simd_read8_to_u64(_mm_loadu_si128(reinterpret_cast<const __m128i*>(chars)));
|
_mm_loadu_si128(reinterpret_cast<__m128i const *>(chars)));
|
||||||
FASTFLOAT_SIMD_RESTORE_WARNINGS
|
FASTFLOAT_SIMD_RESTORE_WARNINGS
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(FASTFLOAT_NEON)
|
#elif defined(FASTFLOAT_NEON)
|
||||||
|
|
||||||
|
fastfloat_really_inline uint64_t simd_read8_to_u64(uint16x8_t const data) {
|
||||||
fastfloat_really_inline
|
FASTFLOAT_SIMD_DISABLE_WARNINGS
|
||||||
uint64_t simd_read8_to_u64(const uint16x8_t data) {
|
|
||||||
FASTFLOAT_SIMD_DISABLE_WARNINGS
|
|
||||||
uint8x8_t utf8_packed = vmovn_u16(data);
|
uint8x8_t utf8_packed = vmovn_u16(data);
|
||||||
return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0);
|
return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0);
|
||||||
FASTFLOAT_SIMD_RESTORE_WARNINGS
|
FASTFLOAT_SIMD_RESTORE_WARNINGS
|
||||||
}
|
}
|
||||||
|
|
||||||
fastfloat_really_inline
|
fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) {
|
||||||
uint64_t simd_read8_to_u64(const char16_t* chars) {
|
FASTFLOAT_SIMD_DISABLE_WARNINGS
|
||||||
FASTFLOAT_SIMD_DISABLE_WARNINGS
|
return simd_read8_to_u64(
|
||||||
return simd_read8_to_u64(vld1q_u16(reinterpret_cast<const uint16_t*>(chars)));
|
vld1q_u16(reinterpret_cast<uint16_t const *>(chars)));
|
||||||
FASTFLOAT_SIMD_RESTORE_WARNINGS
|
FASTFLOAT_SIMD_RESTORE_WARNINGS
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // FASTFLOAT_SSE2
|
#endif // FASTFLOAT_SSE2
|
||||||
@ -119,101 +111,84 @@ template <typename UC>
|
|||||||
template <typename UC, FASTFLOAT_ENABLE_IF(!has_simd_opt<UC>()) = 0>
|
template <typename UC, FASTFLOAT_ENABLE_IF(!has_simd_opt<UC>()) = 0>
|
||||||
#endif
|
#endif
|
||||||
// dummy for compile
|
// dummy for compile
|
||||||
uint64_t simd_read8_to_u64(UC const*) {
|
uint64_t simd_read8_to_u64(UC const *) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
|
||||||
void write_u64(uint8_t *chars, uint64_t val) {
|
|
||||||
if (cpp20_and_in_constexpr()) {
|
|
||||||
for(int i = 0; i < 8; ++i) {
|
|
||||||
*chars = uint8_t(val);
|
|
||||||
val >>= 8;
|
|
||||||
++chars;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#if FASTFLOAT_IS_BIG_ENDIAN == 1
|
|
||||||
// Need to read as-if the number was in little-endian order.
|
|
||||||
val = byteswap(val);
|
|
||||||
#endif
|
|
||||||
::memcpy(chars, &val, sizeof(uint64_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
// credit @aqrit
|
// credit @aqrit
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR14
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t
|
||||||
uint32_t parse_eight_digits_unrolled(uint64_t val) {
|
parse_eight_digits_unrolled(uint64_t val) {
|
||||||
const uint64_t mask = 0x000000FF000000FF;
|
uint64_t const mask = 0x000000FF000000FF;
|
||||||
const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
|
uint64_t const mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
|
||||||
const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32)
|
uint64_t const mul2 = 0x0000271000000001; // 1 + (10000ULL << 32)
|
||||||
val -= 0x3030303030303030;
|
val -= 0x3030303030303030;
|
||||||
val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8;
|
val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8;
|
||||||
val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32;
|
val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32;
|
||||||
return uint32_t(val);
|
return uint32_t(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Call this if chars are definitely 8 digits.
|
// Call this if chars are definitely 8 digits.
|
||||||
template <typename UC>
|
template <typename UC>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t
|
||||||
uint32_t parse_eight_digits_unrolled(UC const * chars) noexcept {
|
parse_eight_digits_unrolled(UC const *chars) noexcept {
|
||||||
if (cpp20_and_in_constexpr() || !has_simd_opt<UC>()) {
|
if (cpp20_and_in_constexpr() || !has_simd_opt<UC>()) {
|
||||||
return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay
|
return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay
|
||||||
}
|
}
|
||||||
return parse_eight_digits_unrolled(simd_read8_to_u64(chars));
|
return parse_eight_digits_unrolled(simd_read8_to_u64(chars));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// credit @aqrit
|
// credit @aqrit
|
||||||
fastfloat_really_inline constexpr bool is_made_of_eight_digits_fast(uint64_t val) noexcept {
|
fastfloat_really_inline constexpr bool
|
||||||
|
is_made_of_eight_digits_fast(uint64_t val) noexcept {
|
||||||
return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) &
|
return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) &
|
||||||
0x8080808080808080));
|
0x8080808080808080));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef FASTFLOAT_HAS_SIMD
|
#ifdef FASTFLOAT_HAS_SIMD
|
||||||
|
|
||||||
// Call this if chars might not be 8 digits.
|
// Call this if chars might not be 8 digits.
|
||||||
// Using this style (instead of is_made_of_eight_digits_fast() then parse_eight_digits_unrolled())
|
// Using this style (instead of is_made_of_eight_digits_fast() then
|
||||||
// ensures we don't load SIMD registers twice.
|
// parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice.
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
|
||||||
bool simd_parse_if_eight_digits_unrolled(const char16_t* chars, uint64_t& i) noexcept {
|
simd_parse_if_eight_digits_unrolled(char16_t const *chars,
|
||||||
|
uint64_t &i) noexcept {
|
||||||
if (cpp20_and_in_constexpr()) {
|
if (cpp20_and_in_constexpr()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#ifdef FASTFLOAT_SSE2
|
#ifdef FASTFLOAT_SSE2
|
||||||
FASTFLOAT_SIMD_DISABLE_WARNINGS
|
FASTFLOAT_SIMD_DISABLE_WARNINGS
|
||||||
const __m128i data = _mm_loadu_si128(reinterpret_cast<const __m128i*>(chars));
|
__m128i const data =
|
||||||
|
_mm_loadu_si128(reinterpret_cast<__m128i const *>(chars));
|
||||||
|
|
||||||
// (x - '0') <= 9
|
// (x - '0') <= 9
|
||||||
// http://0x80.pl/articles/simd-parsing-int-sequences.html
|
// http://0x80.pl/articles/simd-parsing-int-sequences.html
|
||||||
const __m128i t0 = _mm_add_epi16(data, _mm_set1_epi16(32720));
|
__m128i const t0 = _mm_add_epi16(data, _mm_set1_epi16(32720));
|
||||||
const __m128i t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759));
|
__m128i const t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759));
|
||||||
|
|
||||||
if (_mm_movemask_epi8(t1) == 0) {
|
if (_mm_movemask_epi8(t1) == 0) {
|
||||||
i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data));
|
i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data));
|
||||||
return true;
|
return true;
|
||||||
}
|
} else
|
||||||
else return false;
|
return false;
|
||||||
FASTFLOAT_SIMD_RESTORE_WARNINGS
|
FASTFLOAT_SIMD_RESTORE_WARNINGS
|
||||||
#elif defined(FASTFLOAT_NEON)
|
#elif defined(FASTFLOAT_NEON)
|
||||||
FASTFLOAT_SIMD_DISABLE_WARNINGS
|
FASTFLOAT_SIMD_DISABLE_WARNINGS
|
||||||
const uint16x8_t data = vld1q_u16(reinterpret_cast<const uint16_t*>(chars));
|
uint16x8_t const data = vld1q_u16(reinterpret_cast<uint16_t const *>(chars));
|
||||||
|
|
||||||
// (x - '0') <= 9
|
// (x - '0') <= 9
|
||||||
// http://0x80.pl/articles/simd-parsing-int-sequences.html
|
// http://0x80.pl/articles/simd-parsing-int-sequences.html
|
||||||
const uint16x8_t t0 = vsubq_u16(data, vmovq_n_u16('0'));
|
uint16x8_t const t0 = vsubq_u16(data, vmovq_n_u16('0'));
|
||||||
const uint16x8_t mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1));
|
uint16x8_t const mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1));
|
||||||
|
|
||||||
if (vminvq_u16(mask) == 0xFFFF) {
|
if (vminvq_u16(mask) == 0xFFFF) {
|
||||||
i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data));
|
i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data));
|
||||||
return true;
|
return true;
|
||||||
}
|
} else
|
||||||
else return false;
|
return false;
|
||||||
FASTFLOAT_SIMD_RESTORE_WARNINGS
|
FASTFLOAT_SIMD_RESTORE_WARNINGS
|
||||||
#else
|
#else
|
||||||
(void)chars; (void)i;
|
(void)chars;
|
||||||
|
(void)i;
|
||||||
return false;
|
return false;
|
||||||
#endif // FASTFLOAT_SSE2
|
#endif // FASTFLOAT_SSE2
|
||||||
}
|
}
|
||||||
@ -227,79 +202,119 @@ template <typename UC>
|
|||||||
template <typename UC, FASTFLOAT_ENABLE_IF(!has_simd_opt<UC>()) = 0>
|
template <typename UC, FASTFLOAT_ENABLE_IF(!has_simd_opt<UC>()) = 0>
|
||||||
#endif
|
#endif
|
||||||
// dummy for compile
|
// dummy for compile
|
||||||
bool simd_parse_if_eight_digits_unrolled(UC const*, uint64_t&) {
|
bool simd_parse_if_eight_digits_unrolled(UC const *, uint64_t &) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template <typename UC, FASTFLOAT_ENABLE_IF(!std::is_same<UC, char>::value) = 0>
|
template <typename UC, FASTFLOAT_ENABLE_IF(!std::is_same<UC, char>::value) = 0>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
|
||||||
void loop_parse_if_eight_digits(const UC*& p, const UC* const pend, uint64_t& i) {
|
loop_parse_if_eight_digits(UC const *&p, UC const *const pend, uint64_t &i) {
|
||||||
if (!has_simd_opt<UC>()) {
|
if (!has_simd_opt<UC>()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
while ((std::distance(p, pend) >= 8) && simd_parse_if_eight_digits_unrolled(p, i)) { // in rare cases, this will overflow, but that's ok
|
while ((std::distance(p, pend) >= 8) &&
|
||||||
|
simd_parse_if_eight_digits_unrolled(
|
||||||
|
p, i)) { // in rare cases, this will overflow, but that's ok
|
||||||
p += 8;
|
p += 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
|
||||||
void loop_parse_if_eight_digits(const char*& p, const char* const pend, uint64_t& i) {
|
loop_parse_if_eight_digits(char const *&p, char const *const pend,
|
||||||
|
uint64_t &i) {
|
||||||
// optimizes better than parse_if_eight_digits_unrolled() for UC = char.
|
// optimizes better than parse_if_eight_digits_unrolled() for UC = char.
|
||||||
while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(read8_to_u64(p))) {
|
while ((std::distance(p, pend) >= 8) &&
|
||||||
i = i * 100000000 + parse_eight_digits_unrolled(read8_to_u64(p)); // in rare cases, this will overflow, but that's ok
|
is_made_of_eight_digits_fast(read8_to_u64(p))) {
|
||||||
|
i = i * 100000000 +
|
||||||
|
parse_eight_digits_unrolled(read8_to_u64(
|
||||||
|
p)); // in rare cases, this will overflow, but that's ok
|
||||||
p += 8;
|
p += 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename UC>
|
enum class parse_error {
|
||||||
struct parsed_number_string_t {
|
no_error,
|
||||||
|
// [JSON-only] The minus sign must be followed by an integer.
|
||||||
|
missing_integer_after_sign,
|
||||||
|
// A sign must be followed by an integer or dot.
|
||||||
|
missing_integer_or_dot_after_sign,
|
||||||
|
// [JSON-only] The integer part must not have leading zeros.
|
||||||
|
leading_zeros_in_integer_part,
|
||||||
|
// [JSON-only] The integer part must have at least one digit.
|
||||||
|
no_digits_in_integer_part,
|
||||||
|
// [JSON-only] If there is a decimal point, there must be digits in the
|
||||||
|
// fractional part.
|
||||||
|
no_digits_in_fractional_part,
|
||||||
|
// The mantissa must have at least one digit.
|
||||||
|
no_digits_in_mantissa,
|
||||||
|
// Scientific notation requires an exponential part.
|
||||||
|
missing_exponential_part,
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename UC> struct parsed_number_string_t {
|
||||||
int64_t exponent{0};
|
int64_t exponent{0};
|
||||||
uint64_t mantissa{0};
|
uint64_t mantissa{0};
|
||||||
UC const * lastmatch{nullptr};
|
UC const *lastmatch{nullptr};
|
||||||
bool negative{false};
|
bool negative{false};
|
||||||
bool valid{false};
|
bool valid{false};
|
||||||
bool too_many_digits{false};
|
bool too_many_digits{false};
|
||||||
// contains the range of the significant digits
|
// contains the range of the significant digits
|
||||||
span<const UC> integer{}; // non-nullable
|
span<UC const> integer{}; // non-nullable
|
||||||
span<const UC> fraction{}; // nullable
|
span<UC const> fraction{}; // nullable
|
||||||
|
parse_error error{parse_error::no_error};
|
||||||
};
|
};
|
||||||
|
|
||||||
using byte_span = span<const char>;
|
using byte_span = span<char const>;
|
||||||
using parsed_number_string = parsed_number_string_t<char>;
|
using parsed_number_string = parsed_number_string_t<char>;
|
||||||
|
|
||||||
|
template <typename UC>
|
||||||
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC>
|
||||||
|
report_parse_error(UC const *p, parse_error error) {
|
||||||
|
parsed_number_string_t<UC> answer;
|
||||||
|
answer.valid = false;
|
||||||
|
answer.lastmatch = p;
|
||||||
|
answer.error = error;
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
// Assuming that you use no more than 19 digits, this will
|
// Assuming that you use no more than 19 digits, this will
|
||||||
// parse an ASCII string.
|
// parse an ASCII string.
|
||||||
template <typename UC>
|
template <bool basic_json_fmt, typename UC>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC>
|
||||||
parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, parse_options_t<UC> options) noexcept {
|
parse_number_string(UC const *p, UC const *pend,
|
||||||
chars_format const fmt = options.format;
|
parse_options_t<UC> options) noexcept {
|
||||||
|
chars_format const fmt = detail::adjust_for_feature_macros(options.format);
|
||||||
UC const decimal_point = options.decimal_point;
|
UC const decimal_point = options.decimal_point;
|
||||||
|
|
||||||
parsed_number_string_t<UC> answer;
|
parsed_number_string_t<UC> answer;
|
||||||
answer.valid = false;
|
answer.valid = false;
|
||||||
answer.too_many_digits = false;
|
answer.too_many_digits = false;
|
||||||
|
// assume p < pend, so dereference without checks;
|
||||||
answer.negative = (*p == UC('-'));
|
answer.negative = (*p == UC('-'));
|
||||||
#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default
|
// C++17 20.19.3.(7.1) explicitly forbids '+' sign here
|
||||||
if ((*p == UC('-')) || (!(fmt & FASTFLOAT_JSONFMT) && *p == UC('+'))) {
|
if ((*p == UC('-')) || (uint64_t(fmt & chars_format::allow_leading_plus) &&
|
||||||
#else
|
!basic_json_fmt && *p == UC('+'))) {
|
||||||
if (*p == UC('-')) { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
|
|
||||||
#endif
|
|
||||||
++p;
|
++p;
|
||||||
if (p == pend) {
|
if (p == pend) {
|
||||||
return answer;
|
return report_parse_error<UC>(
|
||||||
|
p, parse_error::missing_integer_or_dot_after_sign);
|
||||||
}
|
}
|
||||||
if (fmt & FASTFLOAT_JSONFMT) {
|
FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) {
|
||||||
if (!is_integer(*p)) { // a sign must be followed by an integer
|
if (!is_integer(*p)) { // a sign must be followed by an integer
|
||||||
return answer;
|
return report_parse_error<UC>(p,
|
||||||
}
|
parse_error::missing_integer_after_sign);
|
||||||
} else {
|
}
|
||||||
if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot
|
}
|
||||||
return answer;
|
else {
|
||||||
|
if (!is_integer(*p) &&
|
||||||
|
(*p !=
|
||||||
|
decimal_point)) { // a sign must be followed by an integer or the dot
|
||||||
|
return report_parse_error<UC>(
|
||||||
|
p, parse_error::missing_integer_or_dot_after_sign);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UC const * const start_digits = p;
|
UC const *const start_digits = p;
|
||||||
|
|
||||||
uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad)
|
uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad)
|
||||||
|
|
||||||
@ -307,24 +322,29 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
|
|||||||
// a multiplication by 10 is cheaper than an arbitrary integer
|
// a multiplication by 10 is cheaper than an arbitrary integer
|
||||||
// multiplication
|
// multiplication
|
||||||
i = 10 * i +
|
i = 10 * i +
|
||||||
uint64_t(*p - UC('0')); // might overflow, we will handle the overflow later
|
uint64_t(*p -
|
||||||
|
UC('0')); // might overflow, we will handle the overflow later
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
UC const * const end_of_integer_part = p;
|
UC const *const end_of_integer_part = p;
|
||||||
int64_t digit_count = int64_t(end_of_integer_part - start_digits);
|
int64_t digit_count = int64_t(end_of_integer_part - start_digits);
|
||||||
answer.integer = span<const UC>(start_digits, size_t(digit_count));
|
answer.integer = span<UC const>(start_digits, size_t(digit_count));
|
||||||
if (fmt & FASTFLOAT_JSONFMT) {
|
FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) {
|
||||||
// at least 1 digit in integer part, without leading zeros
|
// at least 1 digit in integer part, without leading zeros
|
||||||
if (digit_count == 0 || (start_digits[0] == UC('0') && digit_count > 1)) {
|
if (digit_count == 0) {
|
||||||
return answer;
|
return report_parse_error<UC>(p, parse_error::no_digits_in_integer_part);
|
||||||
|
}
|
||||||
|
if ((start_digits[0] == UC('0') && digit_count > 1)) {
|
||||||
|
return report_parse_error<UC>(start_digits,
|
||||||
|
parse_error::leading_zeros_in_integer_part);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t exponent = 0;
|
int64_t exponent = 0;
|
||||||
const bool has_decimal_point = (p != pend) && (*p == decimal_point);
|
bool const has_decimal_point = (p != pend) && (*p == decimal_point);
|
||||||
if (has_decimal_point) {
|
if (has_decimal_point) {
|
||||||
++p;
|
++p;
|
||||||
UC const * before = p;
|
UC const *before = p;
|
||||||
// can occur at most twice without overflowing, but let it occur more, since
|
// can occur at most twice without overflowing, but let it occur more, since
|
||||||
// for integers with many digits, digit parsing is the primary bottleneck.
|
// for integers with many digits, digit parsing is the primary bottleneck.
|
||||||
loop_parse_if_eight_digits(p, pend, i);
|
loop_parse_if_eight_digits(p, pend, i);
|
||||||
@ -335,41 +355,45 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
|
|||||||
i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
|
i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
|
||||||
}
|
}
|
||||||
exponent = before - p;
|
exponent = before - p;
|
||||||
answer.fraction = span<const UC>(before, size_t(p - before));
|
answer.fraction = span<UC const>(before, size_t(p - before));
|
||||||
digit_count -= exponent;
|
digit_count -= exponent;
|
||||||
}
|
}
|
||||||
if (fmt & FASTFLOAT_JSONFMT) {
|
FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) {
|
||||||
// at least 1 digit in fractional part
|
// at least 1 digit in fractional part
|
||||||
if (has_decimal_point && exponent == 0) {
|
if (has_decimal_point && exponent == 0) {
|
||||||
return answer;
|
return report_parse_error<UC>(p,
|
||||||
|
parse_error::no_digits_in_fractional_part);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (digit_count == 0) { // we must have encountered at least one integer!
|
|
||||||
return answer;
|
|
||||||
}
|
}
|
||||||
int64_t exp_number = 0; // explicit exponential part
|
else if (digit_count == 0) { // we must have encountered at least one integer!
|
||||||
if ( ((fmt & chars_format::scientific) &&
|
return report_parse_error<UC>(p, parse_error::no_digits_in_mantissa);
|
||||||
(p != pend) &&
|
}
|
||||||
((UC('e') == *p) || (UC('E') == *p)))
|
int64_t exp_number = 0; // explicit exponential part
|
||||||
||
|
if ((uint64_t(fmt & chars_format::scientific) && (p != pend) &&
|
||||||
((fmt & FASTFLOAT_FORTRANFMT) &&
|
((UC('e') == *p) || (UC('E') == *p))) ||
|
||||||
(p != pend) &&
|
(uint64_t(fmt & detail::basic_fortran_fmt) && (p != pend) &&
|
||||||
((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || (UC('D') == *p)))) {
|
((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) ||
|
||||||
UC const * location_of_e = p;
|
(UC('D') == *p)))) {
|
||||||
if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || (UC('D') == *p)) {
|
UC const *location_of_e = p;
|
||||||
|
if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) ||
|
||||||
|
(UC('D') == *p)) {
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
bool neg_exp = false;
|
bool neg_exp = false;
|
||||||
if ((p != pend) && (UC('-') == *p)) {
|
if ((p != pend) && (UC('-') == *p)) {
|
||||||
neg_exp = true;
|
neg_exp = true;
|
||||||
++p;
|
++p;
|
||||||
} else if ((p != pend) && (UC('+') == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1)
|
} else if ((p != pend) &&
|
||||||
|
(UC('+') ==
|
||||||
|
*p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1)
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
if ((p == pend) || !is_integer(*p)) {
|
if ((p == pend) || !is_integer(*p)) {
|
||||||
if(!(fmt & chars_format::fixed)) {
|
if (!uint64_t(fmt & chars_format::fixed)) {
|
||||||
// We are in error.
|
// The exponential part is invalid for scientific notation, so it must
|
||||||
return answer;
|
// be a trailing token for fixed notation. However, fixed notation is
|
||||||
|
// disabled, so report a scientific notation error.
|
||||||
|
return report_parse_error<UC>(p, parse_error::missing_exponential_part);
|
||||||
}
|
}
|
||||||
// Otherwise, we will be ignoring the 'e'.
|
// Otherwise, we will be ignoring the 'e'.
|
||||||
p = location_of_e;
|
p = location_of_e;
|
||||||
@ -381,12 +405,17 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
|
|||||||
}
|
}
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
if(neg_exp) { exp_number = - exp_number; }
|
if (neg_exp) {
|
||||||
|
exp_number = -exp_number;
|
||||||
|
}
|
||||||
exponent += exp_number;
|
exponent += exp_number;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If it scientific and not fixed, we have to bail out.
|
// If it scientific and not fixed, we have to bail out.
|
||||||
if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; }
|
if (uint64_t(fmt & chars_format::scientific) &&
|
||||||
|
!uint64_t(fmt & chars_format::fixed)) {
|
||||||
|
return report_parse_error<UC>(p, parse_error::missing_exponential_part);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
answer.lastmatch = p;
|
answer.lastmatch = p;
|
||||||
answer.valid = true;
|
answer.valid = true;
|
||||||
@ -401,31 +430,32 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
|
|||||||
// We have to handle the case where we have 0.0000somenumber.
|
// We have to handle the case where we have 0.0000somenumber.
|
||||||
// We need to be mindful of the case where we only have zeroes...
|
// We need to be mindful of the case where we only have zeroes...
|
||||||
// E.g., 0.000000000...000.
|
// E.g., 0.000000000...000.
|
||||||
UC const * start = start_digits;
|
UC const *start = start_digits;
|
||||||
while ((start != pend) && (*start == UC('0') || *start == decimal_point)) {
|
while ((start != pend) && (*start == UC('0') || *start == decimal_point)) {
|
||||||
if(*start == UC('0')) { digit_count --; }
|
if (*start == UC('0')) {
|
||||||
|
digit_count--;
|
||||||
|
}
|
||||||
start++;
|
start++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (digit_count > 19) {
|
if (digit_count > 19) {
|
||||||
answer.too_many_digits = true;
|
answer.too_many_digits = true;
|
||||||
// Let us start again, this time, avoiding overflows.
|
// Let us start again, this time, avoiding overflows.
|
||||||
// We don't need to check if is_integer, since we use the
|
// We don't need to call if is_integer, since we use the
|
||||||
// pre-tokenized spans from above.
|
// pre-tokenized spans from above.
|
||||||
i = 0;
|
i = 0;
|
||||||
p = answer.integer.ptr;
|
p = answer.integer.ptr;
|
||||||
UC const* int_end = p + answer.integer.len();
|
UC const *int_end = p + answer.integer.len();
|
||||||
const uint64_t minimal_nineteen_digit_integer{ 1000000000000000000 };
|
uint64_t const minimal_nineteen_digit_integer{1000000000000000000};
|
||||||
while ((i < minimal_nineteen_digit_integer) && (p != int_end)) {
|
while ((i < minimal_nineteen_digit_integer) && (p != int_end)) {
|
||||||
i = i * 10 + uint64_t(*p - UC('0'));
|
i = i * 10 + uint64_t(*p - UC('0'));
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
if (i >= minimal_nineteen_digit_integer) { // We have a big integers
|
if (i >= minimal_nineteen_digit_integer) { // We have a big integer
|
||||||
exponent = end_of_integer_part - p + exp_number;
|
exponent = end_of_integer_part - p + exp_number;
|
||||||
}
|
} else { // We have a value with a fractional component.
|
||||||
else { // We have a value with a fractional component.
|
|
||||||
p = answer.fraction.ptr;
|
p = answer.fraction.ptr;
|
||||||
UC const* frac_end = p + answer.fraction.len();
|
UC const *frac_end = p + answer.fraction.len();
|
||||||
while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
|
while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
|
||||||
i = i * 10 + uint64_t(*p - UC('0'));
|
i = i * 10 + uint64_t(*p - UC('0'));
|
||||||
++p;
|
++p;
|
||||||
@ -441,34 +471,43 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, typename UC>
|
template <typename T, typename UC>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
|
||||||
from_chars_result_t<UC> parse_int_string(UC const* p, UC const* pend, T& value, int base)
|
parse_int_string(UC const *p, UC const *pend, T &value,
|
||||||
{
|
parse_options_t<UC> options) {
|
||||||
from_chars_result_t<UC> answer;
|
chars_format const fmt = detail::adjust_for_feature_macros(options.format);
|
||||||
|
int const base = options.base;
|
||||||
UC const* const first = p;
|
|
||||||
|
|
||||||
bool negative = (*p == UC('-'));
|
from_chars_result_t<UC> answer;
|
||||||
|
|
||||||
|
UC const *const first = p;
|
||||||
|
|
||||||
|
bool const negative = (*p == UC('-'));
|
||||||
|
#ifdef FASTFLOAT_VISUAL_STUDIO
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable : 4127)
|
||||||
|
#endif
|
||||||
if (!std::is_signed<T>::value && negative) {
|
if (!std::is_signed<T>::value && negative) {
|
||||||
|
#ifdef FASTFLOAT_VISUAL_STUDIO
|
||||||
|
#pragma warning(pop)
|
||||||
|
#endif
|
||||||
answer.ec = std::errc::invalid_argument;
|
answer.ec = std::errc::invalid_argument;
|
||||||
answer.ptr = first;
|
answer.ptr = first;
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default
|
if ((*p == UC('-')) ||
|
||||||
if ((*p == UC('-')) || (*p == UC('+'))) {
|
(uint64_t(fmt & chars_format::allow_leading_plus) && (*p == UC('+')))) {
|
||||||
#else
|
|
||||||
if (*p == UC('-')) {
|
|
||||||
#endif
|
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
|
|
||||||
UC const* const start_num = p;
|
UC const *const start_num = p;
|
||||||
while (*p == UC('0')) {
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
const bool has_leading_zeros = p > start_num;
|
|
||||||
|
|
||||||
UC const* const start_digits = p;
|
while (p != pend && *p == UC('0')) {
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool const has_leading_zeros = p > start_num;
|
||||||
|
|
||||||
|
UC const *const start_digits = p;
|
||||||
|
|
||||||
uint64_t i = 0;
|
uint64_t i = 0;
|
||||||
if (base == 10) {
|
if (base == 10) {
|
||||||
@ -480,9 +519,9 @@ from_chars_result_t<UC> parse_int_string(UC const* p, UC const* pend, T& value,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
i = uint64_t(base) * i + digit; // might overflow, check this later
|
i = uint64_t(base) * i + digit; // might overflow, check this later
|
||||||
p++;
|
p++;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t digit_count = size_t(p - start_digits);
|
size_t digit_count = size_t(p - start_digits);
|
||||||
|
|
||||||
if (digit_count == 0) {
|
if (digit_count == 0) {
|
||||||
@ -490,12 +529,11 @@ from_chars_result_t<UC> parse_int_string(UC const* p, UC const* pend, T& value,
|
|||||||
value = 0;
|
value = 0;
|
||||||
answer.ec = std::errc();
|
answer.ec = std::errc();
|
||||||
answer.ptr = p;
|
answer.ptr = p;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
answer.ec = std::errc::invalid_argument;
|
answer.ec = std::errc::invalid_argument;
|
||||||
answer.ptr = first;
|
answer.ptr = first;
|
||||||
}
|
}
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
answer.ptr = p;
|
answer.ptr = p;
|
||||||
@ -506,7 +544,8 @@ from_chars_result_t<UC> parse_int_string(UC const* p, UC const* pend, T& value,
|
|||||||
answer.ec = std::errc::result_out_of_range;
|
answer.ec = std::errc::result_out_of_range;
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
// this check can be eliminated for all other types, but they will all require a max_digits(base) equivalent
|
// this check can be eliminated for all other types, but they will all require
|
||||||
|
// a max_digits(base) equivalent
|
||||||
if (digit_count == max_digits && i < min_safe_u64(base)) {
|
if (digit_count == max_digits && i < min_safe_u64(base)) {
|
||||||
answer.ec = std::errc::result_out_of_range;
|
answer.ec = std::errc::result_out_of_range;
|
||||||
return answer;
|
return answer;
|
||||||
@ -523,18 +562,22 @@ from_chars_result_t<UC> parse_int_string(UC const* p, UC const* pend, T& value,
|
|||||||
if (negative) {
|
if (negative) {
|
||||||
#ifdef FASTFLOAT_VISUAL_STUDIO
|
#ifdef FASTFLOAT_VISUAL_STUDIO
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
#pragma warning(disable: 4146)
|
#pragma warning(disable : 4146)
|
||||||
#endif
|
#endif
|
||||||
// this weird workaround is required because:
|
// this weird workaround is required because:
|
||||||
// - converting unsigned to signed when its value is greater than signed max is UB pre-C++23.
|
// - converting unsigned to signed when its value is greater than signed max
|
||||||
|
// is UB pre-C++23.
|
||||||
// - reinterpret_casting (~i + 1) would work, but it is not constexpr
|
// - reinterpret_casting (~i + 1) would work, but it is not constexpr
|
||||||
// this is always optimized into a neg instruction.
|
// this is always optimized into a neg instruction (note: T is an integer
|
||||||
value = T(-std::numeric_limits<T>::max() - T(i - std::numeric_limits<T>::max()));
|
// type)
|
||||||
|
value = T(-std::numeric_limits<T>::max() -
|
||||||
|
T(i - uint64_t(std::numeric_limits<T>::max())));
|
||||||
#ifdef FASTFLOAT_VISUAL_STUDIO
|
#ifdef FASTFLOAT_VISUAL_STUDIO
|
||||||
#pragma warning(pop)
|
#pragma warning(pop)
|
||||||
#endif
|
#endif
|
||||||
|
} else {
|
||||||
|
value = T(i);
|
||||||
}
|
}
|
||||||
else { value = T(i); }
|
|
||||||
|
|
||||||
answer.ec = std::errc();
|
answer.ec = std::errc();
|
||||||
return answer;
|
return answer;
|
||||||
|
|||||||
@ -37,15 +37,14 @@ constexpr size_t bigint_limbs = bigint_bits / limb_bits;
|
|||||||
|
|
||||||
// vector-like type that is allocated on the stack. the entire
|
// vector-like type that is allocated on the stack. the entire
|
||||||
// buffer is pre-allocated, and only the length changes.
|
// buffer is pre-allocated, and only the length changes.
|
||||||
template <uint16_t size>
|
template <uint16_t size> struct stackvec {
|
||||||
struct stackvec {
|
|
||||||
limb data[size];
|
limb data[size];
|
||||||
// we never need more than 150 limbs
|
// we never need more than 150 limbs
|
||||||
uint16_t length{0};
|
uint16_t length{0};
|
||||||
|
|
||||||
stackvec() = default;
|
stackvec() = default;
|
||||||
stackvec(const stackvec &) = delete;
|
stackvec(stackvec const &) = delete;
|
||||||
stackvec &operator=(const stackvec &) = delete;
|
stackvec &operator=(stackvec const &) = delete;
|
||||||
stackvec(stackvec &&) = delete;
|
stackvec(stackvec &&) = delete;
|
||||||
stackvec &operator=(stackvec &&other) = delete;
|
stackvec &operator=(stackvec &&other) = delete;
|
||||||
|
|
||||||
@ -54,16 +53,18 @@ struct stackvec {
|
|||||||
FASTFLOAT_ASSERT(try_extend(s));
|
FASTFLOAT_ASSERT(try_extend(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
FASTFLOAT_CONSTEXPR14 limb& operator[](size_t index) noexcept {
|
FASTFLOAT_CONSTEXPR14 limb &operator[](size_t index) noexcept {
|
||||||
FASTFLOAT_DEBUG_ASSERT(index < length);
|
FASTFLOAT_DEBUG_ASSERT(index < length);
|
||||||
return data[index];
|
return data[index];
|
||||||
}
|
}
|
||||||
FASTFLOAT_CONSTEXPR14 const limb& operator[](size_t index) const noexcept {
|
|
||||||
|
FASTFLOAT_CONSTEXPR14 const limb &operator[](size_t index) const noexcept {
|
||||||
FASTFLOAT_DEBUG_ASSERT(index < length);
|
FASTFLOAT_DEBUG_ASSERT(index < length);
|
||||||
return data[index];
|
return data[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
// index from the end of the container
|
// index from the end of the container
|
||||||
FASTFLOAT_CONSTEXPR14 const limb& rindex(size_t index) const noexcept {
|
FASTFLOAT_CONSTEXPR14 const limb &rindex(size_t index) const noexcept {
|
||||||
FASTFLOAT_DEBUG_ASSERT(index < length);
|
FASTFLOAT_DEBUG_ASSERT(index < length);
|
||||||
size_t rindex = length - index - 1;
|
size_t rindex = length - index - 1;
|
||||||
return data[rindex];
|
return data[rindex];
|
||||||
@ -73,20 +74,19 @@ struct stackvec {
|
|||||||
FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept {
|
FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept {
|
||||||
length = uint16_t(len);
|
length = uint16_t(len);
|
||||||
}
|
}
|
||||||
constexpr size_t len() const noexcept {
|
|
||||||
return length;
|
constexpr size_t len() const noexcept { return length; }
|
||||||
}
|
|
||||||
constexpr bool is_empty() const noexcept {
|
constexpr bool is_empty() const noexcept { return length == 0; }
|
||||||
return length == 0;
|
|
||||||
}
|
constexpr size_t capacity() const noexcept { return size; }
|
||||||
constexpr size_t capacity() const noexcept {
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
// append item to vector, without bounds checking
|
// append item to vector, without bounds checking
|
||||||
FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept {
|
FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept {
|
||||||
data[length] = value;
|
data[length] = value;
|
||||||
length++;
|
length++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// append item to vector, returning if item was added
|
// append item to vector, returning if item was added
|
||||||
FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept {
|
FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept {
|
||||||
if (len() < capacity()) {
|
if (len() < capacity()) {
|
||||||
@ -96,12 +96,14 @@ struct stackvec {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add items to the vector, from a span, without bounds checking
|
// add items to the vector, from a span, without bounds checking
|
||||||
FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept {
|
FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept {
|
||||||
limb* ptr = data + length;
|
limb *ptr = data + length;
|
||||||
std::copy_n(s.ptr, s.len(), ptr);
|
std::copy_n(s.ptr, s.len(), ptr);
|
||||||
set_len(len() + s.len());
|
set_len(len() + s.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to add items to the vector, returning if items were added
|
// try to add items to the vector, returning if items were added
|
||||||
FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept {
|
FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept {
|
||||||
if (len() + s.len() <= capacity()) {
|
if (len() + s.len() <= capacity()) {
|
||||||
@ -111,6 +113,7 @@ struct stackvec {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// resize the vector, without bounds checking
|
// resize the vector, without bounds checking
|
||||||
// if the new size is longer than the vector, assign value to each
|
// if the new size is longer than the vector, assign value to each
|
||||||
// appended item.
|
// appended item.
|
||||||
@ -118,14 +121,15 @@ struct stackvec {
|
|||||||
void resize_unchecked(size_t new_len, limb value) noexcept {
|
void resize_unchecked(size_t new_len, limb value) noexcept {
|
||||||
if (new_len > len()) {
|
if (new_len > len()) {
|
||||||
size_t count = new_len - len();
|
size_t count = new_len - len();
|
||||||
limb* first = data + len();
|
limb *first = data + len();
|
||||||
limb* last = first + count;
|
limb *last = first + count;
|
||||||
::std::fill(first, last, value);
|
::std::fill(first, last, value);
|
||||||
set_len(new_len);
|
set_len(new_len);
|
||||||
} else {
|
} else {
|
||||||
set_len(new_len);
|
set_len(new_len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to resize the vector, returning if the vector was resized.
|
// try to resize the vector, returning if the vector was resized.
|
||||||
FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept {
|
FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept {
|
||||||
if (new_len > capacity()) {
|
if (new_len > capacity()) {
|
||||||
@ -135,6 +139,7 @@ struct stackvec {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if any limbs are non-zero after the given index.
|
// check if any limbs are non-zero after the given index.
|
||||||
// this needs to be done in reverse order, since the index
|
// this needs to be done in reverse order, since the index
|
||||||
// is relative to the most significant limbs.
|
// is relative to the most significant limbs.
|
||||||
@ -147,6 +152,7 @@ struct stackvec {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalize the big integer, so most-significant zero limbs are removed.
|
// normalize the big integer, so most-significant zero limbs are removed.
|
||||||
FASTFLOAT_CONSTEXPR14 void normalize() noexcept {
|
FASTFLOAT_CONSTEXPR14 void normalize() noexcept {
|
||||||
while (len() > 0 && rindex(0) == 0) {
|
while (len() > 0 && rindex(0) == 0) {
|
||||||
@ -155,21 +161,21 @@ struct stackvec {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR14
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t
|
||||||
uint64_t empty_hi64(bool& truncated) noexcept {
|
empty_hi64(bool &truncated) noexcept {
|
||||||
truncated = false;
|
truncated = false;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
|
||||||
uint64_t uint64_hi64(uint64_t r0, bool& truncated) noexcept {
|
uint64_hi64(uint64_t r0, bool &truncated) noexcept {
|
||||||
truncated = false;
|
truncated = false;
|
||||||
int shl = leading_zeroes(r0);
|
int shl = leading_zeroes(r0);
|
||||||
return r0 << shl;
|
return r0 << shl;
|
||||||
}
|
}
|
||||||
|
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
|
||||||
uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept {
|
uint64_hi64(uint64_t r0, uint64_t r1, bool &truncated) noexcept {
|
||||||
int shl = leading_zeroes(r0);
|
int shl = leading_zeroes(r0);
|
||||||
if (shl == 0) {
|
if (shl == 0) {
|
||||||
truncated = r1 != 0;
|
truncated = r1 != 0;
|
||||||
@ -181,20 +187,20 @@ uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
|
||||||
uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept {
|
uint32_hi64(uint32_t r0, bool &truncated) noexcept {
|
||||||
return uint64_hi64(r0, truncated);
|
return uint64_hi64(r0, truncated);
|
||||||
}
|
}
|
||||||
|
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
|
||||||
uint64_t uint32_hi64(uint32_t r0, uint32_t r1, bool& truncated) noexcept {
|
uint32_hi64(uint32_t r0, uint32_t r1, bool &truncated) noexcept {
|
||||||
uint64_t x0 = r0;
|
uint64_t x0 = r0;
|
||||||
uint64_t x1 = r1;
|
uint64_t x1 = r1;
|
||||||
return uint64_hi64((x0 << 32) | x1, truncated);
|
return uint64_hi64((x0 << 32) | x1, truncated);
|
||||||
}
|
}
|
||||||
|
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t
|
||||||
uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool& truncated) noexcept {
|
uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool &truncated) noexcept {
|
||||||
uint64_t x0 = r0;
|
uint64_t x0 = r0;
|
||||||
uint64_t x1 = r1;
|
uint64_t x1 = r1;
|
||||||
uint64_t x2 = r2;
|
uint64_t x2 = r2;
|
||||||
@ -205,17 +211,17 @@ uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool& truncated) noe
|
|||||||
// we want an efficient operation. for msvc, where
|
// we want an efficient operation. for msvc, where
|
||||||
// we don't have built-in intrinsics, this is still
|
// we don't have built-in intrinsics, this is still
|
||||||
// pretty fast.
|
// pretty fast.
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb
|
||||||
limb scalar_add(limb x, limb y, bool& overflow) noexcept {
|
scalar_add(limb x, limb y, bool &overflow) noexcept {
|
||||||
limb z;
|
limb z;
|
||||||
// gcc and clang
|
// gcc and clang
|
||||||
#if defined(__has_builtin)
|
#if defined(__has_builtin)
|
||||||
#if __has_builtin(__builtin_add_overflow)
|
#if __has_builtin(__builtin_add_overflow)
|
||||||
if (!cpp20_and_in_constexpr()) {
|
if (!cpp20_and_in_constexpr()) {
|
||||||
overflow = __builtin_add_overflow(x, y, &z);
|
overflow = __builtin_add_overflow(x, y, &z);
|
||||||
return z;
|
return z;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// generic, this still optimizes correctly on MSVC.
|
// generic, this still optimizes correctly on MSVC.
|
||||||
@ -225,24 +231,24 @@ limb scalar_add(limb x, limb y, bool& overflow) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// multiply two small integers, getting both the high and low bits.
|
// multiply two small integers, getting both the high and low bits.
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb
|
||||||
limb scalar_mul(limb x, limb y, limb& carry) noexcept {
|
scalar_mul(limb x, limb y, limb &carry) noexcept {
|
||||||
#ifdef FASTFLOAT_64BIT_LIMB
|
#ifdef FASTFLOAT_64BIT_LIMB
|
||||||
#if defined(__SIZEOF_INT128__)
|
#if defined(__SIZEOF_INT128__)
|
||||||
// GCC and clang both define it as an extension.
|
// GCC and clang both define it as an extension.
|
||||||
__uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry);
|
__uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry);
|
||||||
carry = limb(z >> limb_bits);
|
carry = limb(z >> limb_bits);
|
||||||
return limb(z);
|
return limb(z);
|
||||||
#else
|
#else
|
||||||
// fallback, no native 128-bit integer multiplication with carry.
|
// fallback, no native 128-bit integer multiplication with carry.
|
||||||
// on msvc, this optimizes identically, somehow.
|
// on msvc, this optimizes identically, somehow.
|
||||||
value128 z = full_multiplication(x, y);
|
value128 z = full_multiplication(x, y);
|
||||||
bool overflow;
|
bool overflow;
|
||||||
z.low = scalar_add(z.low, carry, overflow);
|
z.low = scalar_add(z.low, carry, overflow);
|
||||||
z.high += uint64_t(overflow); // cannot overflow
|
z.high += uint64_t(overflow); // cannot overflow
|
||||||
carry = z.high;
|
carry = z.high;
|
||||||
return z.low;
|
return z.low;
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry);
|
uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry);
|
||||||
carry = limb(z >> limb_bits);
|
carry = limb(z >> limb_bits);
|
||||||
@ -253,8 +259,8 @@ limb scalar_mul(limb x, limb y, limb& carry) noexcept {
|
|||||||
// add scalar value to bigint starting from offset.
|
// add scalar value to bigint starting from offset.
|
||||||
// used in grade school multiplication
|
// used in grade school multiplication
|
||||||
template <uint16_t size>
|
template <uint16_t size>
|
||||||
inline FASTFLOAT_CONSTEXPR20
|
inline FASTFLOAT_CONSTEXPR20 bool small_add_from(stackvec<size> &vec, limb y,
|
||||||
bool small_add_from(stackvec<size>& vec, limb y, size_t start) noexcept {
|
size_t start) noexcept {
|
||||||
size_t index = start;
|
size_t index = start;
|
||||||
limb carry = y;
|
limb carry = y;
|
||||||
bool overflow;
|
bool overflow;
|
||||||
@ -271,15 +277,15 @@ bool small_add_from(stackvec<size>& vec, limb y, size_t start) noexcept {
|
|||||||
|
|
||||||
// add scalar value to bigint.
|
// add scalar value to bigint.
|
||||||
template <uint16_t size>
|
template <uint16_t size>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
|
||||||
bool small_add(stackvec<size>& vec, limb y) noexcept {
|
small_add(stackvec<size> &vec, limb y) noexcept {
|
||||||
return small_add_from(vec, y, 0);
|
return small_add_from(vec, y, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// multiply bigint by scalar value.
|
// multiply bigint by scalar value.
|
||||||
template <uint16_t size>
|
template <uint16_t size>
|
||||||
inline FASTFLOAT_CONSTEXPR20
|
inline FASTFLOAT_CONSTEXPR20 bool small_mul(stackvec<size> &vec,
|
||||||
bool small_mul(stackvec<size>& vec, limb y) noexcept {
|
limb y) noexcept {
|
||||||
limb carry = 0;
|
limb carry = 0;
|
||||||
for (size_t index = 0; index < vec.len(); index++) {
|
for (size_t index = 0; index < vec.len(); index++) {
|
||||||
vec[index] = scalar_mul(vec[index], y, carry);
|
vec[index] = scalar_mul(vec[index], y, carry);
|
||||||
@ -293,12 +299,12 @@ bool small_mul(stackvec<size>& vec, limb y) noexcept {
|
|||||||
// add bigint to bigint starting from index.
|
// add bigint to bigint starting from index.
|
||||||
// used in grade school multiplication
|
// used in grade school multiplication
|
||||||
template <uint16_t size>
|
template <uint16_t size>
|
||||||
FASTFLOAT_CONSTEXPR20
|
FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec<size> &x, limb_span y,
|
||||||
bool large_add_from(stackvec<size>& x, limb_span y, size_t start) noexcept {
|
size_t start) noexcept {
|
||||||
// the effective x buffer is from `xstart..x.len()`, so exit early
|
// the effective x buffer is from `xstart..x.len()`, so exit early
|
||||||
// if we can't get that current range.
|
// if we can't get that current range.
|
||||||
if (x.len() < start || y.len() > x.len() - start) {
|
if (x.len() < start || y.len() > x.len() - start) {
|
||||||
FASTFLOAT_TRY(x.try_resize(y.len() + start, 0));
|
FASTFLOAT_TRY(x.try_resize(y.len() + start, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool carry = false;
|
bool carry = false;
|
||||||
@ -324,15 +330,14 @@ bool large_add_from(stackvec<size>& x, limb_span y, size_t start) noexcept {
|
|||||||
|
|
||||||
// add bigint to bigint.
|
// add bigint to bigint.
|
||||||
template <uint16_t size>
|
template <uint16_t size>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
|
||||||
bool large_add_from(stackvec<size>& x, limb_span y) noexcept {
|
large_add_from(stackvec<size> &x, limb_span y) noexcept {
|
||||||
return large_add_from(x, y, 0);
|
return large_add_from(x, y, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// grade-school multiplication algorithm
|
// grade-school multiplication algorithm
|
||||||
template <uint16_t size>
|
template <uint16_t size>
|
||||||
FASTFLOAT_CONSTEXPR20
|
FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec<size> &x, limb_span y) noexcept {
|
||||||
bool long_mul(stackvec<size>& x, limb_span y) noexcept {
|
|
||||||
limb_span xs = limb_span(x.data, x.len());
|
limb_span xs = limb_span(x.data, x.len());
|
||||||
stackvec<size> z(xs);
|
stackvec<size> z(xs);
|
||||||
limb_span zs = limb_span(z.data, z.len());
|
limb_span zs = limb_span(z.data, z.len());
|
||||||
@ -360,8 +365,7 @@ bool long_mul(stackvec<size>& x, limb_span y) noexcept {
|
|||||||
|
|
||||||
// grade-school multiplication algorithm
|
// grade-school multiplication algorithm
|
||||||
template <uint16_t size>
|
template <uint16_t size>
|
||||||
FASTFLOAT_CONSTEXPR20
|
FASTFLOAT_CONSTEXPR20 bool large_mul(stackvec<size> &x, limb_span y) noexcept {
|
||||||
bool large_mul(stackvec<size>& x, limb_span y) noexcept {
|
|
||||||
if (y.len() == 1) {
|
if (y.len() == 1) {
|
||||||
FASTFLOAT_TRY(small_mul(x, y[0]));
|
FASTFLOAT_TRY(small_mul(x, y[0]));
|
||||||
} else {
|
} else {
|
||||||
@ -370,36 +374,58 @@ bool large_mul(stackvec<size>& x, limb_span y) noexcept {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename = void>
|
template <typename = void> struct pow5_tables {
|
||||||
struct pow5_tables {
|
|
||||||
static constexpr uint32_t large_step = 135;
|
static constexpr uint32_t large_step = 135;
|
||||||
static constexpr uint64_t small_power_of_5[] = {
|
static constexpr uint64_t small_power_of_5[] = {
|
||||||
1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL, 78125UL, 390625UL,
|
1UL,
|
||||||
1953125UL, 9765625UL, 48828125UL, 244140625UL, 1220703125UL,
|
5UL,
|
||||||
6103515625UL, 30517578125UL, 152587890625UL, 762939453125UL,
|
25UL,
|
||||||
3814697265625UL, 19073486328125UL, 95367431640625UL, 476837158203125UL,
|
125UL,
|
||||||
2384185791015625UL, 11920928955078125UL, 59604644775390625UL,
|
625UL,
|
||||||
298023223876953125UL, 1490116119384765625UL, 7450580596923828125UL,
|
3125UL,
|
||||||
|
15625UL,
|
||||||
|
78125UL,
|
||||||
|
390625UL,
|
||||||
|
1953125UL,
|
||||||
|
9765625UL,
|
||||||
|
48828125UL,
|
||||||
|
244140625UL,
|
||||||
|
1220703125UL,
|
||||||
|
6103515625UL,
|
||||||
|
30517578125UL,
|
||||||
|
152587890625UL,
|
||||||
|
762939453125UL,
|
||||||
|
3814697265625UL,
|
||||||
|
19073486328125UL,
|
||||||
|
95367431640625UL,
|
||||||
|
476837158203125UL,
|
||||||
|
2384185791015625UL,
|
||||||
|
11920928955078125UL,
|
||||||
|
59604644775390625UL,
|
||||||
|
298023223876953125UL,
|
||||||
|
1490116119384765625UL,
|
||||||
|
7450580596923828125UL,
|
||||||
};
|
};
|
||||||
#ifdef FASTFLOAT_64BIT_LIMB
|
#ifdef FASTFLOAT_64BIT_LIMB
|
||||||
constexpr static limb large_power_of_5[] = {
|
constexpr static limb large_power_of_5[] = {
|
||||||
1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL,
|
1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL,
|
||||||
10482974169319127550UL, 198276706040285095UL};
|
10482974169319127550UL, 198276706040285095UL};
|
||||||
#else
|
#else
|
||||||
constexpr static limb large_power_of_5[] = {
|
constexpr static limb large_power_of_5[] = {
|
||||||
4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U,
|
4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U,
|
||||||
1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U};
|
1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U};
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE
|
||||||
constexpr uint32_t pow5_tables<T>::large_step;
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> constexpr uint32_t pow5_tables<T>::large_step;
|
||||||
constexpr uint64_t pow5_tables<T>::small_power_of_5[];
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> constexpr uint64_t pow5_tables<T>::small_power_of_5[];
|
||||||
constexpr limb pow5_tables<T>::large_power_of_5[];
|
|
||||||
|
template <typename T> constexpr limb pow5_tables<T>::large_power_of_5[];
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
// big integer type. implements a small subset of big integer
|
// big integer type. implements a small subset of big integer
|
||||||
// arithmetic, using simple algorithms since asymptotically
|
// arithmetic, using simple algorithms since asymptotically
|
||||||
@ -409,13 +435,14 @@ struct bigint : pow5_tables<> {
|
|||||||
// storage of the limbs, in little-endian order.
|
// storage of the limbs, in little-endian order.
|
||||||
stackvec<bigint_limbs> vec;
|
stackvec<bigint_limbs> vec;
|
||||||
|
|
||||||
FASTFLOAT_CONSTEXPR20 bigint(): vec() {}
|
FASTFLOAT_CONSTEXPR20 bigint() : vec() {}
|
||||||
bigint(const bigint &) = delete;
|
|
||||||
bigint &operator=(const bigint &) = delete;
|
bigint(bigint const &) = delete;
|
||||||
|
bigint &operator=(bigint const &) = delete;
|
||||||
bigint(bigint &&) = delete;
|
bigint(bigint &&) = delete;
|
||||||
bigint &operator=(bigint &&other) = delete;
|
bigint &operator=(bigint &&other) = delete;
|
||||||
|
|
||||||
FASTFLOAT_CONSTEXPR20 bigint(uint64_t value): vec() {
|
FASTFLOAT_CONSTEXPR20 bigint(uint64_t value) : vec() {
|
||||||
#ifdef FASTFLOAT_64BIT_LIMB
|
#ifdef FASTFLOAT_64BIT_LIMB
|
||||||
vec.push_unchecked(value);
|
vec.push_unchecked(value);
|
||||||
#else
|
#else
|
||||||
@ -427,7 +454,7 @@ struct bigint : pow5_tables<> {
|
|||||||
|
|
||||||
// get the high 64 bits from the vector, and if bits were truncated.
|
// get the high 64 bits from the vector, and if bits were truncated.
|
||||||
// this is to get the significant digits for the float.
|
// this is to get the significant digits for the float.
|
||||||
FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool& truncated) const noexcept {
|
FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool &truncated) const noexcept {
|
||||||
#ifdef FASTFLOAT_64BIT_LIMB
|
#ifdef FASTFLOAT_64BIT_LIMB
|
||||||
if (vec.len() == 0) {
|
if (vec.len() == 0) {
|
||||||
return empty_hi64(truncated);
|
return empty_hi64(truncated);
|
||||||
@ -446,7 +473,8 @@ struct bigint : pow5_tables<> {
|
|||||||
} else if (vec.len() == 2) {
|
} else if (vec.len() == 2) {
|
||||||
return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated);
|
return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated);
|
||||||
} else {
|
} else {
|
||||||
uint64_t result = uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated);
|
uint64_t result =
|
||||||
|
uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated);
|
||||||
truncated |= vec.nonzero(3);
|
truncated |= vec.nonzero(3);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -459,7 +487,7 @@ struct bigint : pow5_tables<> {
|
|||||||
// positive, this is larger, otherwise they are equal.
|
// positive, this is larger, otherwise they are equal.
|
||||||
// the limbs are stored in little-endian order, so we
|
// the limbs are stored in little-endian order, so we
|
||||||
// must compare the limbs in ever order.
|
// must compare the limbs in ever order.
|
||||||
FASTFLOAT_CONSTEXPR20 int compare(const bigint& other) const noexcept {
|
FASTFLOAT_CONSTEXPR20 int compare(bigint const &other) const noexcept {
|
||||||
if (vec.len() > other.vec.len()) {
|
if (vec.len() > other.vec.len()) {
|
||||||
return 1;
|
return 1;
|
||||||
} else if (vec.len() < other.vec.len()) {
|
} else if (vec.len() < other.vec.len()) {
|
||||||
@ -512,12 +540,12 @@ struct bigint : pow5_tables<> {
|
|||||||
return false;
|
return false;
|
||||||
} else if (!vec.is_empty()) {
|
} else if (!vec.is_empty()) {
|
||||||
// move limbs
|
// move limbs
|
||||||
limb* dst = vec.data + n;
|
limb *dst = vec.data + n;
|
||||||
const limb* src = vec.data;
|
limb const *src = vec.data;
|
||||||
std::copy_backward(src, src + vec.len(), dst + vec.len());
|
std::copy_backward(src, src + vec.len(), dst + vec.len());
|
||||||
// fill in empty limbs
|
// fill in empty limbs
|
||||||
limb* first = vec.data;
|
limb *first = vec.data;
|
||||||
limb* last = first + n;
|
limb *last = first + n;
|
||||||
::std::fill(first, last, 0);
|
::std::fill(first, last, 0);
|
||||||
vec.set_len(n + vec.len());
|
vec.set_len(n + vec.len());
|
||||||
return true;
|
return true;
|
||||||
@ -560,18 +588,12 @@ struct bigint : pow5_tables<> {
|
|||||||
return int(limb_bits * vec.len()) - lz;
|
return int(limb_bits * vec.len()) - lz;
|
||||||
}
|
}
|
||||||
|
|
||||||
FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept {
|
FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { return small_mul(vec, y); }
|
||||||
return small_mul(vec, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept {
|
FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { return small_add(vec, y); }
|
||||||
return small_add(vec, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// multiply as if by 2 raised to a power.
|
// multiply as if by 2 raised to a power.
|
||||||
FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept {
|
FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { return shl(exp); }
|
||||||
return shl(exp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// multiply as if by 5 raised to a power.
|
// multiply as if by 5 raised to a power.
|
||||||
FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept {
|
FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept {
|
||||||
@ -597,9 +619,8 @@ struct bigint : pow5_tables<> {
|
|||||||
// Work around clang bug https://godbolt.org/z/zedh7rrhc
|
// Work around clang bug https://godbolt.org/z/zedh7rrhc
|
||||||
// This is similar to https://github.com/llvm/llvm-project/issues/47746,
|
// This is similar to https://github.com/llvm/llvm-project/issues/47746,
|
||||||
// except the workaround described there don't work here
|
// except the workaround described there don't work here
|
||||||
FASTFLOAT_TRY(
|
FASTFLOAT_TRY(small_mul(
|
||||||
small_mul(vec, limb(((void)small_power_of_5[0], small_power_of_5[exp])))
|
vec, limb(((void)small_power_of_5[0], small_power_of_5[exp]))));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Testing for https://wg21.link/N3652, adopted in C++14
|
// Testing for https://wg21.link/N3652, adopted in C++14
|
||||||
#if __cpp_constexpr >= 201304
|
#if defined(__cpp_constexpr) && __cpp_constexpr >= 201304
|
||||||
#define FASTFLOAT_CONSTEXPR14 constexpr
|
#define FASTFLOAT_CONSTEXPR14 constexpr
|
||||||
#else
|
#else
|
||||||
#define FASTFLOAT_CONSTEXPR14
|
#define FASTFLOAT_CONSTEXPR14
|
||||||
@ -20,16 +20,23 @@
|
|||||||
#define FASTFLOAT_HAS_BIT_CAST 0
|
#define FASTFLOAT_HAS_BIT_CAST 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__cpp_lib_is_constant_evaluated) && __cpp_lib_is_constant_evaluated >= 201811L
|
#if defined(__cpp_lib_is_constant_evaluated) && \
|
||||||
|
__cpp_lib_is_constant_evaluated >= 201811L
|
||||||
#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1
|
#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1
|
||||||
#else
|
#else
|
||||||
#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0
|
#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L
|
||||||
|
#define FASTFLOAT_IF_CONSTEXPR17(x) if constexpr (x)
|
||||||
|
#else
|
||||||
|
#define FASTFLOAT_IF_CONSTEXPR17(x) if (x)
|
||||||
|
#endif
|
||||||
|
|
||||||
// Testing for relevant C++20 constexpr library features
|
// Testing for relevant C++20 constexpr library features
|
||||||
#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED \
|
#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST && \
|
||||||
&& FASTFLOAT_HAS_BIT_CAST \
|
defined(__cpp_lib_constexpr_algorithms) && \
|
||||||
&& __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/
|
__cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/
|
||||||
#define FASTFLOAT_CONSTEXPR20 constexpr
|
#define FASTFLOAT_CONSTEXPR20 constexpr
|
||||||
#define FASTFLOAT_IS_CONSTEXPR 1
|
#define FASTFLOAT_IS_CONSTEXPR 1
|
||||||
#else
|
#else
|
||||||
@ -37,4 +44,10 @@
|
|||||||
#define FASTFLOAT_IS_CONSTEXPR 0
|
#define FASTFLOAT_IS_CONSTEXPR 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
|
||||||
|
#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 0
|
||||||
|
#else
|
||||||
|
#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H
|
#endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H
|
||||||
|
|||||||
@ -12,27 +12,34 @@
|
|||||||
|
|
||||||
namespace fast_float {
|
namespace fast_float {
|
||||||
|
|
||||||
// This will compute or rather approximate w * 5**q and return a pair of 64-bit words approximating
|
// This will compute or rather approximate w * 5**q and return a pair of 64-bit
|
||||||
// the result, with the "high" part corresponding to the most significant bits and the
|
// words approximating the result, with the "high" part corresponding to the
|
||||||
// low part corresponding to the least significant bits.
|
// most significant bits and the low part corresponding to the least significant
|
||||||
|
// bits.
|
||||||
//
|
//
|
||||||
template <int bit_precision>
|
template <int bit_precision>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128
|
||||||
value128 compute_product_approximation(int64_t q, uint64_t w) {
|
compute_product_approximation(int64_t q, uint64_t w) {
|
||||||
const int index = 2 * int(q - powers::smallest_power_of_five);
|
int const index = 2 * int(q - powers::smallest_power_of_five);
|
||||||
// For small values of q, e.g., q in [0,27], the answer is always exact because
|
// For small values of q, e.g., q in [0,27], the answer is always exact
|
||||||
// The line value128 firstproduct = full_multiplication(w, power_of_five_128[index]);
|
// because The line value128 firstproduct = full_multiplication(w,
|
||||||
// gives the exact answer.
|
// power_of_five_128[index]); gives the exact answer.
|
||||||
value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]);
|
value128 firstproduct =
|
||||||
static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]");
|
full_multiplication(w, powers::power_of_five_128[index]);
|
||||||
constexpr uint64_t precision_mask = (bit_precision < 64) ?
|
static_assert((bit_precision >= 0) && (bit_precision <= 64),
|
||||||
(uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision)
|
" precision should be in (0,64]");
|
||||||
: uint64_t(0xFFFFFFFFFFFFFFFF);
|
constexpr uint64_t precision_mask =
|
||||||
if((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower)
|
(bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision)
|
||||||
// regarding the second product, we only need secondproduct.high, but our expectation is that the compiler will optimize this extra work away if needed.
|
: uint64_t(0xFFFFFFFFFFFFFFFF);
|
||||||
value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]);
|
if ((firstproduct.high & precision_mask) ==
|
||||||
|
precision_mask) { // could further guard with (lower + w < lower)
|
||||||
|
// regarding the second product, we only need secondproduct.high, but our
|
||||||
|
// expectation is that the compiler will optimize this extra work away if
|
||||||
|
// needed.
|
||||||
|
value128 secondproduct =
|
||||||
|
full_multiplication(w, powers::power_of_five_128[index + 1]);
|
||||||
firstproduct.low += secondproduct.high;
|
firstproduct.low += secondproduct.high;
|
||||||
if(secondproduct.high > firstproduct.low) {
|
if (secondproduct.high > firstproduct.low) {
|
||||||
firstproduct.high++;
|
firstproduct.high++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,43 +62,45 @@ namespace detail {
|
|||||||
* where
|
* where
|
||||||
* p = log(5**-q)/log(2) = -q * log(5)/log(2)
|
* p = log(5**-q)/log(2) = -q * log(5)/log(2)
|
||||||
*/
|
*/
|
||||||
constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept {
|
constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept {
|
||||||
return (((152170 + 65536) * q) >> 16) + 63;
|
return (((152170 + 65536) * q) >> 16) + 63;
|
||||||
}
|
}
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
// create an adjusted mantissa, biased by the invalid power2
|
// create an adjusted mantissa, biased by the invalid power2
|
||||||
// for significant digits already multiplied by 10 ** q.
|
// for significant digits already multiplied by 10 ** q.
|
||||||
template <typename binary>
|
template <typename binary>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR14
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 adjusted_mantissa
|
||||||
adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept {
|
compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept {
|
||||||
int hilz = int(w >> 63) ^ 1;
|
int hilz = int(w >> 63) ^ 1;
|
||||||
adjusted_mantissa answer;
|
adjusted_mantissa answer;
|
||||||
answer.mantissa = w << hilz;
|
answer.mantissa = w << hilz;
|
||||||
int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent();
|
int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent();
|
||||||
answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + invalid_am_bias);
|
answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 +
|
||||||
|
invalid_am_bias);
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// w * 10 ** q, without rounding the representation up.
|
// w * 10 ** q, without rounding the representation up.
|
||||||
// the power2 in the exponent will be adjusted by invalid_am_bias.
|
// the power2 in the exponent will be adjusted by invalid_am_bias.
|
||||||
template <typename binary>
|
template <typename binary>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
|
||||||
adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept {
|
compute_error(int64_t q, uint64_t w) noexcept {
|
||||||
int lz = leading_zeroes(w);
|
int lz = leading_zeroes(w);
|
||||||
w <<= lz;
|
w <<= lz;
|
||||||
value128 product = compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w);
|
value128 product =
|
||||||
|
compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w);
|
||||||
return compute_error_scaled<binary>(q, product.high, lz);
|
return compute_error_scaled<binary>(q, product.high, lz);
|
||||||
}
|
}
|
||||||
|
|
||||||
// w * 10 ** q
|
// Computers w * 10 ** q.
|
||||||
// The returned value should be a valid ieee64 number that simply need to be packed.
|
// The returned value should be a valid number that simply needs to be
|
||||||
// However, in some very rare cases, the computation will fail. In such cases, we
|
// packed. However, in some very rare cases, the computation will fail. In such
|
||||||
// return an adjusted_mantissa with a negative power of 2: the caller should recompute
|
// cases, we return an adjusted_mantissa with a negative power of 2: the caller
|
||||||
// in such cases.
|
// should recompute in such cases.
|
||||||
template <typename binary>
|
template <typename binary>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
|
||||||
adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept {
|
compute_float(int64_t q, uint64_t w) noexcept {
|
||||||
adjusted_mantissa answer;
|
adjusted_mantissa answer;
|
||||||
if ((w == 0) || (q < binary::smallest_power_of_ten())) {
|
if ((w == 0) || (q < binary::smallest_power_of_ten())) {
|
||||||
answer.power2 = 0;
|
answer.power2 = 0;
|
||||||
@ -105,7 +114,8 @@ adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept {
|
|||||||
answer.mantissa = 0;
|
answer.mantissa = 0;
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
// At this point in time q is in [powers::smallest_power_of_five, powers::largest_power_of_five].
|
// At this point in time q is in [powers::smallest_power_of_five,
|
||||||
|
// powers::largest_power_of_five].
|
||||||
|
|
||||||
// We want the most significant bit of i to be 1. Shift if needed.
|
// We want the most significant bit of i to be 1. Shift if needed.
|
||||||
int lz = leading_zeroes(w);
|
int lz = leading_zeroes(w);
|
||||||
@ -114,26 +124,32 @@ adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept {
|
|||||||
// The required precision is binary::mantissa_explicit_bits() + 3 because
|
// The required precision is binary::mantissa_explicit_bits() + 3 because
|
||||||
// 1. We need the implicit bit
|
// 1. We need the implicit bit
|
||||||
// 2. We need an extra bit for rounding purposes
|
// 2. We need an extra bit for rounding purposes
|
||||||
// 3. We might lose a bit due to the "upperbit" routine (result too small, requiring a shift)
|
// 3. We might lose a bit due to the "upperbit" routine (result too small,
|
||||||
|
// requiring a shift)
|
||||||
|
|
||||||
value128 product = compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w);
|
value128 product =
|
||||||
|
compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w);
|
||||||
// The computed 'product' is always sufficient.
|
// The computed 'product' is always sufficient.
|
||||||
// Mathematical proof:
|
// Mathematical proof:
|
||||||
// Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to appear)
|
// Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to
|
||||||
// See script/mushtak_lemire.py
|
// appear) See script/mushtak_lemire.py
|
||||||
|
|
||||||
// The "compute_product_approximation" function can be slightly slower than a branchless approach:
|
// The "compute_product_approximation" function can be slightly slower than a
|
||||||
// value128 product = compute_product(q, w);
|
// branchless approach: value128 product = compute_product(q, w); but in
|
||||||
// but in practice, we can win big with the compute_product_approximation if its additional branch
|
// practice, we can win big with the compute_product_approximation if its
|
||||||
// is easily predicted. Which is best is data specific.
|
// additional branch is easily predicted. Which is best is data specific.
|
||||||
int upperbit = int(product.high >> 63);
|
int upperbit = int(product.high >> 63);
|
||||||
|
int shift = upperbit + 64 - binary::mantissa_explicit_bits() - 3;
|
||||||
|
|
||||||
answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3);
|
answer.mantissa = product.high >> shift;
|
||||||
|
|
||||||
answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - binary::minimum_exponent());
|
answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz -
|
||||||
|
binary::minimum_exponent());
|
||||||
if (answer.power2 <= 0) { // we have a subnormal?
|
if (answer.power2 <= 0) { // we have a subnormal?
|
||||||
// Here have that answer.power2 <= 0 so -answer.power2 >= 0
|
// Here have that answer.power2 <= 0 so -answer.power2 >= 0
|
||||||
if(-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure.
|
if (-answer.power2 + 1 >=
|
||||||
|
64) { // if we have more than 64 bits below the minimum exponent, you
|
||||||
|
// have a zero for sure.
|
||||||
answer.power2 = 0;
|
answer.power2 = 0;
|
||||||
answer.mantissa = 0;
|
answer.mantissa = 0;
|
||||||
// result should be zero
|
// result should be zero
|
||||||
@ -142,7 +158,8 @@ adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept {
|
|||||||
// next line is safe because -answer.power2 + 1 < 64
|
// next line is safe because -answer.power2 + 1 < 64
|
||||||
answer.mantissa >>= -answer.power2 + 1;
|
answer.mantissa >>= -answer.power2 + 1;
|
||||||
// Thankfully, we can't have both "round-to-even" and subnormals because
|
// Thankfully, we can't have both "round-to-even" and subnormals because
|
||||||
// "round-to-even" only occurs for powers close to 0.
|
// "round-to-even" only occurs for powers close to 0 in the 32-bit and
|
||||||
|
// and 64-bit case (with no more than 19 digits).
|
||||||
answer.mantissa += (answer.mantissa & 1); // round up
|
answer.mantissa += (answer.mantissa & 1); // round up
|
||||||
answer.mantissa >>= 1;
|
answer.mantissa >>= 1;
|
||||||
// There is a weird scenario where we don't have a subnormal but just.
|
// There is a weird scenario where we don't have a subnormal but just.
|
||||||
@ -152,20 +169,26 @@ adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept {
|
|||||||
// up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer
|
// up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer
|
||||||
// subnormal, but we can only know this after rounding.
|
// subnormal, but we can only know this after rounding.
|
||||||
// So we only declare a subnormal if we are smaller than the threshold.
|
// So we only declare a subnormal if we are smaller than the threshold.
|
||||||
answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1;
|
answer.power2 =
|
||||||
|
(answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits()))
|
||||||
|
? 0
|
||||||
|
: 1;
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// usually, we round *up*, but if we fall right in between and and we have an
|
// usually, we round *up*, but if we fall right in between and and we have an
|
||||||
// even basis, we need to round down
|
// even basis, we need to round down
|
||||||
// We are only concerned with the cases where 5**q fits in single 64-bit word.
|
// We are only concerned with the cases where 5**q fits in single 64-bit word.
|
||||||
if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) &&
|
if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) &&
|
||||||
((answer.mantissa & 3) == 1) ) { // we may fall between two floats!
|
(q <= binary::max_exponent_round_to_even()) &&
|
||||||
|
((answer.mantissa & 3) == 1)) { // we may fall between two floats!
|
||||||
// To be in-between two floats we need that in doing
|
// To be in-between two floats we need that in doing
|
||||||
// answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3);
|
// answer.mantissa = product.high >> (upperbit + 64 -
|
||||||
// ... we dropped out only zeroes. But if this happened, then we can go back!!!
|
// binary::mantissa_explicit_bits() - 3);
|
||||||
if((answer.mantissa << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == product.high) {
|
// ... we dropped out only zeroes. But if this happened, then we can go
|
||||||
answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up
|
// back!!!
|
||||||
|
if ((answer.mantissa << shift) == product.high) {
|
||||||
|
answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,21 +13,33 @@
|
|||||||
namespace fast_float {
|
namespace fast_float {
|
||||||
|
|
||||||
// 1e0 to 1e19
|
// 1e0 to 1e19
|
||||||
constexpr static uint64_t powers_of_ten_uint64[] = {
|
constexpr static uint64_t powers_of_ten_uint64[] = {1UL,
|
||||||
1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL,
|
10UL,
|
||||||
1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL,
|
100UL,
|
||||||
100000000000000UL, 1000000000000000UL, 10000000000000000UL, 100000000000000000UL,
|
1000UL,
|
||||||
1000000000000000000UL, 10000000000000000000UL};
|
10000UL,
|
||||||
|
100000UL,
|
||||||
|
1000000UL,
|
||||||
|
10000000UL,
|
||||||
|
100000000UL,
|
||||||
|
1000000000UL,
|
||||||
|
10000000000UL,
|
||||||
|
100000000000UL,
|
||||||
|
1000000000000UL,
|
||||||
|
10000000000000UL,
|
||||||
|
100000000000000UL,
|
||||||
|
1000000000000000UL,
|
||||||
|
10000000000000000UL,
|
||||||
|
100000000000000000UL,
|
||||||
|
1000000000000000000UL,
|
||||||
|
10000000000000000000UL};
|
||||||
|
|
||||||
// calculate the exponent, in scientific notation, of the number.
|
// calculate the exponent, in scientific notation, of the number.
|
||||||
// this algorithm is not even close to optimized, but it has no practical
|
// this algorithm is not even close to optimized, but it has no practical
|
||||||
// effect on performance: in order to have a faster algorithm, we'd need
|
// effect on performance: in order to have a faster algorithm, we'd need
|
||||||
// to slow down performance for faster algorithms, and this is still fast.
|
// to slow down performance for faster algorithms, and this is still fast.
|
||||||
template <typename UC>
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int32_t
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR14
|
scientific_exponent(uint64_t mantissa, int32_t exponent) noexcept {
|
||||||
int32_t scientific_exponent(parsed_number_string_t<UC> & num) noexcept {
|
|
||||||
uint64_t mantissa = num.mantissa;
|
|
||||||
int32_t exponent = int32_t(num.exponent);
|
|
||||||
while (mantissa >= 10000) {
|
while (mantissa >= 10000) {
|
||||||
mantissa /= 10000;
|
mantissa /= 10000;
|
||||||
exponent += 4;
|
exponent += 4;
|
||||||
@ -45,15 +57,16 @@ int32_t scientific_exponent(parsed_number_string_t<UC> & num) noexcept {
|
|||||||
|
|
||||||
// this converts a native floating-point number to an extended-precision float.
|
// this converts a native floating-point number to an extended-precision float.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
|
||||||
adjusted_mantissa to_extended(T value) noexcept {
|
to_extended(T value) noexcept {
|
||||||
using equiv_uint = typename binary_format<T>::equiv_uint;
|
using equiv_uint = equiv_uint_t<T>;
|
||||||
constexpr equiv_uint exponent_mask = binary_format<T>::exponent_mask();
|
constexpr equiv_uint exponent_mask = binary_format<T>::exponent_mask();
|
||||||
constexpr equiv_uint mantissa_mask = binary_format<T>::mantissa_mask();
|
constexpr equiv_uint mantissa_mask = binary_format<T>::mantissa_mask();
|
||||||
constexpr equiv_uint hidden_bit_mask = binary_format<T>::hidden_bit_mask();
|
constexpr equiv_uint hidden_bit_mask = binary_format<T>::hidden_bit_mask();
|
||||||
|
|
||||||
adjusted_mantissa am;
|
adjusted_mantissa am;
|
||||||
int32_t bias = binary_format<T>::mantissa_explicit_bits() - binary_format<T>::minimum_exponent();
|
int32_t bias = binary_format<T>::mantissa_explicit_bits() -
|
||||||
|
binary_format<T>::minimum_exponent();
|
||||||
equiv_uint bits;
|
equiv_uint bits;
|
||||||
#if FASTFLOAT_HAS_BIT_CAST
|
#if FASTFLOAT_HAS_BIT_CAST
|
||||||
bits = std::bit_cast<equiv_uint>(value);
|
bits = std::bit_cast<equiv_uint>(value);
|
||||||
@ -66,7 +79,8 @@ adjusted_mantissa to_extended(T value) noexcept {
|
|||||||
am.mantissa = bits & mantissa_mask;
|
am.mantissa = bits & mantissa_mask;
|
||||||
} else {
|
} else {
|
||||||
// normal
|
// normal
|
||||||
am.power2 = int32_t((bits & exponent_mask) >> binary_format<T>::mantissa_explicit_bits());
|
am.power2 = int32_t((bits & exponent_mask) >>
|
||||||
|
binary_format<T>::mantissa_explicit_bits());
|
||||||
am.power2 -= bias;
|
am.power2 -= bias;
|
||||||
am.mantissa = (bits & mantissa_mask) | hidden_bit_mask;
|
am.mantissa = (bits & mantissa_mask) | hidden_bit_mask;
|
||||||
}
|
}
|
||||||
@ -78,8 +92,8 @@ adjusted_mantissa to_extended(T value) noexcept {
|
|||||||
// we are given a native float that represents b, so we need to adjust it
|
// we are given a native float that represents b, so we need to adjust it
|
||||||
// halfway between b and b+u.
|
// halfway between b and b+u.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
|
||||||
adjusted_mantissa to_extended_halfway(T value) noexcept {
|
to_extended_halfway(T value) noexcept {
|
||||||
adjusted_mantissa am = to_extended(value);
|
adjusted_mantissa am = to_extended(value);
|
||||||
am.mantissa <<= 1;
|
am.mantissa <<= 1;
|
||||||
am.mantissa += 1;
|
am.mantissa += 1;
|
||||||
@ -89,15 +103,18 @@ adjusted_mantissa to_extended_halfway(T value) noexcept {
|
|||||||
|
|
||||||
// round an extended-precision float to the nearest machine float.
|
// round an extended-precision float to the nearest machine float.
|
||||||
template <typename T, typename callback>
|
template <typename T, typename callback>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR14
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am,
|
||||||
void round(adjusted_mantissa& am, callback cb) noexcept {
|
callback cb) noexcept {
|
||||||
int32_t mantissa_shift = 64 - binary_format<T>::mantissa_explicit_bits() - 1;
|
int32_t mantissa_shift = 64 - binary_format<T>::mantissa_explicit_bits() - 1;
|
||||||
if (-am.power2 >= mantissa_shift) {
|
if (-am.power2 >= mantissa_shift) {
|
||||||
// have a denormal float
|
// have a denormal float
|
||||||
int32_t shift = -am.power2 + 1;
|
int32_t shift = -am.power2 + 1;
|
||||||
cb(am, std::min<int32_t>(shift, 64));
|
cb(am, std::min<int32_t>(shift, 64));
|
||||||
// check for round-up: if rounding-nearest carried us to the hidden bit.
|
// check for round-up: if rounding-nearest carried us to the hidden bit.
|
||||||
am.power2 = (am.mantissa < (uint64_t(1) << binary_format<T>::mantissa_explicit_bits())) ? 0 : 1;
|
am.power2 = (am.mantissa <
|
||||||
|
(uint64_t(1) << binary_format<T>::mantissa_explicit_bits()))
|
||||||
|
? 0
|
||||||
|
: 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +122,8 @@ void round(adjusted_mantissa& am, callback cb) noexcept {
|
|||||||
cb(am, mantissa_shift);
|
cb(am, mantissa_shift);
|
||||||
|
|
||||||
// check for carry
|
// check for carry
|
||||||
if (am.mantissa >= (uint64_t(2) << binary_format<T>::mantissa_explicit_bits())) {
|
if (am.mantissa >=
|
||||||
|
(uint64_t(2) << binary_format<T>::mantissa_explicit_bits())) {
|
||||||
am.mantissa = (uint64_t(1) << binary_format<T>::mantissa_explicit_bits());
|
am.mantissa = (uint64_t(1) << binary_format<T>::mantissa_explicit_bits());
|
||||||
am.power2++;
|
am.power2++;
|
||||||
}
|
}
|
||||||
@ -119,16 +137,11 @@ void round(adjusted_mantissa& am, callback cb) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename callback>
|
template <typename callback>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR14
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void
|
||||||
void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept {
|
round_nearest_tie_even(adjusted_mantissa &am, int32_t shift,
|
||||||
const uint64_t mask
|
callback cb) noexcept {
|
||||||
= (shift == 64)
|
uint64_t const mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1;
|
||||||
? UINT64_MAX
|
uint64_t const halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1);
|
||||||
: (uint64_t(1) << shift) - 1;
|
|
||||||
const uint64_t halfway
|
|
||||||
= (shift == 0)
|
|
||||||
? 0
|
|
||||||
: uint64_t(1) << (shift - 1);
|
|
||||||
uint64_t truncated_bits = am.mantissa & mask;
|
uint64_t truncated_bits = am.mantissa & mask;
|
||||||
bool is_above = truncated_bits > halfway;
|
bool is_above = truncated_bits > halfway;
|
||||||
bool is_halfway = truncated_bits == halfway;
|
bool is_halfway = truncated_bits == halfway;
|
||||||
@ -145,8 +158,8 @@ void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) n
|
|||||||
am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above));
|
am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above));
|
||||||
}
|
}
|
||||||
|
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR14
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void
|
||||||
void round_down(adjusted_mantissa& am, int32_t shift) noexcept {
|
round_down(adjusted_mantissa &am, int32_t shift) noexcept {
|
||||||
if (shift == 64) {
|
if (shift == 64) {
|
||||||
am.mantissa = 0;
|
am.mantissa = 0;
|
||||||
} else {
|
} else {
|
||||||
@ -154,11 +167,13 @@ void round_down(adjusted_mantissa& am, int32_t shift) noexcept {
|
|||||||
}
|
}
|
||||||
am.power2 += shift;
|
am.power2 += shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename UC>
|
template <typename UC>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
|
||||||
void skip_zeros(UC const * & first, UC const * last) noexcept {
|
skip_zeros(UC const *&first, UC const *last) noexcept {
|
||||||
uint64_t val;
|
uint64_t val;
|
||||||
while (!cpp20_and_in_constexpr() && std::distance(first, last) >= int_cmp_len<UC>()) {
|
while (!cpp20_and_in_constexpr() &&
|
||||||
|
std::distance(first, last) >= int_cmp_len<UC>()) {
|
||||||
::memcpy(&val, first, sizeof(uint64_t));
|
::memcpy(&val, first, sizeof(uint64_t));
|
||||||
if (val != int_cmp_zeros<UC>()) {
|
if (val != int_cmp_zeros<UC>()) {
|
||||||
break;
|
break;
|
||||||
@ -176,11 +191,12 @@ void skip_zeros(UC const * & first, UC const * last) noexcept {
|
|||||||
// determine if any non-zero digits were truncated.
|
// determine if any non-zero digits were truncated.
|
||||||
// all characters must be valid digits.
|
// all characters must be valid digits.
|
||||||
template <typename UC>
|
template <typename UC>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
|
||||||
bool is_truncated(UC const * first, UC const * last) noexcept {
|
is_truncated(UC const *first, UC const *last) noexcept {
|
||||||
// do 8-bit optimizations, can just compare to 8 literal 0s.
|
// do 8-bit optimizations, can just compare to 8 literal 0s.
|
||||||
uint64_t val;
|
uint64_t val;
|
||||||
while (!cpp20_and_in_constexpr() && std::distance(first, last) >= int_cmp_len<UC>()) {
|
while (!cpp20_and_in_constexpr() &&
|
||||||
|
std::distance(first, last) >= int_cmp_len<UC>()) {
|
||||||
::memcpy(&val, first, sizeof(uint64_t));
|
::memcpy(&val, first, sizeof(uint64_t));
|
||||||
if (val != int_cmp_zeros<UC>()) {
|
if (val != int_cmp_zeros<UC>()) {
|
||||||
return true;
|
return true;
|
||||||
@ -195,16 +211,17 @@ bool is_truncated(UC const * first, UC const * last) noexcept {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename UC>
|
template <typename UC>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
|
||||||
bool is_truncated(span<const UC> s) noexcept {
|
is_truncated(span<UC const> s) noexcept {
|
||||||
return is_truncated(s.ptr, s.ptr + s.len());
|
return is_truncated(s.ptr, s.ptr + s.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template <typename UC>
|
template <typename UC>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
|
||||||
void parse_eight_digits(const UC*& p, limb& value, size_t& counter, size_t& count) noexcept {
|
parse_eight_digits(UC const *&p, limb &value, size_t &counter,
|
||||||
|
size_t &count) noexcept {
|
||||||
value = value * 100000000 + parse_eight_digits_unrolled(p);
|
value = value * 100000000 + parse_eight_digits_unrolled(p);
|
||||||
p += 8;
|
p += 8;
|
||||||
counter += 8;
|
counter += 8;
|
||||||
@ -212,22 +229,23 @@ void parse_eight_digits(const UC*& p, limb& value, size_t& counter, size_t& coun
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename UC>
|
template <typename UC>
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR14
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void
|
||||||
void parse_one_digit(UC const *& p, limb& value, size_t& counter, size_t& count) noexcept {
|
parse_one_digit(UC const *&p, limb &value, size_t &counter,
|
||||||
|
size_t &count) noexcept {
|
||||||
value = value * 10 + limb(*p - UC('0'));
|
value = value * 10 + limb(*p - UC('0'));
|
||||||
p++;
|
p++;
|
||||||
counter++;
|
counter++;
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
|
||||||
void add_native(bigint& big, limb power, limb value) noexcept {
|
add_native(bigint &big, limb power, limb value) noexcept {
|
||||||
big.mul(power);
|
big.mul(power);
|
||||||
big.add(value);
|
big.add(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void
|
||||||
void round_up_bigint(bigint& big, size_t& count) noexcept {
|
round_up_bigint(bigint &big, size_t &count) noexcept {
|
||||||
// need to round-up the digits, but need to avoid rounding
|
// need to round-up the digits, but need to avoid rounding
|
||||||
// ....9999 to ...10000, which could cause a false halfway point.
|
// ....9999 to ...10000, which could cause a false halfway point.
|
||||||
add_native(big, 10, 1);
|
add_native(big, 10, 1);
|
||||||
@ -236,8 +254,9 @@ void round_up_bigint(bigint& big, size_t& count) noexcept {
|
|||||||
|
|
||||||
// parse the significant digits into a big integer
|
// parse the significant digits into a big integer
|
||||||
template <typename UC>
|
template <typename UC>
|
||||||
inline FASTFLOAT_CONSTEXPR20
|
inline FASTFLOAT_CONSTEXPR20 void
|
||||||
void parse_mantissa(bigint& result, parsed_number_string_t<UC>& num, size_t max_digits, size_t& digits) noexcept {
|
parse_mantissa(bigint &result, parsed_number_string_t<UC> &num,
|
||||||
|
size_t max_digits, size_t &digits) noexcept {
|
||||||
// try to minimize the number of big integer and scalar multiplication.
|
// try to minimize the number of big integer and scalar multiplication.
|
||||||
// therefore, try to parse 8 digits at a time, and multiply by the largest
|
// therefore, try to parse 8 digits at a time, and multiply by the largest
|
||||||
// scalar value (9 or 19 digits) for each step.
|
// scalar value (9 or 19 digits) for each step.
|
||||||
@ -251,12 +270,13 @@ void parse_mantissa(bigint& result, parsed_number_string_t<UC>& num, size_t max_
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// process all integer digits.
|
// process all integer digits.
|
||||||
UC const * p = num.integer.ptr;
|
UC const *p = num.integer.ptr;
|
||||||
UC const * pend = p + num.integer.len();
|
UC const *pend = p + num.integer.len();
|
||||||
skip_zeros(p, pend);
|
skip_zeros(p, pend);
|
||||||
// process all digits, in increments of step per loop
|
// process all digits, in increments of step per loop
|
||||||
while (p != pend) {
|
while (p != pend) {
|
||||||
while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) {
|
while ((std::distance(p, pend) >= 8) && (step - counter >= 8) &&
|
||||||
|
(max_digits - digits >= 8)) {
|
||||||
parse_eight_digits(p, value, counter, digits);
|
parse_eight_digits(p, value, counter, digits);
|
||||||
}
|
}
|
||||||
while (counter < step && p != pend && digits < max_digits) {
|
while (counter < step && p != pend && digits < max_digits) {
|
||||||
@ -289,7 +309,8 @@ void parse_mantissa(bigint& result, parsed_number_string_t<UC>& num, size_t max_
|
|||||||
}
|
}
|
||||||
// process all digits, in increments of step per loop
|
// process all digits, in increments of step per loop
|
||||||
while (p != pend) {
|
while (p != pend) {
|
||||||
while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) {
|
while ((std::distance(p, pend) >= 8) && (step - counter >= 8) &&
|
||||||
|
(max_digits - digits >= 8)) {
|
||||||
parse_eight_digits(p, value, counter, digits);
|
parse_eight_digits(p, value, counter, digits);
|
||||||
}
|
}
|
||||||
while (counter < step && p != pend && digits < max_digits) {
|
while (counter < step && p != pend && digits < max_digits) {
|
||||||
@ -317,19 +338,23 @@ void parse_mantissa(bigint& result, parsed_number_string_t<UC>& num, size_t max_
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline FASTFLOAT_CONSTEXPR20
|
inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
|
||||||
adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcept {
|
positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept {
|
||||||
FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent)));
|
FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent)));
|
||||||
adjusted_mantissa answer;
|
adjusted_mantissa answer;
|
||||||
bool truncated;
|
bool truncated;
|
||||||
answer.mantissa = bigmant.hi64(truncated);
|
answer.mantissa = bigmant.hi64(truncated);
|
||||||
int bias = binary_format<T>::mantissa_explicit_bits() - binary_format<T>::minimum_exponent();
|
int bias = binary_format<T>::mantissa_explicit_bits() -
|
||||||
|
binary_format<T>::minimum_exponent();
|
||||||
answer.power2 = bigmant.bit_length() - 64 + bias;
|
answer.power2 = bigmant.bit_length() - 64 + bias;
|
||||||
|
|
||||||
round<T>(answer, [truncated](adjusted_mantissa& a, int32_t shift) {
|
round<T>(answer, [truncated](adjusted_mantissa &a, int32_t shift) {
|
||||||
round_nearest_tie_even(a, shift, [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool {
|
round_nearest_tie_even(
|
||||||
return is_above || (is_halfway && truncated) || (is_odd && is_halfway);
|
a, shift,
|
||||||
});
|
[truncated](bool is_odd, bool is_halfway, bool is_above) -> bool {
|
||||||
|
return is_above || (is_halfway && truncated) ||
|
||||||
|
(is_odd && is_halfway);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return answer;
|
return answer;
|
||||||
@ -341,15 +366,17 @@ adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcep
|
|||||||
// we then need to scale by `2^(f- e)`, and then the two significant digits
|
// we then need to scale by `2^(f- e)`, and then the two significant digits
|
||||||
// are of the same magnitude.
|
// are of the same magnitude.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline FASTFLOAT_CONSTEXPR20
|
inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp(
|
||||||
adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int32_t exponent) noexcept {
|
bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept {
|
||||||
bigint& real_digits = bigmant;
|
bigint &real_digits = bigmant;
|
||||||
int32_t real_exp = exponent;
|
int32_t real_exp = exponent;
|
||||||
|
|
||||||
// get the value of `b`, rounded down, and get a bigint representation of b+h
|
// get the value of `b`, rounded down, and get a bigint representation of b+h
|
||||||
adjusted_mantissa am_b = am;
|
adjusted_mantissa am_b = am;
|
||||||
// gcc7 buf: use a lambda to remove the noexcept qualifier bug with -Wnoexcept-type.
|
// gcc7 buf: use a lambda to remove the noexcept qualifier bug with
|
||||||
round<T>(am_b, [](adjusted_mantissa&a, int32_t shift) { round_down(a, shift); });
|
// -Wnoexcept-type.
|
||||||
|
round<T>(am_b,
|
||||||
|
[](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); });
|
||||||
T b;
|
T b;
|
||||||
to_float(false, am_b, b);
|
to_float(false, am_b, b);
|
||||||
adjusted_mantissa theor = to_extended_halfway(b);
|
adjusted_mantissa theor = to_extended_halfway(b);
|
||||||
@ -368,27 +395,28 @@ adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int
|
|||||||
FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp)));
|
FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// compare digits, and use it to director rounding
|
// compare digits, and use it to direct rounding
|
||||||
int ord = real_digits.compare(theor_digits);
|
int ord = real_digits.compare(theor_digits);
|
||||||
adjusted_mantissa answer = am;
|
adjusted_mantissa answer = am;
|
||||||
round<T>(answer, [ord](adjusted_mantissa& a, int32_t shift) {
|
round<T>(answer, [ord](adjusted_mantissa &a, int32_t shift) {
|
||||||
round_nearest_tie_even(a, shift, [ord](bool is_odd, bool _, bool __) -> bool {
|
round_nearest_tie_even(
|
||||||
(void)_; // not needed, since we've done our comparison
|
a, shift, [ord](bool is_odd, bool _, bool __) -> bool {
|
||||||
(void)__; // not needed, since we've done our comparison
|
(void)_; // not needed, since we've done our comparison
|
||||||
if (ord > 0) {
|
(void)__; // not needed, since we've done our comparison
|
||||||
return true;
|
if (ord > 0) {
|
||||||
} else if (ord < 0) {
|
return true;
|
||||||
return false;
|
} else if (ord < 0) {
|
||||||
} else {
|
return false;
|
||||||
return is_odd;
|
} else {
|
||||||
}
|
return is_odd;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the significant digits as a big integer to unambiguously round the
|
// parse the significant digits as a big integer to unambiguously round
|
||||||
// the significant digits. here, we are trying to determine how to round
|
// the significant digits. here, we are trying to determine how to round
|
||||||
// an extended float representation close to `b+h`, halfway between `b`
|
// an extended float representation close to `b+h`, halfway between `b`
|
||||||
// (the float rounded-down) and `b+u`, the next positive float. this
|
// (the float rounded-down) and `b+u`, the next positive float. this
|
||||||
@ -402,12 +430,13 @@ adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int
|
|||||||
// the actual digits. we then compare the big integer representations
|
// the actual digits. we then compare the big integer representations
|
||||||
// of both, and use that to direct rounding.
|
// of both, and use that to direct rounding.
|
||||||
template <typename T, typename UC>
|
template <typename T, typename UC>
|
||||||
inline FASTFLOAT_CONSTEXPR20
|
inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa
|
||||||
adjusted_mantissa digit_comp(parsed_number_string_t<UC>& num, adjusted_mantissa am) noexcept {
|
digit_comp(parsed_number_string_t<UC> &num, adjusted_mantissa am) noexcept {
|
||||||
// remove the invalid exponent bias
|
// remove the invalid exponent bias
|
||||||
am.power2 -= invalid_am_bias;
|
am.power2 -= invalid_am_bias;
|
||||||
|
|
||||||
int32_t sci_exp = scientific_exponent(num);
|
int32_t sci_exp =
|
||||||
|
scientific_exponent(num.mantissa, static_cast<int32_t>(num.exponent));
|
||||||
size_t max_digits = binary_format<T>::max_digits();
|
size_t max_digits = binary_format<T>::max_digits();
|
||||||
size_t digits = 0;
|
size_t digits = 0;
|
||||||
bigint bigmant;
|
bigint bigmant;
|
||||||
|
|||||||
@ -6,43 +6,86 @@
|
|||||||
|
|
||||||
namespace fast_float {
|
namespace fast_float {
|
||||||
/**
|
/**
|
||||||
* This function parses the character sequence [first,last) for a number. It parses floating-point numbers expecting
|
* This function parses the character sequence [first,last) for a number. It
|
||||||
* a locale-indepent format equivalent to what is used by std::strtod in the default ("C") locale.
|
* parses floating-point numbers expecting a locale-indepent format equivalent
|
||||||
* The resulting floating-point value is the closest floating-point values (using either float or double),
|
* to what is used by std::strtod in the default ("C") locale. The resulting
|
||||||
* using the "round to even" convention for values that would otherwise fall right in-between two values.
|
* floating-point value is the closest floating-point values (using either float
|
||||||
* That is, we provide exact parsing according to the IEEE standard.
|
* or double), using the "round to even" convention for values that would
|
||||||
|
* otherwise fall right in-between two values. That is, we provide exact parsing
|
||||||
|
* according to the IEEE standard.
|
||||||
*
|
*
|
||||||
* Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the
|
* Given a successful parse, the pointer (`ptr`) in the returned value is set to
|
||||||
* parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned
|
* point right after the parsed number, and the `value` referenced is set to the
|
||||||
* `ec` contains a representative error, otherwise the default (`std::errc()`) value is stored.
|
* parsed value. In case of error, the returned `ec` contains a representative
|
||||||
|
* error, otherwise the default (`std::errc()`) value is stored.
|
||||||
*
|
*
|
||||||
* The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`).
|
* The implementation does not throw and does not allocate memory (e.g., with
|
||||||
|
* `new` or `malloc`).
|
||||||
*
|
*
|
||||||
* Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of
|
* Like the C++17 standard, the `fast_float::from_chars` functions take an
|
||||||
* the type `fast_float::chars_format`. It is a bitset value: we check whether
|
* optional last argument of the type `fast_float::chars_format`. It is a bitset
|
||||||
* `fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set
|
* value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt &
|
||||||
* to determine whether we allow the fixed point and scientific notation respectively.
|
* fast_float::chars_format::scientific` are set to determine whether we allow
|
||||||
* The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`.
|
* the fixed point and scientific notation respectively. The default is
|
||||||
|
* `fast_float::chars_format::general` which allows both `fixed` and
|
||||||
|
* `scientific`.
|
||||||
*/
|
*/
|
||||||
template<typename T, typename UC = char, typename = FASTFLOAT_ENABLE_IF(is_supported_float_type<T>())>
|
template <typename T, typename UC = char,
|
||||||
FASTFLOAT_CONSTEXPR20
|
typename = FASTFLOAT_ENABLE_IF(is_supported_float_type<T>::value)>
|
||||||
from_chars_result_t<UC> from_chars(UC const * first, UC const * last,
|
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
|
||||||
T &value, chars_format fmt = chars_format::general) noexcept;
|
from_chars(UC const *first, UC const *last, T &value,
|
||||||
|
chars_format fmt = chars_format::general) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like from_chars, but accepts an `options` argument to govern number parsing.
|
* Like from_chars, but accepts an `options` argument to govern number parsing.
|
||||||
|
* Both for floating-point types and integer types.
|
||||||
*/
|
*/
|
||||||
template<typename T, typename UC = char>
|
template <typename T, typename UC = char>
|
||||||
FASTFLOAT_CONSTEXPR20
|
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
|
||||||
from_chars_result_t<UC> from_chars_advanced(UC const * first, UC const * last,
|
from_chars_advanced(UC const *first, UC const *last, T &value,
|
||||||
T &value, parse_options_t<UC> options) noexcept;
|
parse_options_t<UC> options) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* from_chars for integer types.
|
* This function multiplies an integer number by a power of 10 and returns
|
||||||
*/
|
* the result as a double precision floating-point value that is correctly
|
||||||
template <typename T, typename UC = char, typename = FASTFLOAT_ENABLE_IF(!is_supported_float_type<T>())>
|
* rounded. The resulting floating-point value is the closest floating-point
|
||||||
|
* value, using the "round to nearest, tie to even" convention for values that
|
||||||
|
* would otherwise fall right in-between two values. That is, we provide exact
|
||||||
|
* conversion according to the IEEE standard.
|
||||||
|
*
|
||||||
|
* On overflow infinity is returned, on underflow 0 is returned.
|
||||||
|
*
|
||||||
|
* The implementation does not throw and does not allocate memory (e.g., with
|
||||||
|
* `new` or `malloc`).
|
||||||
|
*/
|
||||||
|
FASTFLOAT_CONSTEXPR20 inline double
|
||||||
|
integer_times_pow10(uint64_t mantissa, int decimal_exponent) noexcept;
|
||||||
|
FASTFLOAT_CONSTEXPR20 inline double
|
||||||
|
integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is a template overload of `integer_times_pow10()`
|
||||||
|
* that returns a floating-point value of type `T` that is one of
|
||||||
|
* supported floating-point types (e.g. `double`, `float`).
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
FASTFLOAT_CONSTEXPR20
|
FASTFLOAT_CONSTEXPR20
|
||||||
from_chars_result_t<UC> from_chars(UC const * first, UC const * last, T& value, int base = 10) noexcept;
|
typename std::enable_if<is_supported_float_type<T>::value, T>::type
|
||||||
|
integer_times_pow10(uint64_t mantissa, int decimal_exponent) noexcept;
|
||||||
|
template <typename T>
|
||||||
|
FASTFLOAT_CONSTEXPR20
|
||||||
|
typename std::enable_if<is_supported_float_type<T>::value, T>::type
|
||||||
|
integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* from_chars for integer types.
|
||||||
|
*/
|
||||||
|
template <typename T, typename UC = char,
|
||||||
|
typename = FASTFLOAT_ENABLE_IF(is_supported_integer_type<T>::value)>
|
||||||
|
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
|
||||||
|
from_chars(UC const *first, UC const *last, T &value, int base = 10) noexcept;
|
||||||
|
|
||||||
} // namespace fast_float
|
} // namespace fast_float
|
||||||
|
|
||||||
#include "parse_number.h"
|
#include "parse_number.h"
|
||||||
#endif // FASTFLOAT_FAST_FLOAT_H
|
#endif // FASTFLOAT_FAST_FLOAT_H
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
namespace fast_float {
|
namespace fast_float {
|
||||||
|
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
/**
|
/**
|
||||||
* Special case +inf, -inf, nan, infinity, -infinity.
|
* Special case +inf, -inf, nan, infinity, -infinity.
|
||||||
@ -21,45 +20,49 @@ namespace detail {
|
|||||||
* strings a null-free and fixed.
|
* strings a null-free and fixed.
|
||||||
**/
|
**/
|
||||||
template <typename T, typename UC>
|
template <typename T, typename UC>
|
||||||
from_chars_result_t<UC> FASTFLOAT_CONSTEXPR14
|
from_chars_result_t<UC>
|
||||||
parse_infnan(UC const * first, UC const * last, T &value) noexcept {
|
FASTFLOAT_CONSTEXPR14 parse_infnan(UC const *first, UC const *last,
|
||||||
|
T &value, chars_format fmt) noexcept {
|
||||||
from_chars_result_t<UC> answer{};
|
from_chars_result_t<UC> answer{};
|
||||||
answer.ptr = first;
|
answer.ptr = first;
|
||||||
answer.ec = std::errc(); // be optimistic
|
answer.ec = std::errc(); // be optimistic
|
||||||
bool minusSign = false;
|
// assume first < last, so dereference without checks;
|
||||||
if (*first == UC('-')) { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here
|
bool const minusSign = (*first == UC('-'));
|
||||||
minusSign = true;
|
// C++17 20.19.3.(7.1) explicitly forbids '+' sign here
|
||||||
++first;
|
if ((*first == UC('-')) ||
|
||||||
|
(uint64_t(fmt & chars_format::allow_leading_plus) &&
|
||||||
|
(*first == UC('+')))) {
|
||||||
|
++first;
|
||||||
}
|
}
|
||||||
#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default
|
|
||||||
if (*first == UC('+')) {
|
|
||||||
++first;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (last - first >= 3) {
|
if (last - first >= 3) {
|
||||||
if (fastfloat_strncasecmp(first, str_const_nan<UC>(), 3)) {
|
if (fastfloat_strncasecmp(first, str_const_nan<UC>(), 3)) {
|
||||||
answer.ptr = (first += 3);
|
answer.ptr = (first += 3);
|
||||||
value = minusSign ? -std::numeric_limits<T>::quiet_NaN() : std::numeric_limits<T>::quiet_NaN();
|
value = minusSign ? -std::numeric_limits<T>::quiet_NaN()
|
||||||
// Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan).
|
: std::numeric_limits<T>::quiet_NaN();
|
||||||
if(first != last && *first == UC('(')) {
|
// Check for possible nan(n-char-seq-opt), C++17 20.19.3.7,
|
||||||
for(UC const * ptr = first + 1; ptr != last; ++ptr) {
|
// C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan).
|
||||||
|
if (first != last && *first == UC('(')) {
|
||||||
|
for (UC const *ptr = first + 1; ptr != last; ++ptr) {
|
||||||
if (*ptr == UC(')')) {
|
if (*ptr == UC(')')) {
|
||||||
answer.ptr = ptr + 1; // valid nan(n-char-seq-opt)
|
answer.ptr = ptr + 1; // valid nan(n-char-seq-opt)
|
||||||
break;
|
break;
|
||||||
}
|
} else if (!((UC('a') <= *ptr && *ptr <= UC('z')) ||
|
||||||
else if(!((UC('a') <= *ptr && *ptr <= UC('z')) || (UC('A') <= *ptr && *ptr <= UC('Z')) || (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_')))
|
(UC('A') <= *ptr && *ptr <= UC('Z')) ||
|
||||||
|
(UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_')))
|
||||||
break; // forbidden char, not nan(n-char-seq-opt)
|
break; // forbidden char, not nan(n-char-seq-opt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
if (fastfloat_strncasecmp(first, str_const_inf<UC>(), 3)) {
|
if (fastfloat_strncasecmp(first, str_const_inf<UC>(), 3)) {
|
||||||
if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, str_const_inf<UC>() + 3, 5)) {
|
if ((last - first >= 8) &&
|
||||||
|
fastfloat_strncasecmp(first + 3, str_const_inf<UC>() + 3, 5)) {
|
||||||
answer.ptr = first + 8;
|
answer.ptr = first + 8;
|
||||||
} else {
|
} else {
|
||||||
answer.ptr = first + 3;
|
answer.ptr = first + 3;
|
||||||
}
|
}
|
||||||
value = minusSign ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::infinity();
|
value = minusSign ? -std::numeric_limits<T>::infinity()
|
||||||
|
: std::numeric_limits<T>::infinity();
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,97 +90,115 @@ fastfloat_really_inline bool rounds_to_nearest() noexcept {
|
|||||||
// However, it is expected to be much faster than the fegetround()
|
// However, it is expected to be much faster than the fegetround()
|
||||||
// function call.
|
// function call.
|
||||||
//
|
//
|
||||||
// The volatile keywoard prevents the compiler from computing the function
|
// The volatile keyword prevents the compiler from computing the function
|
||||||
// at compile-time.
|
// at compile-time.
|
||||||
// There might be other ways to prevent compile-time optimizations (e.g., asm).
|
// There might be other ways to prevent compile-time optimizations (e.g.,
|
||||||
// The value does not need to be std::numeric_limits<float>::min(), any small
|
// asm). The value does not need to be std::numeric_limits<float>::min(), any
|
||||||
// value so that 1 + x should round to 1 would do (after accounting for excess
|
// small value so that 1 + x should round to 1 would do (after accounting for
|
||||||
// precision, as in 387 instructions).
|
// excess precision, as in 387 instructions).
|
||||||
static volatile float fmin = std::numeric_limits<float>::min();
|
static float volatile fmin = std::numeric_limits<float>::min();
|
||||||
float fmini = fmin; // we copy it so that it gets loaded at most once.
|
float fmini = fmin; // we copy it so that it gets loaded at most once.
|
||||||
//
|
//
|
||||||
// Explanation:
|
// Explanation:
|
||||||
// Only when fegetround() == FE_TONEAREST do we have that
|
// Only when fegetround() == FE_TONEAREST do we have that
|
||||||
// fmin + 1.0f == 1.0f - fmin.
|
// fmin + 1.0f == 1.0f - fmin.
|
||||||
//
|
//
|
||||||
// FE_UPWARD:
|
// FE_UPWARD:
|
||||||
// fmin + 1.0f > 1
|
// fmin + 1.0f > 1
|
||||||
// 1.0f - fmin == 1
|
// 1.0f - fmin == 1
|
||||||
//
|
//
|
||||||
// FE_DOWNWARD or FE_TOWARDZERO:
|
// FE_DOWNWARD or FE_TOWARDZERO:
|
||||||
// fmin + 1.0f == 1
|
// fmin + 1.0f == 1
|
||||||
// 1.0f - fmin < 1
|
// 1.0f - fmin < 1
|
||||||
//
|
//
|
||||||
// Note: This may fail to be accurate if fast-math has been
|
// Note: This may fail to be accurate if fast-math has been
|
||||||
// enabled, as rounding conventions may not apply.
|
// enabled, as rounding conventions may not apply.
|
||||||
#ifdef FASTFLOAT_VISUAL_STUDIO
|
#ifdef FASTFLOAT_VISUAL_STUDIO
|
||||||
# pragma warning(push)
|
#pragma warning(push)
|
||||||
// todo: is there a VS warning?
|
// todo: is there a VS warning?
|
||||||
// see https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013
|
// see
|
||||||
#elif defined(__clang__)
|
// https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013
|
||||||
# pragma clang diagnostic push
|
#elif defined(__clang__)
|
||||||
# pragma clang diagnostic ignored "-Wfloat-equal"
|
#pragma clang diagnostic push
|
||||||
#elif defined(__GNUC__)
|
#pragma clang diagnostic ignored "-Wfloat-equal"
|
||||||
# pragma GCC diagnostic push
|
#elif defined(__GNUC__)
|
||||||
# pragma GCC diagnostic ignored "-Wfloat-equal"
|
#pragma GCC diagnostic push
|
||||||
#endif
|
#pragma GCC diagnostic ignored "-Wfloat-equal"
|
||||||
|
#endif
|
||||||
return (fmini + 1.0f == 1.0f - fmini);
|
return (fmini + 1.0f == 1.0f - fmini);
|
||||||
#ifdef FASTFLOAT_VISUAL_STUDIO
|
#ifdef FASTFLOAT_VISUAL_STUDIO
|
||||||
# pragma warning(pop)
|
#pragma warning(pop)
|
||||||
#elif defined(__clang__)
|
#elif defined(__clang__)
|
||||||
# pragma clang diagnostic pop
|
#pragma clang diagnostic pop
|
||||||
#elif defined(__GNUC__)
|
#elif defined(__GNUC__)
|
||||||
# pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template<typename T, typename UC, typename>
|
template <typename T> struct from_chars_caller {
|
||||||
FASTFLOAT_CONSTEXPR20
|
template <typename UC>
|
||||||
from_chars_result_t<UC> from_chars(UC const * first, UC const * last,
|
FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
|
||||||
T &value, chars_format fmt /*= chars_format::general*/) noexcept {
|
call(UC const *first, UC const *last, T &value,
|
||||||
return from_chars_advanced(first, last, value, parse_options_t<UC>{fmt});
|
parse_options_t<UC> options) noexcept {
|
||||||
|
return from_chars_advanced(first, last, value, options);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __STDCPP_FLOAT32_T__
|
||||||
|
template <> struct from_chars_caller<std::float32_t> {
|
||||||
|
template <typename UC>
|
||||||
|
FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
|
||||||
|
call(UC const *first, UC const *last, std::float32_t &value,
|
||||||
|
parse_options_t<UC> options) noexcept {
|
||||||
|
// if std::float32_t is defined, and we are in C++23 mode; macro set for
|
||||||
|
// float32; set value to float due to equivalence between float and
|
||||||
|
// float32_t
|
||||||
|
float val;
|
||||||
|
auto ret = from_chars_advanced(first, last, val, options);
|
||||||
|
value = val;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __STDCPP_FLOAT64_T__
|
||||||
|
template <> struct from_chars_caller<std::float64_t> {
|
||||||
|
template <typename UC>
|
||||||
|
FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
|
||||||
|
call(UC const *first, UC const *last, std::float64_t &value,
|
||||||
|
parse_options_t<UC> options) noexcept {
|
||||||
|
// if std::float64_t is defined, and we are in C++23 mode; macro set for
|
||||||
|
// float64; set value as double due to equivalence between double and
|
||||||
|
// float64_t
|
||||||
|
double val;
|
||||||
|
auto ret = from_chars_advanced(first, last, val, options);
|
||||||
|
value = val;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename T, typename UC, typename>
|
||||||
|
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
|
||||||
|
from_chars(UC const *first, UC const *last, T &value,
|
||||||
|
chars_format fmt /*= chars_format::general*/) noexcept {
|
||||||
|
return from_chars_caller<T>::call(first, last, value,
|
||||||
|
parse_options_t<UC>(fmt));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename UC>
|
template <typename T>
|
||||||
FASTFLOAT_CONSTEXPR20
|
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
|
||||||
from_chars_result_t<UC> from_chars_advanced(UC const * first, UC const * last,
|
clinger_fast_path_impl(uint64_t mantissa, int64_t exponent, bool is_negative,
|
||||||
T &value, parse_options_t<UC> options) noexcept {
|
T &value) noexcept {
|
||||||
|
|
||||||
static_assert (is_supported_float_type<T>(), "only float and double are supported");
|
|
||||||
static_assert (is_supported_char_type<UC>(), "only char, wchar_t, char16_t and char32_t are supported");
|
|
||||||
|
|
||||||
from_chars_result_t<UC> answer;
|
|
||||||
#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default
|
|
||||||
while ((first != last) && fast_float::is_space(uint8_t(*first))) {
|
|
||||||
first++;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (first == last) {
|
|
||||||
answer.ec = std::errc::invalid_argument;
|
|
||||||
answer.ptr = first;
|
|
||||||
return answer;
|
|
||||||
}
|
|
||||||
parsed_number_string_t<UC> pns = parse_number_string<UC>(first, last, options);
|
|
||||||
if (!pns.valid) {
|
|
||||||
if (options.format & chars_format::no_infnan) {
|
|
||||||
answer.ec = std::errc::invalid_argument;
|
|
||||||
answer.ptr = first;
|
|
||||||
return answer;
|
|
||||||
} else {
|
|
||||||
return detail::parse_infnan(first, last, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
answer.ec = std::errc(); // be optimistic
|
|
||||||
answer.ptr = pns.lastmatch;
|
|
||||||
// The implementation of the Clinger's fast path is convoluted because
|
// The implementation of the Clinger's fast path is convoluted because
|
||||||
// we want round-to-nearest in all cases, irrespective of the rounding mode
|
// we want round-to-nearest in all cases, irrespective of the rounding mode
|
||||||
// selected on the thread.
|
// selected on the thread.
|
||||||
// We proceed optimistically, assuming that detail::rounds_to_nearest() returns
|
// We proceed optimistically, assuming that detail::rounds_to_nearest()
|
||||||
// true.
|
// returns true.
|
||||||
if (binary_format<T>::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format<T>::max_exponent_fast_path() && !pns.too_many_digits) {
|
if (binary_format<T>::min_exponent_fast_path() <= exponent &&
|
||||||
|
exponent <= binary_format<T>::max_exponent_fast_path()) {
|
||||||
// Unfortunately, the conventional Clinger's fast path is only possible
|
// Unfortunately, the conventional Clinger's fast path is only possible
|
||||||
// when the system rounds to the nearest float.
|
// when the system rounds to the nearest float.
|
||||||
//
|
//
|
||||||
@ -185,69 +206,280 @@ from_chars_result_t<UC> from_chars_advanced(UC const * first, UC const * last,
|
|||||||
// We could check it first (before the previous branch), but
|
// We could check it first (before the previous branch), but
|
||||||
// there might be performance advantages at having the check
|
// there might be performance advantages at having the check
|
||||||
// be last.
|
// be last.
|
||||||
if(!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) {
|
if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) {
|
||||||
// We have that fegetround() == FE_TONEAREST.
|
// We have that fegetround() == FE_TONEAREST.
|
||||||
// Next is Clinger's fast path.
|
// Next is Clinger's fast path.
|
||||||
if (pns.mantissa <=binary_format<T>::max_mantissa_fast_path()) {
|
if (mantissa <= binary_format<T>::max_mantissa_fast_path()) {
|
||||||
value = T(pns.mantissa);
|
value = T(mantissa);
|
||||||
if (pns.exponent < 0) { value = value / binary_format<T>::exact_power_of_ten(-pns.exponent); }
|
if (exponent < 0) {
|
||||||
else { value = value * binary_format<T>::exact_power_of_ten(pns.exponent); }
|
value = value / binary_format<T>::exact_power_of_ten(-exponent);
|
||||||
if (pns.negative) { value = -value; }
|
} else {
|
||||||
return answer;
|
value = value * binary_format<T>::exact_power_of_ten(exponent);
|
||||||
|
}
|
||||||
|
if (is_negative) {
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We do not have that fegetround() == FE_TONEAREST.
|
// We do not have that fegetround() == FE_TONEAREST.
|
||||||
// Next is a modified Clinger's fast path, inspired by Jakub Jelínek's proposal
|
// Next is a modified Clinger's fast path, inspired by Jakub Jelínek's
|
||||||
if (pns.exponent >= 0 && pns.mantissa <=binary_format<T>::max_mantissa_fast_path(pns.exponent)) {
|
// proposal
|
||||||
|
if (exponent >= 0 &&
|
||||||
|
mantissa <= binary_format<T>::max_mantissa_fast_path(exponent)) {
|
||||||
#if defined(__clang__) || defined(FASTFLOAT_32BIT)
|
#if defined(__clang__) || defined(FASTFLOAT_32BIT)
|
||||||
// Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD
|
// Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD
|
||||||
if(pns.mantissa == 0) {
|
if (mantissa == 0) {
|
||||||
value = pns.negative ? T(-0.) : T(0.);
|
value = is_negative ? T(-0.) : T(0.);
|
||||||
return answer;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
value = T(pns.mantissa) * binary_format<T>::exact_power_of_ten(pns.exponent);
|
value = T(mantissa) * binary_format<T>::exact_power_of_ten(exponent);
|
||||||
if (pns.negative) { value = -value; }
|
if (is_negative) {
|
||||||
return answer;
|
value = -value;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adjusted_mantissa am = compute_float<binary_format<T>>(pns.exponent, pns.mantissa);
|
return false;
|
||||||
if(pns.too_many_digits && am.power2 >= 0) {
|
}
|
||||||
if(am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) {
|
|
||||||
|
/**
|
||||||
|
* This function overload takes parsed_number_string_t structure that is created
|
||||||
|
* and populated either by from_chars_advanced function taking chars range and
|
||||||
|
* parsing options or other parsing custom function implemented by user.
|
||||||
|
*/
|
||||||
|
template <typename T, typename UC>
|
||||||
|
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
|
||||||
|
from_chars_advanced(parsed_number_string_t<UC> &pns, T &value) noexcept {
|
||||||
|
static_assert(is_supported_float_type<T>::value,
|
||||||
|
"only some floating-point types are supported");
|
||||||
|
static_assert(is_supported_char_type<UC>::value,
|
||||||
|
"only char, wchar_t, char16_t and char32_t are supported");
|
||||||
|
|
||||||
|
from_chars_result_t<UC> answer;
|
||||||
|
|
||||||
|
answer.ec = std::errc(); // be optimistic
|
||||||
|
answer.ptr = pns.lastmatch;
|
||||||
|
|
||||||
|
if (!pns.too_many_digits &&
|
||||||
|
clinger_fast_path_impl(pns.mantissa, pns.exponent, pns.negative, value))
|
||||||
|
return answer;
|
||||||
|
|
||||||
|
adjusted_mantissa am =
|
||||||
|
compute_float<binary_format<T>>(pns.exponent, pns.mantissa);
|
||||||
|
if (pns.too_many_digits && am.power2 >= 0) {
|
||||||
|
if (am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) {
|
||||||
am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa);
|
am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we called compute_float<binary_format<T>>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0),
|
// If we called compute_float<binary_format<T>>(pns.exponent, pns.mantissa)
|
||||||
// then we need to go the long way around again. This is very uncommon.
|
// and we have an invalid power (am.power2 < 0), then we need to go the long
|
||||||
if(am.power2 < 0) { am = digit_comp<T>(pns, am); }
|
// way around again. This is very uncommon.
|
||||||
|
if (am.power2 < 0) {
|
||||||
|
am = digit_comp<T>(pns, am);
|
||||||
|
}
|
||||||
to_float(pns.negative, am, value);
|
to_float(pns.negative, am, value);
|
||||||
// Test for over/underflow.
|
// Test for over/underflow.
|
||||||
if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || am.power2 == binary_format<T>::infinite_power()) {
|
if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) ||
|
||||||
|
am.power2 == binary_format<T>::infinite_power()) {
|
||||||
answer.ec = std::errc::result_out_of_range;
|
answer.ec = std::errc::result_out_of_range;
|
||||||
}
|
}
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T, typename UC>
|
||||||
|
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
|
||||||
|
from_chars_float_advanced(UC const *first, UC const *last, T &value,
|
||||||
|
parse_options_t<UC> options) noexcept {
|
||||||
|
|
||||||
template <typename T, typename UC, typename>
|
static_assert(is_supported_float_type<T>::value,
|
||||||
FASTFLOAT_CONSTEXPR20
|
"only some floating-point types are supported");
|
||||||
from_chars_result_t<UC> from_chars(UC const* first, UC const* last, T& value, int base) noexcept
|
static_assert(is_supported_char_type<UC>::value,
|
||||||
{
|
"only char, wchar_t, char16_t and char32_t are supported");
|
||||||
static_assert (is_supported_char_type<UC>(), "only char, wchar_t, char16_t and char32_t are supported");
|
|
||||||
|
chars_format const fmt = detail::adjust_for_feature_macros(options.format);
|
||||||
|
|
||||||
from_chars_result_t<UC> answer;
|
from_chars_result_t<UC> answer;
|
||||||
#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default
|
if (uint64_t(fmt & chars_format::skip_white_space)) {
|
||||||
while ((first != last) && fast_float::is_space(uint8_t(*first))) {
|
while ((first != last) && fast_float::is_space(*first)) {
|
||||||
first++;
|
first++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (first == last) {
|
||||||
|
answer.ec = std::errc::invalid_argument;
|
||||||
|
answer.ptr = first;
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
parsed_number_string_t<UC> pns =
|
||||||
|
uint64_t(fmt & detail::basic_json_fmt)
|
||||||
|
? parse_number_string<true, UC>(first, last, options)
|
||||||
|
: parse_number_string<false, UC>(first, last, options);
|
||||||
|
if (!pns.valid) {
|
||||||
|
if (uint64_t(fmt & chars_format::no_infnan)) {
|
||||||
|
answer.ec = std::errc::invalid_argument;
|
||||||
|
answer.ptr = first;
|
||||||
|
return answer;
|
||||||
|
} else {
|
||||||
|
return detail::parse_infnan(first, last, value, fmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// call overload that takes parsed_number_string_t directly.
|
||||||
|
return from_chars_advanced(pns, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename UC, typename>
|
||||||
|
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
|
||||||
|
from_chars(UC const *first, UC const *last, T &value, int base) noexcept {
|
||||||
|
|
||||||
|
static_assert(is_supported_integer_type<T>::value,
|
||||||
|
"only integer types are supported");
|
||||||
|
static_assert(is_supported_char_type<UC>::value,
|
||||||
|
"only char, wchar_t, char16_t and char32_t are supported");
|
||||||
|
|
||||||
|
parse_options_t<UC> options;
|
||||||
|
options.base = base;
|
||||||
|
return from_chars_advanced(first, last, value, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
FASTFLOAT_CONSTEXPR20
|
||||||
|
typename std::enable_if<is_supported_float_type<T>::value, T>::type
|
||||||
|
integer_times_pow10(uint64_t mantissa, int decimal_exponent) noexcept {
|
||||||
|
T value;
|
||||||
|
if (clinger_fast_path_impl(mantissa, decimal_exponent, false, value))
|
||||||
|
return value;
|
||||||
|
|
||||||
|
adjusted_mantissa am =
|
||||||
|
compute_float<binary_format<T>>(decimal_exponent, mantissa);
|
||||||
|
to_float(false, am, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
FASTFLOAT_CONSTEXPR20
|
||||||
|
typename std::enable_if<is_supported_float_type<T>::value, T>::type
|
||||||
|
integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept {
|
||||||
|
const bool is_negative = mantissa < 0;
|
||||||
|
const uint64_t m = static_cast<uint64_t>(is_negative ? -mantissa : mantissa);
|
||||||
|
|
||||||
|
T value;
|
||||||
|
if (clinger_fast_path_impl(m, decimal_exponent, is_negative, value))
|
||||||
|
return value;
|
||||||
|
|
||||||
|
adjusted_mantissa am = compute_float<binary_format<T>>(decimal_exponent, m);
|
||||||
|
to_float(is_negative, am, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
FASTFLOAT_CONSTEXPR20 inline double
|
||||||
|
integer_times_pow10(uint64_t mantissa, int decimal_exponent) noexcept {
|
||||||
|
return integer_times_pow10<double>(mantissa, decimal_exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
FASTFLOAT_CONSTEXPR20 inline double
|
||||||
|
integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept {
|
||||||
|
return integer_times_pow10<double>(mantissa, decimal_exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the following overloads are here to avoid surprising ambiguity for int,
|
||||||
|
// unsigned, etc.
|
||||||
|
template <typename T, typename Int>
|
||||||
|
FASTFLOAT_CONSTEXPR20
|
||||||
|
typename std::enable_if<is_supported_float_type<T>::value &&
|
||||||
|
std::is_integral<Int>::value &&
|
||||||
|
!std::is_signed<Int>::value,
|
||||||
|
T>::type
|
||||||
|
integer_times_pow10(Int mantissa, int decimal_exponent) noexcept {
|
||||||
|
return integer_times_pow10<T>(static_cast<uint64_t>(mantissa),
|
||||||
|
decimal_exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Int>
|
||||||
|
FASTFLOAT_CONSTEXPR20
|
||||||
|
typename std::enable_if<is_supported_float_type<T>::value &&
|
||||||
|
std::is_integral<Int>::value &&
|
||||||
|
std::is_signed<Int>::value,
|
||||||
|
T>::type
|
||||||
|
integer_times_pow10(Int mantissa, int decimal_exponent) noexcept {
|
||||||
|
return integer_times_pow10<T>(static_cast<int64_t>(mantissa),
|
||||||
|
decimal_exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Int>
|
||||||
|
FASTFLOAT_CONSTEXPR20 typename std::enable_if<
|
||||||
|
std::is_integral<Int>::value && !std::is_signed<Int>::value, double>::type
|
||||||
|
integer_times_pow10(Int mantissa, int decimal_exponent) noexcept {
|
||||||
|
return integer_times_pow10(static_cast<uint64_t>(mantissa), decimal_exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Int>
|
||||||
|
FASTFLOAT_CONSTEXPR20 typename std::enable_if<
|
||||||
|
std::is_integral<Int>::value && std::is_signed<Int>::value, double>::type
|
||||||
|
integer_times_pow10(Int mantissa, int decimal_exponent) noexcept {
|
||||||
|
return integer_times_pow10(static_cast<int64_t>(mantissa), decimal_exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename UC>
|
||||||
|
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
|
||||||
|
from_chars_int_advanced(UC const *first, UC const *last, T &value,
|
||||||
|
parse_options_t<UC> options) noexcept {
|
||||||
|
|
||||||
|
static_assert(is_supported_integer_type<T>::value,
|
||||||
|
"only integer types are supported");
|
||||||
|
static_assert(is_supported_char_type<UC>::value,
|
||||||
|
"only char, wchar_t, char16_t and char32_t are supported");
|
||||||
|
|
||||||
|
chars_format const fmt = detail::adjust_for_feature_macros(options.format);
|
||||||
|
int const base = options.base;
|
||||||
|
|
||||||
|
from_chars_result_t<UC> answer;
|
||||||
|
if (uint64_t(fmt & chars_format::skip_white_space)) {
|
||||||
|
while ((first != last) && fast_float::is_space(*first)) {
|
||||||
|
first++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
if (first == last || base < 2 || base > 36) {
|
if (first == last || base < 2 || base > 36) {
|
||||||
answer.ec = std::errc::invalid_argument;
|
answer.ec = std::errc::invalid_argument;
|
||||||
answer.ptr = first;
|
answer.ptr = first;
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
return parse_int_string(first, last, value, base);
|
|
||||||
|
return parse_int_string(first, last, value, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t TypeIx> struct from_chars_advanced_caller {
|
||||||
|
static_assert(TypeIx > 0, "unsupported type");
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct from_chars_advanced_caller<1> {
|
||||||
|
template <typename T, typename UC>
|
||||||
|
FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
|
||||||
|
call(UC const *first, UC const *last, T &value,
|
||||||
|
parse_options_t<UC> options) noexcept {
|
||||||
|
return from_chars_float_advanced(first, last, value, options);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct from_chars_advanced_caller<2> {
|
||||||
|
template <typename T, typename UC>
|
||||||
|
FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
|
||||||
|
call(UC const *first, UC const *last, T &value,
|
||||||
|
parse_options_t<UC> options) noexcept {
|
||||||
|
return from_chars_int_advanced(first, last, value, options);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename UC>
|
||||||
|
FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC>
|
||||||
|
from_chars_advanced(UC const *first, UC const *last, T &value,
|
||||||
|
parse_options_t<UC> options) noexcept {
|
||||||
|
return from_chars_advanced_caller<
|
||||||
|
size_t(is_supported_float_type<T>::value) +
|
||||||
|
2 * size_t(is_supported_integer_type<T>::value)>::call(first, last, value,
|
||||||
|
options);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fast_float
|
} // namespace fast_float
|
||||||
|
|||||||
@ -1,97 +1,122 @@
|
|||||||
# text parts
|
# text parts
|
||||||
processed_files = { }
|
processed_files = {}
|
||||||
|
|
||||||
# authors
|
# authors
|
||||||
for filename in ['AUTHORS', 'CONTRIBUTORS']:
|
for filename in ["AUTHORS", "CONTRIBUTORS"]:
|
||||||
with open(filename, encoding='utf8') as f:
|
with open(filename, encoding="utf8") as f:
|
||||||
text = ''
|
text = ""
|
||||||
for line in f:
|
for line in f:
|
||||||
if filename == 'AUTHORS':
|
if filename == "AUTHORS":
|
||||||
text += '// fast_float by ' + line
|
text += "// fast_float by " + line
|
||||||
if filename == 'CONTRIBUTORS':
|
if filename == "CONTRIBUTORS":
|
||||||
text += '// with contributions from ' + line
|
text += "// with contributions from " + line
|
||||||
processed_files[filename] = text + '//\n//\n'
|
processed_files[filename] = text + "//\n//\n"
|
||||||
|
|
||||||
# licenses
|
# licenses
|
||||||
for filename in ['LICENSE-MIT', 'LICENSE-APACHE', 'LICENSE-BOOST']:
|
for filename in ["LICENSE-MIT", "LICENSE-APACHE", "LICENSE-BOOST"]:
|
||||||
lines = []
|
lines = []
|
||||||
with open(filename, encoding='utf8') as f:
|
with open(filename, encoding="utf8") as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
|
|
||||||
# Retrieve subset required for inclusion in source
|
# Retrieve subset required for inclusion in source
|
||||||
if filename == 'LICENSE-APACHE':
|
if filename == "LICENSE-APACHE":
|
||||||
lines = [
|
lines = [" Copyright 2021 The fast_float authors\n", *lines[179:-1]]
|
||||||
' Copyright 2021 The fast_float authors\n',
|
|
||||||
*lines[179:-1]
|
|
||||||
]
|
|
||||||
|
|
||||||
text = ''
|
text = ""
|
||||||
for line in lines:
|
for line in lines:
|
||||||
text += '// ' + line.strip() + '\n'
|
line = line.strip()
|
||||||
processed_files[filename] = text
|
if len(line):
|
||||||
|
line = " " + line
|
||||||
|
text += "//" + line + "\n"
|
||||||
|
processed_files[filename] = text
|
||||||
|
|
||||||
# code
|
# code
|
||||||
for filename in [ 'constexpr_feature_detect.h', 'float_common.h', 'fast_float.h', 'ascii_number.h',
|
for filename in [
|
||||||
'fast_table.h', 'decimal_to_binary.h', 'bigint.h', 'digit_comparison.h', 'parse_number.h']:
|
"constexpr_feature_detect.h",
|
||||||
with open('include/fast_float/' + filename, encoding='utf8') as f:
|
"float_common.h",
|
||||||
text = ''
|
"fast_float.h",
|
||||||
for line in f:
|
"ascii_number.h",
|
||||||
if line.startswith('#include "'): continue
|
"fast_table.h",
|
||||||
text += line
|
"decimal_to_binary.h",
|
||||||
processed_files[filename] = '\n' + text
|
"bigint.h",
|
||||||
|
"digit_comparison.h",
|
||||||
|
"parse_number.h",
|
||||||
|
]:
|
||||||
|
with open("include/fast_float/" + filename, encoding="utf8") as f:
|
||||||
|
text = ""
|
||||||
|
for line in f:
|
||||||
|
if line.startswith('#include "'):
|
||||||
|
continue
|
||||||
|
text += line
|
||||||
|
processed_files[filename] = "\n" + text
|
||||||
|
|
||||||
# command line
|
# command line
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Amalgamate fast_float.')
|
parser = argparse.ArgumentParser(description="Amalgamate fast_float.")
|
||||||
parser.add_argument('--license', default='TRIPLE', choices=['DUAL', 'TRIPLE', 'MIT', 'APACHE', 'BOOST'], help='choose license')
|
parser.add_argument(
|
||||||
parser.add_argument('--output', default='', help='output file (stdout if none')
|
"--license",
|
||||||
|
default="TRIPLE",
|
||||||
|
choices=["DUAL", "TRIPLE", "MIT", "APACHE", "BOOST"],
|
||||||
|
help="choose license",
|
||||||
|
)
|
||||||
|
parser.add_argument("--output", default="", help="output file (stdout if none")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def license_content(license_arg):
|
def license_content(license_arg):
|
||||||
result = []
|
result = []
|
||||||
if license_arg == 'TRIPLE':
|
if license_arg == "TRIPLE":
|
||||||
result += [
|
result += [
|
||||||
'// Licensed under the Apache License, Version 2.0, or the\n',
|
"// Licensed under the Apache License, Version 2.0, or the\n",
|
||||||
'// MIT License or the Boost License. This file may not be copied,\n',
|
"// MIT License or the Boost License. This file may not be copied,\n",
|
||||||
'// modified, or distributed except according to those terms.\n',
|
"// modified, or distributed except according to those terms.\n",
|
||||||
'//\n'
|
"//\n",
|
||||||
|
]
|
||||||
|
if license_arg == "DUAL":
|
||||||
|
result += [
|
||||||
|
"// Licensed under the Apache License, Version 2.0, or the\n",
|
||||||
|
"// MIT License at your option. This file may not be copied,\n",
|
||||||
|
"// modified, or distributed except according to those terms.\n",
|
||||||
|
"//\n",
|
||||||
|
]
|
||||||
|
|
||||||
|
if license_arg in ("DUAL", "TRIPLE", "MIT"):
|
||||||
|
result.append("// MIT License Notice\n//\n")
|
||||||
|
result.append(processed_files["LICENSE-MIT"])
|
||||||
|
result.append("//\n")
|
||||||
|
if license_arg in ("DUAL", "TRIPLE", "APACHE"):
|
||||||
|
result.append("// Apache License (Version 2.0) Notice\n//\n")
|
||||||
|
result.append(processed_files["LICENSE-APACHE"])
|
||||||
|
result.append("//\n")
|
||||||
|
if license_arg in ("TRIPLE", "BOOST"):
|
||||||
|
result.append("// BOOST License Notice\n//\n")
|
||||||
|
result.append(processed_files["LICENSE-BOOST"])
|
||||||
|
result.append("//\n")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
text = "".join(
|
||||||
|
[
|
||||||
|
processed_files["AUTHORS"],
|
||||||
|
processed_files["CONTRIBUTORS"],
|
||||||
|
*license_content(args.license),
|
||||||
|
processed_files["constexpr_feature_detect.h"],
|
||||||
|
processed_files["float_common.h"],
|
||||||
|
processed_files["fast_float.h"],
|
||||||
|
processed_files["ascii_number.h"],
|
||||||
|
processed_files["fast_table.h"],
|
||||||
|
processed_files["decimal_to_binary.h"],
|
||||||
|
processed_files["bigint.h"],
|
||||||
|
processed_files["digit_comparison.h"],
|
||||||
|
processed_files["parse_number.h"],
|
||||||
]
|
]
|
||||||
if license_arg == 'DUAL':
|
)
|
||||||
result += [
|
|
||||||
'// Licensed under the Apache License, Version 2.0, or the\n',
|
|
||||||
'// MIT License at your option. This file may not be copied,\n',
|
|
||||||
'// modified, or distributed except according to those terms.\n',
|
|
||||||
'//\n'
|
|
||||||
]
|
|
||||||
|
|
||||||
if license_arg in ('DUAL', 'TRIPLE', 'MIT'):
|
|
||||||
result.append('// MIT License Notice\n//\n')
|
|
||||||
result.append(processed_files['LICENSE-MIT'])
|
|
||||||
result.append('//\n')
|
|
||||||
if license_arg in ('DUAL', 'TRIPLE', 'APACHE'):
|
|
||||||
result.append('// Apache License (Version 2.0) Notice\n//\n')
|
|
||||||
result.append(processed_files['LICENSE-APACHE'])
|
|
||||||
result.append('//\n')
|
|
||||||
if license_arg in ('TRIPLE', 'BOOST'):
|
|
||||||
result.append('// BOOST License Notice\n//\n')
|
|
||||||
result.append(processed_files['LICENSE-BOOST'])
|
|
||||||
result.append('//\n')
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
text = ''.join([
|
|
||||||
processed_files['AUTHORS'], processed_files['CONTRIBUTORS'],
|
|
||||||
*license_content(args.license),
|
|
||||||
processed_files['constexpr_feature_detect.h'],
|
|
||||||
processed_files['float_common.h'], processed_files['fast_float.h'],
|
|
||||||
processed_files['ascii_number.h'], processed_files['fast_table.h'],
|
|
||||||
processed_files['decimal_to_binary.h'], processed_files['bigint.h'],
|
|
||||||
processed_files['digit_comparison.h'], processed_files['parse_number.h']])
|
|
||||||
|
|
||||||
if args.output:
|
if args.output:
|
||||||
with open(args.output, 'wt', encoding='utf8') as f:
|
with open(args.output, "wt", encoding="utf8") as f:
|
||||||
f.write(text)
|
f.write(text)
|
||||||
else:
|
else:
|
||||||
print(text)
|
print(text)
|
||||||
|
|||||||
@ -1,36 +1,38 @@
|
|||||||
|
import sys
|
||||||
from math import floor
|
from math import floor
|
||||||
|
|
||||||
|
|
||||||
def log2(x):
|
def log2(x):
|
||||||
"""returns ceil(log2(x)))"""
|
"""returns ceil(log2(x)))"""
|
||||||
y = 0
|
y = 0
|
||||||
while((1<<y) < x):
|
while (1 << y) < x:
|
||||||
y = y + 1
|
y = y + 1
|
||||||
return y
|
return y
|
||||||
|
|
||||||
|
|
||||||
for q in range(1,17+1):
|
for q in range(1, 17 + 1):
|
||||||
d = 5**q
|
d = 5 ** q
|
||||||
b = 127 + log2(d)
|
b = 127 + log2(d)
|
||||||
t = 2** b
|
t = 2 ** b
|
||||||
c = t//d + 1
|
c = t // d + 1
|
||||||
assert c < 2**128
|
assert c < 2 ** 128
|
||||||
assert c >= 2**127
|
assert c >= 2 ** 127
|
||||||
K = 2**127
|
K = 2 ** 127
|
||||||
if(not(c * K * d<=( K + 1) * t)):
|
if not (c * K * d <= (K + 1) * t):
|
||||||
print(q)
|
print(q)
|
||||||
top = floor(t/(c * d - t))
|
top = floor(t / (c * d - t))
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
for q in range(18, 344+1):
|
for q in range(18, 344 + 1):
|
||||||
d = 5**q
|
d = 5 ** q
|
||||||
b = 64 + 2*log2(d)
|
b = 64 + 2 * log2(d)
|
||||||
t = 2**b
|
t = 2 ** b
|
||||||
c = t//d + 1
|
c = t // d + 1
|
||||||
assert c > 2**(64 +log2(d))
|
assert c > 2 ** (64 + log2(d))
|
||||||
K = 2**64
|
K = 2 ** 64
|
||||||
if(not(c * K * d<=( K + 1) * t)):
|
if not (c * K * d <= (K + 1) * t):
|
||||||
print(q)
|
print(q)
|
||||||
top = floor(t/(c * d - t))
|
top = floor(t / (c * d - t))
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
print("all good")
|
print("all good")
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#
|
#
|
||||||
# Reference :
|
# Reference :
|
||||||
# Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to appear)
|
# Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback, Software: Practice and Experience 53 (6), 2023 https://arxiv.org/abs/2212.06644
|
||||||
#
|
#
|
||||||
|
|
||||||
all_tqs = []
|
all_tqs = []
|
||||||
@ -9,25 +9,25 @@ all_tqs = []
|
|||||||
# Appendix B of Number parsing at a gigabyte per second.
|
# Appendix B of Number parsing at a gigabyte per second.
|
||||||
# Software: Practice and Experience 2021;51(8):1700–1727.
|
# Software: Practice and Experience 2021;51(8):1700–1727.
|
||||||
for q in range(-342, -27):
|
for q in range(-342, -27):
|
||||||
power5 = 5**-q
|
power5 = 5 ** -q
|
||||||
z = 0
|
z = 0
|
||||||
while (1 << z) < power5:
|
while (1 << z) < power5:
|
||||||
z += 1
|
z += 1
|
||||||
b = 2 * z + 2 * 64
|
b = 2 * z + 2 * 64
|
||||||
c = 2**b // power5 + 1
|
c = 2 ** b // power5 + 1
|
||||||
while c >= (1 << 128):
|
while c >= (1 << 128):
|
||||||
c //= 2
|
c //= 2
|
||||||
all_tqs.append(c)
|
all_tqs.append(c)
|
||||||
for q in range(-27, 0):
|
for q in range(-27, 0):
|
||||||
power5 = 5**-q
|
power5 = 5 ** -q
|
||||||
z = 0
|
z = 0
|
||||||
while (1 << z) < power5:
|
while (1 << z) < power5:
|
||||||
z += 1
|
z += 1
|
||||||
b = z + 127
|
b = z + 127
|
||||||
c = 2**b // power5 + 1
|
c = 2 ** b // power5 + 1
|
||||||
all_tqs.append(c)
|
all_tqs.append(c)
|
||||||
for q in range(0, 308 + 1):
|
for q in range(0, 308 + 1):
|
||||||
power5 = 5**q
|
power5 = 5 ** q
|
||||||
while power5 < (1 << 127):
|
while power5 < (1 << 127):
|
||||||
power5 *= 2
|
power5 *= 2
|
||||||
while power5 >= (1 << 128):
|
while power5 >= (1 << 128):
|
||||||
@ -44,6 +44,7 @@ def continued_fraction(numer, denom):
|
|||||||
numer, denom = denom, rem
|
numer, denom = denom, rem
|
||||||
return cf
|
return cf
|
||||||
|
|
||||||
|
|
||||||
# Given a continued fraction [a0; a1, a2, ..., an], returns
|
# Given a continued fraction [a0; a1, a2, ..., an], returns
|
||||||
# all the convergents of that continued fraction
|
# all the convergents of that continued fraction
|
||||||
# as pairs of the form (numer, denom), where numer/denom is
|
# as pairs of the form (numer, denom), where numer/denom is
|
||||||
@ -58,18 +59,23 @@ def convergents(cf):
|
|||||||
p_n = a_n * p_n_minus_1 + p_n_minus_2
|
p_n = a_n * p_n_minus_1 + p_n_minus_2
|
||||||
q_n = a_n * q_n_minus_1 + q_n_minus_2
|
q_n = a_n * q_n_minus_1 + q_n_minus_2
|
||||||
convergents.append((p_n, q_n))
|
convergents.append((p_n, q_n))
|
||||||
p_n_minus_2, q_n_minus_2, p_n_minus_1, q_n_minus_1 = p_n_minus_1, q_n_minus_1, p_n, q_n
|
p_n_minus_2, q_n_minus_2, p_n_minus_1, q_n_minus_1 = (
|
||||||
|
p_n_minus_1,
|
||||||
|
q_n_minus_1,
|
||||||
|
p_n,
|
||||||
|
q_n,
|
||||||
|
)
|
||||||
return convergents
|
return convergents
|
||||||
|
|
||||||
|
|
||||||
# Enumerate through all the convergents of T[q] / 2^137 with denominators < 2^64
|
# Enumerate through all the convergents of T[q] / 2^137 with denominators < 2^64
|
||||||
found_solution = False
|
found_solution = False
|
||||||
for j, tq in enumerate(all_tqs):
|
for j, tq in enumerate(all_tqs):
|
||||||
for _, w in convergents(continued_fraction(tq, 2**137)):
|
for _, w in convergents(continued_fraction(tq, 2 ** 137)):
|
||||||
if w >= 2**64:
|
if w >= 2 ** 64:
|
||||||
break
|
break
|
||||||
if (tq*w) % 2**137 > 2**137 - 2**64:
|
if (tq * w) % 2 ** 137 > 2 ** 137 - 2 ** 64:
|
||||||
print(f"SOLUTION: q={j-342} T[q]={tq} w={w}")
|
print(f"SOLUTION: q={j-342} T[q]={tq} w={w}")
|
||||||
found_solution = True
|
found_solution = True
|
||||||
if not found_solution:
|
if not found_solution:
|
||||||
print("No solutions!")
|
print("No solutions!")
|
||||||
|
|||||||
183
script/release.py
Executable file
183
script/release.py
Executable file
@ -0,0 +1,183 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
########################################################################
|
||||||
|
# Generates a new release.
|
||||||
|
########################################################################
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import fileinput
|
||||||
|
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
sys.stdout.write("Sorry, requires Python 3.x or better\n")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def colored(r, g, b, text):
|
||||||
|
return f"\033[38;2;{r};{g};{b}m{text} \033[38;2;255;255;255m"
|
||||||
|
|
||||||
|
|
||||||
|
def extractnumbers(s):
|
||||||
|
return tuple(map(int, re.findall(r"(\d+)\.(\d+)\.(\d+)", str(s))[0]))
|
||||||
|
|
||||||
|
|
||||||
|
def toversionstring(major, minor, rev):
|
||||||
|
return f"{major}.{minor}.{rev}"
|
||||||
|
|
||||||
|
|
||||||
|
print("Calling git rev-parse --abbrev-ref HEAD")
|
||||||
|
pipe = subprocess.Popen(
|
||||||
|
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
)
|
||||||
|
branchresult = pipe.communicate()[0].decode().strip()
|
||||||
|
|
||||||
|
if branchresult != "main":
|
||||||
|
print(
|
||||||
|
colored(
|
||||||
|
255,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
f"We recommend that you release on main, you are on '{branchresult}'",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
ret = subprocess.call(["git", "remote", "update"])
|
||||||
|
|
||||||
|
if ret != 0:
|
||||||
|
sys.exit(ret)
|
||||||
|
print("Calling git log HEAD.. --oneline")
|
||||||
|
pipe = subprocess.Popen(
|
||||||
|
["git", "log", "HEAD..", "--oneline"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
)
|
||||||
|
uptodateresult = pipe.communicate()[0].decode().strip()
|
||||||
|
|
||||||
|
if len(uptodateresult) != 0:
|
||||||
|
print(uptodateresult)
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
pipe = subprocess.Popen(
|
||||||
|
["git", "rev-parse", "--show-toplevel"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
)
|
||||||
|
maindir = pipe.communicate()[0].decode().strip()
|
||||||
|
scriptlocation = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
print(f"repository: {maindir}")
|
||||||
|
|
||||||
|
pipe = subprocess.Popen(
|
||||||
|
["git", "describe", "--abbrev=0", "--tags"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
)
|
||||||
|
versionresult = pipe.communicate()[0].decode().strip()
|
||||||
|
|
||||||
|
print(f"last version: {versionresult}")
|
||||||
|
try:
|
||||||
|
currentv = extractnumbers(versionresult)
|
||||||
|
except:
|
||||||
|
currentv = [0, 0, 0]
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
nextv = (currentv[0], currentv[1], currentv[2] + 1)
|
||||||
|
print(f"please specify version number, e.g. {toversionstring(*nextv)}")
|
||||||
|
sys.exit(-1)
|
||||||
|
try:
|
||||||
|
newversion = extractnumbers(sys.argv[1])
|
||||||
|
print(newversion)
|
||||||
|
except:
|
||||||
|
print(f"can't parse version number {sys.argv[1]}")
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
print("checking that new version is valid")
|
||||||
|
if newversion[0] != currentv[0]:
|
||||||
|
assert newversion[0] == currentv[0] + 1
|
||||||
|
assert newversion[1] == 0
|
||||||
|
assert newversion[2] == 0
|
||||||
|
elif newversion[1] != currentv[1]:
|
||||||
|
assert newversion[1] == currentv[1] + 1
|
||||||
|
assert newversion[2] == 0
|
||||||
|
else:
|
||||||
|
assert newversion[2] == currentv[2] + 1
|
||||||
|
|
||||||
|
atleastminor = (currentv[0] != newversion[0]) or (currentv[1] != newversion[1])
|
||||||
|
|
||||||
|
newmajorversionstring = str(newversion[0])
|
||||||
|
newminorversionstring = str(newversion[1])
|
||||||
|
newpatchversionstring = str(newversion[2])
|
||||||
|
newversionstring = f"{newversion[0]}.{newversion[1]}.{newversion[2]}"
|
||||||
|
cmakefile = f"{maindir}{os.sep}CMakeLists.txt"
|
||||||
|
|
||||||
|
for line in fileinput.input(cmakefile, inplace=1, backup=".bak"):
|
||||||
|
line = re.sub(
|
||||||
|
r"project\(fast_float VERSION \d+\.\d+\.\d+ LANGUAGES CXX\)",
|
||||||
|
f"project(fast_float VERSION {newversionstring} LANGUAGES CXX)",
|
||||||
|
line.rstrip(),
|
||||||
|
)
|
||||||
|
print(line)
|
||||||
|
|
||||||
|
print(f"modified {cmakefile}, a backup was made")
|
||||||
|
|
||||||
|
versionfilerel = f"{os.sep}include{os.sep}fast_float{os.sep}float_common.h"
|
||||||
|
versionfile = f"{maindir}{versionfilerel}"
|
||||||
|
|
||||||
|
for line in fileinput.input(versionfile, inplace=1, backup=".bak"):
|
||||||
|
line = re.sub(
|
||||||
|
r"#define FASTFLOAT_VERSION_MAJOR \d+",
|
||||||
|
f"#define FASTFLOAT_VERSION_MAJOR {newmajorversionstring}",
|
||||||
|
line.rstrip(),
|
||||||
|
)
|
||||||
|
line = re.sub(
|
||||||
|
r"#define FASTFLOAT_VERSION_MINOR \d+",
|
||||||
|
f"#define FASTFLOAT_VERSION_MINOR {newminorversionstring}",
|
||||||
|
line.rstrip(),
|
||||||
|
)
|
||||||
|
line = re.sub(
|
||||||
|
r"#define FASTFLOAT_VERSION_PATCH \d+",
|
||||||
|
f"#define FASTFLOAT_VERSION_PATCH {newpatchversionstring}",
|
||||||
|
line.rstrip(),
|
||||||
|
)
|
||||||
|
print(line)
|
||||||
|
|
||||||
|
print(f"{versionfile} modified")
|
||||||
|
|
||||||
|
readmefile = f"{maindir}{os.sep}README.md"
|
||||||
|
|
||||||
|
for line in fileinput.input(readmefile, inplace=1, backup=".bak"):
|
||||||
|
line = re.sub(
|
||||||
|
r"https://github.com/fastfloat/fast_float/releases/download/v(\d+\.\d+\.\d+)/fast_float.h",
|
||||||
|
f"https://github.com/fastfloat/fast_float/releases/download/v{newversionstring}/fast_float.h",
|
||||||
|
line.rstrip(),
|
||||||
|
)
|
||||||
|
line = re.sub(
|
||||||
|
r"GIT_TAG tags/v(\d+\.\d+\.\d+)",
|
||||||
|
f"GIT_TAG tags/v{newversionstring}",
|
||||||
|
line.rstrip(),
|
||||||
|
)
|
||||||
|
line = re.sub(
|
||||||
|
r"GIT_TAG v(\d+\.\d+\.\d+)\)", f"GIT_TAG v{newversionstring})", line.rstrip()
|
||||||
|
)
|
||||||
|
print(line)
|
||||||
|
|
||||||
|
print(f"modified {readmefile}, a backup was made")
|
||||||
|
|
||||||
|
print("running amalgamate.py")
|
||||||
|
with open(f"{maindir}{os.sep}fast_float.h", "w") as outfile:
|
||||||
|
cp = subprocess.run(
|
||||||
|
[f"python3", f"{maindir}{os.sep}script{os.sep}amalgamate.py"], stdout=outfile
|
||||||
|
)
|
||||||
|
|
||||||
|
if cp.returncode != 0:
|
||||||
|
print("Failed to run amalgamate")
|
||||||
|
else:
|
||||||
|
print("amalgamate.py ran successfully")
|
||||||
|
print(f"You should upload {maindir}{os.sep}fast_float.h")
|
||||||
|
|
||||||
|
print("Please run the tests before issuing a release.\n")
|
||||||
|
print(
|
||||||
|
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'
|
||||||
|
)
|
||||||
22
script/run-clangcldocker.sh
Executable file
22
script/run-clangcldocker.sh
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
COMMAND=$*
|
||||||
|
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
|
||||||
|
MAINSOURCE=$SCRIPTPATH/..
|
||||||
|
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)
|
||||||
|
|
||||||
|
if clang-format-17 --version 2>/dev/null | grep -qF 'version 17.'; then
|
||||||
|
cd $MAINSOURCE; clang-format-17 --style=file --verbose -i "$@" $ALL_FILES
|
||||||
|
exit 0
|
||||||
|
elif clang-format --version 2>/dev/null | grep -qF 'version 17.'; then
|
||||||
|
cd $MAINSOURCE; clang-format --style=file --verbose -i "$@" $ALL_FILES
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Trying to use docker"
|
||||||
|
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; }
|
||||||
|
docker info >/dev/null 2>&1 || { echo >&2 "Docker server is not running? type 'docker info'."; exit 1; }
|
||||||
|
|
||||||
|
if [ -t 0 ]; then DOCKER_ARGS=-it; fi
|
||||||
|
docker pull kszonek/clang-format-17
|
||||||
|
|
||||||
|
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
|
||||||
@ -1,14 +1,15 @@
|
|||||||
def format(number):
|
def format(number):
|
||||||
upper = number // (1<<64)
|
upper = number // (1 << 64)
|
||||||
lower = number % (1<<64)
|
lower = number % (1 << 64)
|
||||||
print(""+hex(upper)+","+hex(lower)+",")
|
print("" + hex(upper) + "," + hex(lower) + ",")
|
||||||
|
|
||||||
for q in range(-342,0):
|
|
||||||
|
for q in range(-342, 0):
|
||||||
power5 = 5 ** -q
|
power5 = 5 ** -q
|
||||||
z = 0
|
z = 0
|
||||||
while( (1<<z) < power5) :
|
while (1 << z) < power5:
|
||||||
z += 1
|
z += 1
|
||||||
if(q >= -27):
|
if q >= -27:
|
||||||
b = z + 127
|
b = z + 127
|
||||||
c = 2 ** b // power5 + 1
|
c = 2 ** b // power5 + 1
|
||||||
format(c)
|
format(c)
|
||||||
@ -16,16 +17,16 @@ for q in range(-342,0):
|
|||||||
b = 2 * z + 2 * 64
|
b = 2 * z + 2 * 64
|
||||||
c = 2 ** b // power5 + 1
|
c = 2 ** b // power5 + 1
|
||||||
# truncate
|
# truncate
|
||||||
while(c >= (1<<128)):
|
while c >= (1 << 128):
|
||||||
c //= 2
|
c //= 2
|
||||||
format(c)
|
format(c)
|
||||||
|
|
||||||
for q in range(0,308+1):
|
for q in range(0, 308 + 1):
|
||||||
power5 = 5 ** q
|
power5 = 5 ** q
|
||||||
# move the most significant bit in position
|
# move the most significant bit in position
|
||||||
while(power5 < (1<<127)):
|
while power5 < (1 << 127):
|
||||||
power5 *= 2
|
power5 *= 2
|
||||||
# *truncate*
|
# *truncate*
|
||||||
while(power5 >= (1<<128)):
|
while power5 >= (1 << 128):
|
||||||
power5 //= 2
|
power5 //= 2
|
||||||
format(power5)
|
format(power5)
|
||||||
|
|||||||
116
tests/BUILD.bazel
Normal file
116
tests/BUILD.bazel
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
cc_test(
|
||||||
|
name = "basictest",
|
||||||
|
srcs = ["basictest.cpp"],
|
||||||
|
deps = [
|
||||||
|
"//:fast_float",
|
||||||
|
"@doctest//doctest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "example_test",
|
||||||
|
srcs = ["example_test.cpp"],
|
||||||
|
deps = [
|
||||||
|
"//:fast_float",
|
||||||
|
"@doctest//doctest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "example_comma_test",
|
||||||
|
srcs = ["example_comma_test.cpp"],
|
||||||
|
deps = [
|
||||||
|
"//:fast_float",
|
||||||
|
"@doctest//doctest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "fast_int",
|
||||||
|
srcs = ["fast_int.cpp"],
|
||||||
|
deps = [
|
||||||
|
"//:fast_float",
|
||||||
|
"@doctest//doctest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "fixedwidthtest",
|
||||||
|
srcs = ["fixedwidthtest.cpp"],
|
||||||
|
deps = [
|
||||||
|
"//:fast_float",
|
||||||
|
"@doctest//doctest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "fortran",
|
||||||
|
srcs = ["fortran.cpp"],
|
||||||
|
deps = [
|
||||||
|
"//:fast_float",
|
||||||
|
"@doctest//doctest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "json_fmt",
|
||||||
|
srcs = ["json_fmt.cpp"],
|
||||||
|
deps = [
|
||||||
|
"//:fast_float",
|
||||||
|
"@doctest//doctest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "long_test",
|
||||||
|
srcs = ["long_test.cpp"],
|
||||||
|
deps = [
|
||||||
|
"//:fast_float",
|
||||||
|
"@doctest//doctest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "powersoffive_hardround",
|
||||||
|
srcs = ["powersoffive_hardround.cpp"],
|
||||||
|
deps = [
|
||||||
|
"//:fast_float",
|
||||||
|
"@doctest//doctest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "rcppfastfloat_test",
|
||||||
|
srcs = ["rcppfastfloat_test.cpp"],
|
||||||
|
deps = [
|
||||||
|
"//:fast_float",
|
||||||
|
"@doctest//doctest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "wide_char_test",
|
||||||
|
srcs = ["wide_char_test.cpp"],
|
||||||
|
deps = [
|
||||||
|
"//:fast_float",
|
||||||
|
"@doctest//doctest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "supported_chars_test",
|
||||||
|
srcs = ["supported_chars_test.cpp"],
|
||||||
|
deps = [
|
||||||
|
"//:fast_float",
|
||||||
|
"@doctest//doctest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "string_test",
|
||||||
|
srcs = ["string_test.cpp"],
|
||||||
|
deps = [
|
||||||
|
"//:fast_float",
|
||||||
|
"@doctest//doctest",
|
||||||
|
],
|
||||||
|
)
|
||||||
@ -5,37 +5,35 @@ cmake_minimum_required(VERSION 3.11 FATAL_ERROR)
|
|||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
option(SYSTEM_DOCTEST "Use system copy of doctest" OFF)
|
option(SYSTEM_DOCTEST "Use system copy of doctest" OFF)
|
||||||
|
option(FASTFLOAT_SUPPLEMENTAL_TESTS "Run supplemental tests" ON)
|
||||||
|
|
||||||
if (NOT SYSTEM_DOCTEST)
|
if (NOT SYSTEM_DOCTEST)
|
||||||
FetchContent_Declare(doctest
|
FetchContent_Declare(doctest
|
||||||
GIT_REPOSITORY https://github.com/onqtam/doctest.git
|
GIT_REPOSITORY https://github.com/lemire/doctest.git)
|
||||||
GIT_TAG v2.4.10)
|
else ()
|
||||||
|
find_package(doctest REQUIRED)
|
||||||
|
endif()
|
||||||
|
if (FASTFLOAT_SUPPLEMENTAL_TESTS)
|
||||||
|
FetchContent_Declare(supplemental_test_files
|
||||||
|
GIT_REPOSITORY https://github.com/fastfloat/supplemental_test_files.git
|
||||||
|
GIT_TAG origin/main)
|
||||||
endif()
|
endif()
|
||||||
FetchContent_Declare(supplemental_test_files
|
|
||||||
GIT_REPOSITORY https://github.com/fastfloat/supplemental_test_files.git
|
|
||||||
GIT_TAG origin/main)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# FetchContent_MakeAvailable() was only introduced in 3.14
|
# FetchContent_MakeAvailable() was only introduced in 3.14
|
||||||
# https://cmake.org/cmake/help/v3.14/release/3.14.html#modules
|
# https://cmake.org/cmake/help/v3.14/release/3.14.html#modules
|
||||||
# FetchContent_MakeAvailable(doctest)
|
|
||||||
if (NOT SYSTEM_DOCTEST)
|
if (NOT SYSTEM_DOCTEST)
|
||||||
FetchContent_GetProperties(doctest)
|
FetchContent_MakeAvailable(doctest)
|
||||||
if(NOT doctest_POPULATED)
|
|
||||||
FetchContent_Populate(doctest)
|
|
||||||
add_subdirectory(${doctest_SOURCE_DIR} ${doctest_BINARY_DIR})
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
FetchContent_GetProperties(supplemental_test_files)
|
|
||||||
if(NOT supplemental_test_files_POPULATED)
|
|
||||||
message(STATUS "Tests enabled. Retrieving test files.")
|
|
||||||
FetchContent_Populate(supplemental_test_files)
|
|
||||||
message(STATUS "Test files retrieved.")
|
|
||||||
add_subdirectory(${supplemental_test_files_SOURCE_DIR} ${supplemental_test_files_BINARY_DIR})
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(supplemental-data INTERFACE)
|
add_library(supplemental-data INTERFACE)
|
||||||
target_compile_definitions(supplemental-data INTERFACE SUPPLEMENTAL_TEST_DATA_DIR="${supplemental_test_files_BINARY_DIR}/data")
|
if (FASTFLOAT_SUPPLEMENTAL_TESTS)
|
||||||
|
message(STATUS "Supplemental tests enabled. Retrieving test files.")
|
||||||
|
FetchContent_MakeAvailable(supplemental_test_files)
|
||||||
|
message(STATUS "Supplemental test files retrieved.")
|
||||||
|
target_compile_definitions(supplemental-data INTERFACE SUPPLEMENTAL_TEST_DATA_DIR="${supplemental_test_files_BINARY_DIR}/data")
|
||||||
|
endif()
|
||||||
|
|
||||||
function(fast_float_add_cpp_test TEST_NAME)
|
function(fast_float_add_cpp_test TEST_NAME)
|
||||||
add_executable(${TEST_NAME} ${TEST_NAME}.cpp)
|
add_executable(${TEST_NAME} ${TEST_NAME}.cpp)
|
||||||
if(CMAKE_CROSSCOMPILING)
|
if(CMAKE_CROSSCOMPILING)
|
||||||
@ -53,12 +51,17 @@ function(fast_float_add_cpp_test TEST_NAME)
|
|||||||
target_link_libraries(${TEST_NAME} PUBLIC fast_float supplemental-data)
|
target_link_libraries(${TEST_NAME} PUBLIC fast_float supplemental-data)
|
||||||
if (NOT SYSTEM_DOCTEST)
|
if (NOT SYSTEM_DOCTEST)
|
||||||
target_link_libraries(${TEST_NAME} PUBLIC doctest)
|
target_link_libraries(${TEST_NAME} PUBLIC doctest)
|
||||||
|
else ()
|
||||||
|
target_link_libraries(${TEST_NAME} PUBLIC doctest::doctest)
|
||||||
endif()
|
endif()
|
||||||
endfunction(fast_float_add_cpp_test)
|
endfunction(fast_float_add_cpp_test)
|
||||||
|
|
||||||
fast_float_add_cpp_test(rcppfastfloat_test)
|
fast_float_add_cpp_test(rcppfastfloat_test)
|
||||||
|
fast_float_add_cpp_test(wide_char_test)
|
||||||
|
fast_float_add_cpp_test(supported_chars_test)
|
||||||
fast_float_add_cpp_test(example_test)
|
fast_float_add_cpp_test(example_test)
|
||||||
fast_float_add_cpp_test(example_comma_test)
|
fast_float_add_cpp_test(example_comma_test)
|
||||||
|
fast_float_add_cpp_test(example_integer_times_pow10)
|
||||||
fast_float_add_cpp_test(basictest)
|
fast_float_add_cpp_test(basictest)
|
||||||
option(FASTFLOAT_CONSTEXPR_TESTS "Require constexpr tests (build will fail if the compiler won't support it)" OFF)
|
option(FASTFLOAT_CONSTEXPR_TESTS "Require constexpr tests (build will fail if the compiler won't support it)" OFF)
|
||||||
if (FASTFLOAT_CONSTEXPR_TESTS)
|
if (FASTFLOAT_CONSTEXPR_TESTS)
|
||||||
@ -67,13 +70,26 @@ if (FASTFLOAT_CONSTEXPR_TESTS)
|
|||||||
else()
|
else()
|
||||||
target_compile_features(basictest PRIVATE cxx_std_17)
|
target_compile_features(basictest PRIVATE cxx_std_17)
|
||||||
endif()
|
endif()
|
||||||
|
if (FASTFLOAT_SUPPLEMENTAL_TESTS)
|
||||||
|
target_compile_definitions(basictest PRIVATE FASTFLOAT_SUPPLEMENTAL_TESTS)
|
||||||
|
endif()
|
||||||
|
fast_float_add_cpp_test(p2497)
|
||||||
fast_float_add_cpp_test(long_test)
|
fast_float_add_cpp_test(long_test)
|
||||||
fast_float_add_cpp_test(powersoffive_hardround)
|
fast_float_add_cpp_test(powersoffive_hardround)
|
||||||
fast_float_add_cpp_test(string_test)
|
fast_float_add_cpp_test(string_test)
|
||||||
fast_float_add_cpp_test(fast_int)
|
fast_float_add_cpp_test(fast_int)
|
||||||
|
target_compile_features(fast_int PRIVATE cxx_std_17)
|
||||||
fast_float_add_cpp_test(json_fmt)
|
fast_float_add_cpp_test(json_fmt)
|
||||||
fast_float_add_cpp_test(fortran)
|
fast_float_add_cpp_test(fortran)
|
||||||
|
if(CMAKE_CXX_STANDARD GREATER_EQUAL 23)
|
||||||
|
option(FASTFLOAT_FIXEDWIDTH_TESTS "Require fixed width test for C++23 (build will fail if the compiler won't support it)" ON)
|
||||||
|
else()
|
||||||
|
option(FASTFLOAT_FIXEDWIDTH_TESTS "Require fixed width test for C++23 (build will fail if the compiler won't support it)" OFF)
|
||||||
|
endif()
|
||||||
|
if (FASTFLOAT_FIXEDWIDTH_TESTS)
|
||||||
|
fast_float_add_cpp_test(fixedwidthtest)
|
||||||
|
target_compile_features(fixedwidthtest PUBLIC cxx_std_23)
|
||||||
|
endif()
|
||||||
|
|
||||||
option(FASTFLOAT_EXHAUSTIVE "Exhaustive tests" OFF)
|
option(FASTFLOAT_EXHAUSTIVE "Exhaustive tests" OFF)
|
||||||
|
|
||||||
|
|||||||
2203
tests/basictest.cpp
2203
tests/basictest.cpp
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,11 @@
|
|||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
|
double get1(char const *input) {
|
||||||
double get1(const char* input) {
|
double result_value;
|
||||||
double result_value;
|
auto result =
|
||||||
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
|
fast_float::from_chars(input, input + strlen(input), result_value);
|
||||||
if (result.ec != std::errc()) { return 1; }
|
if (result.ec != std::errc()) {
|
||||||
return result_value;
|
return 1;
|
||||||
|
}
|
||||||
|
return result_value;
|
||||||
}
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
|
double get10(char const *input) {
|
||||||
double get10(const char* input) {
|
double result_value;
|
||||||
double result_value;
|
auto result =
|
||||||
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
|
fast_float::from_chars(input, input + strlen(input), result_value);
|
||||||
if (result.ec != std::errc()) { return 10; }
|
if (result.ec != std::errc()) {
|
||||||
return result_value;
|
return 10;
|
||||||
|
}
|
||||||
|
return result_value;
|
||||||
}
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
|
double get2(char const *input) {
|
||||||
double get2(const char* input) {
|
double result_value;
|
||||||
double result_value;
|
auto result =
|
||||||
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
|
fast_float::from_chars(input, input + strlen(input), result_value);
|
||||||
if (result.ec != std::errc()) { return 2; }
|
if (result.ec != std::errc()) {
|
||||||
return result_value;
|
return 2;
|
||||||
|
}
|
||||||
|
return result_value;
|
||||||
}
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
|
double get3(char const *input) {
|
||||||
double get3(const char* input) {
|
double result_value;
|
||||||
double result_value;
|
auto result =
|
||||||
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
|
fast_float::from_chars(input, input + strlen(input), result_value);
|
||||||
if (result.ec != std::errc()) { return 3; }
|
if (result.ec != std::errc()) {
|
||||||
return result_value;
|
return 3;
|
||||||
|
}
|
||||||
|
return result_value;
|
||||||
}
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
|
double get4(char const *input) {
|
||||||
double get4(const char* input) {
|
double result_value;
|
||||||
double result_value;
|
auto result =
|
||||||
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
|
fast_float::from_chars(input, input + strlen(input), result_value);
|
||||||
if (result.ec != std::errc()) { return 4; }
|
if (result.ec != std::errc()) {
|
||||||
return result_value;
|
return 4;
|
||||||
|
}
|
||||||
|
return result_value;
|
||||||
}
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
|
double get5(char const *input) {
|
||||||
double get5(const char* input) {
|
double result_value;
|
||||||
double result_value;
|
auto result =
|
||||||
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
|
fast_float::from_chars(input, input + strlen(input), result_value);
|
||||||
if (result.ec != std::errc()) { return 5; }
|
if (result.ec != std::errc()) {
|
||||||
return result_value;
|
return 5;
|
||||||
|
}
|
||||||
|
return result_value;
|
||||||
}
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
|
double get6(char const *input) {
|
||||||
double get6(const char* input) {
|
double result_value;
|
||||||
double result_value;
|
auto result =
|
||||||
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
|
fast_float::from_chars(input, input + strlen(input), result_value);
|
||||||
if (result.ec != std::errc()) { return 6; }
|
if (result.ec != std::errc()) {
|
||||||
return result_value;
|
return 6;
|
||||||
|
}
|
||||||
|
return result_value;
|
||||||
}
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
|
double get7(char const *input) {
|
||||||
double get7(const char* input) {
|
double result_value;
|
||||||
double result_value;
|
auto result =
|
||||||
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
|
fast_float::from_chars(input, input + strlen(input), result_value);
|
||||||
if (result.ec != std::errc()) { return 7; }
|
if (result.ec != std::errc()) {
|
||||||
return result_value;
|
return 7;
|
||||||
|
}
|
||||||
|
return result_value;
|
||||||
}
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
|
double get8(char const *input) {
|
||||||
double get8(const char* input) {
|
double result_value;
|
||||||
double result_value;
|
auto result =
|
||||||
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
|
fast_float::from_chars(input, input + strlen(input), result_value);
|
||||||
if (result.ec != std::errc()) { return 8; }
|
if (result.ec != std::errc()) {
|
||||||
return result_value;
|
return 8;
|
||||||
|
}
|
||||||
|
return result_value;
|
||||||
}
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
|
double get9(char const *input) {
|
||||||
double get9(const char* input) {
|
double result_value;
|
||||||
double result_value;
|
auto result =
|
||||||
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
|
fast_float::from_chars(input, input + strlen(input), result_value);
|
||||||
if (result.ec != std::errc()) { return 9; }
|
if (result.ec != std::errc()) {
|
||||||
return result_value;
|
return 9;
|
||||||
|
}
|
||||||
|
return result_value;
|
||||||
}
|
}
|
||||||
@ -1,19 +1,20 @@
|
|||||||
|
|
||||||
|
|
||||||
double get1(const char* input);
|
double get1(char const *input);
|
||||||
double get2(const char* input);
|
double get2(char const *input);
|
||||||
double get3(const char* input);
|
double get3(char const *input);
|
||||||
double get4(const char* input);
|
double get4(char const *input);
|
||||||
double get5(const char* input);
|
double get5(char const *input);
|
||||||
double get6(const char* input);
|
double get6(char const *input);
|
||||||
double get7(const char* input);
|
double get7(char const *input);
|
||||||
double get8(const char* input);
|
double get8(char const *input);
|
||||||
double get9(const char* input);
|
double get9(char const *input);
|
||||||
double get10(const char* input);
|
double get10(char const *input);
|
||||||
|
|
||||||
int main(int arg, char** argv) {
|
int main(int arg, char **argv) {
|
||||||
double x = get1(argv[0]) + get2(argv[0]) + get3(argv[0]) + get4(argv[0]) + get5(argv[0])
|
double x = get1(argv[0]) + get2(argv[0]) + get3(argv[0]) + get4(argv[0]) +
|
||||||
+ get6(argv[0]) + get7(argv[0]) + get8(argv[0]) + get9(argv[0]) + get10(argv[0]);
|
get5(argv[0]) + get6(argv[0]) + get7(argv[0]) + get8(argv[0]) +
|
||||||
|
get9(argv[0]) + get10(argv[0]);
|
||||||
|
|
||||||
return int(x);
|
return int(x);
|
||||||
}
|
}
|
||||||
@ -1,13 +1,16 @@
|
|||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
double get(const char * input) {
|
double get(char const *input) {
|
||||||
double result_value;
|
double result_value;
|
||||||
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
|
auto result =
|
||||||
if (result.ec != std::errc()) { return 10; }
|
fast_float::from_chars(input, input + strlen(input), result_value);
|
||||||
return result_value;
|
if (result.ec != std::errc()) {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
return result_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int arg, char** argv) {
|
int main(int arg, char **argv) {
|
||||||
double x = get(argv[0]);
|
double x = get(argv[0]);
|
||||||
return int(x);
|
return int(x);
|
||||||
}
|
}
|
||||||
@ -1,2 +1,3 @@
|
|||||||
#include "test.h"
|
#include "test.h"
|
||||||
void foo() { }
|
|
||||||
|
void foo() {}
|
||||||
@ -1,2 +1,3 @@
|
|||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
int main() { return 0; }
|
int main() { return 0; }
|
||||||
@ -5,11 +5,15 @@
|
|||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
const std::string input = "3,1416 xyz ";
|
std::string const input = "3,1416 xyz ";
|
||||||
double result;
|
double result;
|
||||||
fast_float::parse_options options{fast_float::chars_format::general, ','};
|
fast_float::parse_options options{fast_float::chars_format::general, ','};
|
||||||
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
|
auto answer = fast_float::from_chars_advanced(
|
||||||
if((answer.ec != std::errc()) || ((result != 3.1416))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
|
input.data(), input.data() + input.size(), result, options);
|
||||||
std::cout << "parsed the number " << result << std::endl;
|
if ((answer.ec != std::errc()) || ((result != 3.1416))) {
|
||||||
return EXIT_SUCCESS;
|
std::cerr << "parsing failure\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
std::cout << "parsed the number " << result << std::endl;
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
36
tests/example_integer_times_pow10.cpp
Normal file
36
tests/example_integer_times_pow10.cpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
void default_overload() {
|
||||||
|
const uint64_t W = 12345678901234567;
|
||||||
|
const int Q = 23;
|
||||||
|
const double result = fast_float::integer_times_pow10(W, Q);
|
||||||
|
std::cout.precision(17);
|
||||||
|
std::cout << W << " * 10^" << Q << " = " << result << " ("
|
||||||
|
<< (result == 12345678901234567e23 ? "==" : "!=") << "expected)\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void double_specialization() {
|
||||||
|
const uint64_t W = 12345678901234567;
|
||||||
|
const int Q = 23;
|
||||||
|
const double result = fast_float::integer_times_pow10<double>(W, Q);
|
||||||
|
std::cout.precision(17);
|
||||||
|
std::cout << "double: " << W << " * 10^" << Q << " = " << result << " ("
|
||||||
|
<< (result == 12345678901234567e23 ? "==" : "!=") << "expected)\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void float_specialization() {
|
||||||
|
const uint64_t W = 12345678;
|
||||||
|
const int Q = 23;
|
||||||
|
const float result = fast_float::integer_times_pow10<float>(W, Q);
|
||||||
|
std::cout.precision(9);
|
||||||
|
std::cout << "float: " << W << " * 10^" << Q << " = " << result << " ("
|
||||||
|
<< (result == 12345678e23f ? "==" : "!=") << "expected)\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
default_overload();
|
||||||
|
double_specialization();
|
||||||
|
float_specialization();
|
||||||
|
}
|
||||||
@ -4,36 +4,53 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
|
||||||
|
|
||||||
bool many() {
|
bool many() {
|
||||||
const std::string input = "234532.3426362,7869234.9823,324562.645";
|
std::string const input = "234532.3426362,7869234.9823,324562.645";
|
||||||
double result;
|
double result;
|
||||||
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
|
auto answer =
|
||||||
if(answer.ec != std::errc()) { return false; }
|
fast_float::from_chars(input.data(), input.data() + input.size(), result);
|
||||||
if(result != 234532.3426362) { return false; }
|
if (answer.ec != std::errc()) {
|
||||||
if(answer.ptr[0] != ',') { return false; }
|
return false;
|
||||||
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
|
}
|
||||||
if(answer.ec != std::errc()) { return false; }
|
if (result != 234532.3426362) {
|
||||||
if(result != 7869234.9823) { return false; }
|
return false;
|
||||||
if(answer.ptr[0] != ',') { return false; }
|
}
|
||||||
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
|
if (answer.ptr[0] != ',') {
|
||||||
if(answer.ec != std::errc()) { return false; }
|
return false;
|
||||||
if(result != 324562.645) { return false; }
|
}
|
||||||
|
answer = fast_float::from_chars(answer.ptr + 1, input.data() + input.size(),
|
||||||
|
result);
|
||||||
|
if (answer.ec != std::errc()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (result != 7869234.9823) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (answer.ptr[0] != ',') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
answer = fast_float::from_chars(answer.ptr + 1, input.data() + input.size(),
|
||||||
|
result);
|
||||||
|
if (answer.ec != std::errc()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (result != 324562.645) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void many_loop() {
|
void many_loop() {
|
||||||
const std::string input = "234532.3426362,7869234.9823,324562.645";
|
std::string const input = "234532.3426362,7869234.9823,324562.645";
|
||||||
double result;
|
double result;
|
||||||
const char* pointer = input.data();
|
char const *pointer = input.data();
|
||||||
const char* end_pointer = input.data() + input.size();
|
char const *end_pointer = input.data() + input.size();
|
||||||
|
|
||||||
while(pointer < end_pointer) {
|
while (pointer < end_pointer) {
|
||||||
auto answer = fast_float::from_chars(pointer, end_pointer, result);
|
auto answer = fast_float::from_chars(pointer, end_pointer, result);
|
||||||
if(answer.ec != std::errc()) {
|
if (answer.ec != std::errc()) {
|
||||||
std::cerr << "error while parsing" << std::endl;
|
std::cerr << "error while parsing" << std::endl;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::cout << "parsed: " << result << std::endl;
|
std::cout << "parsed: " << result << std::endl;
|
||||||
pointer = answer.ptr;
|
pointer = answer.ptr;
|
||||||
@ -47,34 +64,95 @@ void many_loop() {
|
|||||||
// consteval forces compile-time evaluation of the function in C++20.
|
// consteval forces compile-time evaluation of the function in C++20.
|
||||||
consteval double parse(std::string_view input) {
|
consteval double parse(std::string_view input) {
|
||||||
double result;
|
double result;
|
||||||
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
|
auto answer =
|
||||||
if(answer.ec != std::errc()) { return -1.0; }
|
fast_float::from_chars(input.data(), input.data() + input.size(), result);
|
||||||
|
if (answer.ec != std::errc()) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function should compile to a function which
|
// This function should compile to a function which
|
||||||
// merely returns 3.1415.
|
// merely returns 3.1415.
|
||||||
constexpr double constexptest() {
|
constexpr double constexptest() { return parse("3.1415 input"); }
|
||||||
return parse("3.1415 input");
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool small() {
|
||||||
|
double result = -1;
|
||||||
|
std::string str = "3e-1000";
|
||||||
|
auto r = fast_float::from_chars(str.data(), str.data() + str.size(), result);
|
||||||
|
if (r.ec != std::errc::result_out_of_range) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (r.ptr != str.data() + 7) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (result != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
printf("small values go to zero\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool large() {
|
||||||
|
double result = -1;
|
||||||
|
std::string str = "3e1000";
|
||||||
|
auto r = fast_float::from_chars(str.data(), str.data() + str.size(), result);
|
||||||
|
if (r.ec != std::errc::result_out_of_range) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (r.ptr != str.data() + 6) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (result != std::numeric_limits<double>::infinity()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
printf("large values go to infinity\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
const std::string input = "3.1416 xyz ";
|
std::string input = "3.1416 xyz ";
|
||||||
double result;
|
double result;
|
||||||
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
|
auto answer =
|
||||||
if((answer.ec != std::errc()) || ((result != 3.1416))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
|
fast_float::from_chars(input.data(), input.data() + input.size(), result);
|
||||||
std::cout << "parsed the number " << result << std::endl;
|
if ((answer.ec != std::errc()) || ((result != 3.1416))) {
|
||||||
|
std::cerr << "parsing failure\n";
|
||||||
if(!many()) {
|
return EXIT_FAILURE;
|
||||||
printf("Bug\n");
|
}
|
||||||
return EXIT_FAILURE;
|
std::cout << "parsed the number " << result << std::endl;
|
||||||
}
|
#ifdef __STDCPP_FLOAT16_T__
|
||||||
many_loop();
|
printf("16-bit float\n");
|
||||||
#if FASTFLOAT_IS_CONSTEXPR
|
// Parse as 16-bit float
|
||||||
if constexpr(constexptest() != 3.1415) {
|
std::float16_t parsed_16{};
|
||||||
return EXIT_FAILURE;
|
input = "10000e-1452";
|
||||||
}
|
auto fast_float_r16 = fast_float::from_chars(
|
||||||
|
input.data(), input.data() + input.size(), parsed_16);
|
||||||
|
if (fast_float_r16.ec != std::errc() &&
|
||||||
|
fast_float_r16.ec != std::errc::result_out_of_range) {
|
||||||
|
std::cerr << "16-bit fast_float parsing failure for: " + input + "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::cout << "parsed the 16-bit value " << float(parsed_16) << std::endl;
|
||||||
#endif
|
#endif
|
||||||
return EXIT_SUCCESS;
|
if (!small()) {
|
||||||
|
printf("Bug\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (!large()) {
|
||||||
|
printf("Bug\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!many()) {
|
||||||
|
printf("Bug\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
many_loop();
|
||||||
|
#if FASTFLOAT_IS_CONSTEXPR
|
||||||
|
if constexpr (constexptest() != 3.1415) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,13 +27,15 @@ void allvalues() {
|
|||||||
memcpy(&v, &word, sizeof(v));
|
memcpy(&v, &word, sizeof(v));
|
||||||
|
|
||||||
{
|
{
|
||||||
const char *string_end = to_string(v, buffer);
|
char const *string_end = to_string(v, buffer);
|
||||||
float result_value;
|
float result_value;
|
||||||
auto result = fast_float::from_chars(buffer, string_end, result_value);
|
auto result = fast_float::from_chars(buffer, string_end, result_value);
|
||||||
// Starting with version 4.0 for fast_float, we return result_out_of_range if the
|
// Starting with version 4.0 for fast_float, we return result_out_of_range
|
||||||
// value is either too small (too close to zero) or too large (effectively infinity).
|
// if the value is either too small (too close to zero) or too large
|
||||||
// So std::errc::result_out_of_range is normal for well-formed input strings.
|
// (effectively infinity). So std::errc::result_out_of_range is normal for
|
||||||
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
// well-formed input strings.
|
||||||
|
if (result.ec != std::errc() &&
|
||||||
|
result.ec != std::errc::result_out_of_range) {
|
||||||
std::cerr << "parsing error ? " << buffer << std::endl;
|
std::cerr << "parsing error ? " << buffer << std::endl;
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
@ -42,9 +44,9 @@ void allvalues() {
|
|||||||
std::cerr << "not nan" << buffer << std::endl;
|
std::cerr << "not nan" << buffer << std::endl;
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
} else if(copysign(1,result_value) != copysign(1,v)) {
|
} else if (copysign(1, result_value) != copysign(1, v)) {
|
||||||
std::cerr << "I got " << std::hexfloat << result_value << " but I was expecting " << v
|
std::cerr << "I got " << std::hexfloat << result_value
|
||||||
<< std::endl;
|
<< " but I was expecting " << v << std::endl;
|
||||||
abort();
|
abort();
|
||||||
} else if (result_value != v) {
|
} else if (result_value != v) {
|
||||||
std::cerr << "no match ? " << buffer << std::endl;
|
std::cerr << "no match ? " << buffer << std::endl;
|
||||||
|
|||||||
@ -16,11 +16,10 @@ template <typename T> char *to_string(T d, char *buffer) {
|
|||||||
return buffer + written;
|
return buffer + written;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool basic_test_64bit(std::string vals, double val) {
|
bool basic_test_64bit(std::string vals, double val) {
|
||||||
double result_value;
|
double result_value;
|
||||||
auto result = fast_float::from_chars(vals.data(), vals.data() + vals.size(),
|
auto result = fast_float::from_chars(vals.data(), vals.data() + vals.size(),
|
||||||
result_value);
|
result_value);
|
||||||
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
||||||
std::cerr << " I could not parse " << vals << std::endl;
|
std::cerr << " I could not parse " << vals << std::endl;
|
||||||
return false;
|
return false;
|
||||||
@ -31,14 +30,14 @@ bool basic_test_64bit(std::string vals, double val) {
|
|||||||
std::cerr << "not nan" << result_value << std::endl;
|
std::cerr << "not nan" << result_value << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if(copysign(1,result_value) != copysign(1,val)) {
|
} else if (copysign(1, result_value) != copysign(1, val)) {
|
||||||
std::cerr << "I got " << std::hexfloat << result_value << " but I was expecting " << val
|
std::cerr << "I got " << std::hexfloat << result_value
|
||||||
<< std::endl;
|
<< " but I was expecting " << val << std::endl;
|
||||||
return false;
|
return false;
|
||||||
} else if (result_value != val) {
|
} else if (result_value != val) {
|
||||||
std::cerr << vals << std::endl;
|
std::cerr << vals << std::endl;
|
||||||
std::cerr << "I got " << std::hexfloat << result_value << " but I was expecting " << val
|
std::cerr << "I got " << std::hexfloat << result_value
|
||||||
<< std::endl;
|
<< " but I was expecting " << val << std::endl;
|
||||||
std::cerr << std::dec;
|
std::cerr << std::dec;
|
||||||
std::cerr << "string: " << vals << std::endl;
|
std::cerr << "string: " << vals << std::endl;
|
||||||
return false;
|
return false;
|
||||||
@ -46,7 +45,6 @@ bool basic_test_64bit(std::string vals, double val) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void all_32bit_values() {
|
void all_32bit_values() {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
for (uint64_t w = 0; w <= 0xFFFFFFFF; w++) {
|
for (uint64_t w = 0; w <= 0xFFFFFFFF; w++) {
|
||||||
@ -60,9 +58,9 @@ void all_32bit_values() {
|
|||||||
double v = v32;
|
double v = v32;
|
||||||
|
|
||||||
{
|
{
|
||||||
const char *string_end = to_string(v, buffer);
|
char const *string_end = to_string(v, buffer);
|
||||||
std::string s(buffer, size_t(string_end-buffer));
|
std::string s(buffer, size_t(string_end - buffer));
|
||||||
if(!basic_test_64bit(s,v)) {
|
if (!basic_test_64bit(s, v)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,30 +14,40 @@
|
|||||||
// gcc.
|
// gcc.
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
// workaround for CYGWIN
|
// workaround for CYGWIN
|
||||||
double cygwin_strtod_l(const char* start, char** end) {
|
double cygwin_strtod_l(char const *start, char **end) {
|
||||||
double d;
|
double d;
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss.imbue(std::locale::classic());
|
ss.imbue(std::locale::classic());
|
||||||
ss << start;
|
ss << start;
|
||||||
ss >> d;
|
ss >> d;
|
||||||
if(ss.fail()) { *end = nullptr; }
|
if (ss.fail()) {
|
||||||
if(ss.eof()) { ss.clear(); }
|
*end = nullptr;
|
||||||
auto nread = ss.tellg();
|
}
|
||||||
*end = const_cast<char*>(start) + nread;
|
if (ss.eof()) {
|
||||||
return d;
|
ss.clear();
|
||||||
|
}
|
||||||
|
auto nread = ss.tellg();
|
||||||
|
*end = const_cast<char *>(start) + nread;
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
float cygwin_strtof_l(const char* start, char** end) {
|
|
||||||
float d;
|
float cygwin_strtof_l(char const *start, char **end) {
|
||||||
std::stringstream ss;
|
float d;
|
||||||
ss.imbue(std::locale::classic());
|
std::stringstream ss;
|
||||||
ss << start;
|
ss.imbue(std::locale::classic());
|
||||||
ss >> d;
|
ss << start;
|
||||||
if(ss.fail()) { *end = nullptr; }
|
ss >> d;
|
||||||
if(ss.eof()) { ss.clear(); }
|
if (ss.fail()) {
|
||||||
auto nread = ss.tellg();
|
*end = nullptr;
|
||||||
*end = const_cast<char*>(start) + nread;
|
}
|
||||||
return d;
|
if (ss.eof()) {
|
||||||
|
ss.clear();
|
||||||
|
}
|
||||||
|
auto nread = ss.tellg();
|
||||||
|
*end = const_cast<char *>(start) + nread;
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -47,20 +57,21 @@ template <typename T> char *to_string(T d, char *buffer) {
|
|||||||
return buffer + written;
|
return buffer + written;
|
||||||
}
|
}
|
||||||
|
|
||||||
void strtof_from_string(const char * st, float& d) {
|
void strtof_from_string(char const *st, float &d) {
|
||||||
char *pr = (char *)st;
|
char *pr = (char *)st;
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
d = cygwin_strtof_l(st, &pr);
|
defined(sun) || defined(__sun)
|
||||||
|
d = cygwin_strtof_l(st, &pr);
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
||||||
d = _strtof_l(st, &pr, c_locale);
|
d = _strtof_l(st, &pr, c_locale);
|
||||||
#else
|
#else
|
||||||
static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL);
|
static locale_t c_locale = newlocale(LC_ALL_MASK, "C", NULL);
|
||||||
d = strtof_l(st, &pr, c_locale);
|
d = strtof_l(st, &pr, c_locale);
|
||||||
#endif
|
#endif
|
||||||
if (pr == st) {
|
if (pr == st) {
|
||||||
throw std::runtime_error("bug in strtod_from_string");
|
throw std::runtime_error("bug in strtod_from_string");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool allvalues() {
|
bool allvalues() {
|
||||||
@ -73,10 +84,14 @@ bool allvalues() {
|
|||||||
}
|
}
|
||||||
uint32_t word = uint32_t(w);
|
uint32_t word = uint32_t(w);
|
||||||
memcpy(&v, &word, sizeof(v));
|
memcpy(&v, &word, sizeof(v));
|
||||||
if(std::isfinite(v)) {
|
if (std::isfinite(v)) {
|
||||||
float nextf = std::nextafterf(v, INFINITY);
|
float nextf = std::nextafterf(v, INFINITY);
|
||||||
if(copysign(1,v) != copysign(1,nextf)) { continue; }
|
if (copysign(1, v) != copysign(1, nextf)) {
|
||||||
if(!std::isfinite(nextf)) { continue; }
|
continue;
|
||||||
|
}
|
||||||
|
if (!std::isfinite(nextf)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
double v1{v};
|
double v1{v};
|
||||||
assert(float(v1) == v);
|
assert(float(v1) == v);
|
||||||
double v2{nextf};
|
double v2{nextf};
|
||||||
@ -84,16 +99,18 @@ bool allvalues() {
|
|||||||
double midv{v1 + (v2 - v1) / 2};
|
double midv{v1 + (v2 - v1) / 2};
|
||||||
float expected_midv = float(midv);
|
float expected_midv = float(midv);
|
||||||
|
|
||||||
const char *string_end = to_string(midv, buffer);
|
char const *string_end = to_string(midv, buffer);
|
||||||
float str_answer;
|
float str_answer;
|
||||||
strtof_from_string(buffer, str_answer);
|
strtof_from_string(buffer, str_answer);
|
||||||
|
|
||||||
float result_value;
|
float result_value;
|
||||||
auto result = fast_float::from_chars(buffer, string_end, result_value);
|
auto result = fast_float::from_chars(buffer, string_end, result_value);
|
||||||
// Starting with version 4.0 for fast_float, we return result_out_of_range if the
|
// Starting with version 4.0 for fast_float, we return result_out_of_range
|
||||||
// value is either too small (too close to zero) or too large (effectively infinity).
|
// if the value is either too small (too close to zero) or too large
|
||||||
// So std::errc::result_out_of_range is normal for well-formed input strings.
|
// (effectively infinity). So std::errc::result_out_of_range is normal for
|
||||||
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
// well-formed input strings.
|
||||||
|
if (result.ec != std::errc() &&
|
||||||
|
result.ec != std::errc::result_out_of_range) {
|
||||||
std::cerr << "parsing error ? " << buffer << std::endl;
|
std::cerr << "parsing error ? " << buffer << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -103,26 +120,30 @@ bool allvalues() {
|
|||||||
std::cerr << "v " << std::hexfloat << v << std::endl;
|
std::cerr << "v " << std::hexfloat << v << std::endl;
|
||||||
std::cerr << "v2 " << std::hexfloat << v2 << std::endl;
|
std::cerr << "v2 " << std::hexfloat << v2 << std::endl;
|
||||||
std::cerr << "midv " << std::hexfloat << midv << std::endl;
|
std::cerr << "midv " << std::hexfloat << midv << std::endl;
|
||||||
std::cerr << "expected_midv " << std::hexfloat << expected_midv << std::endl;
|
std::cerr << "expected_midv " << std::hexfloat << expected_midv
|
||||||
|
<< std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if(copysign(1,result_value) != copysign(1,v)) {
|
} else if (copysign(1, result_value) != copysign(1, v)) {
|
||||||
std::cerr << buffer << std::endl;
|
std::cerr << buffer << std::endl;
|
||||||
std::cerr << "v " << std::hexfloat << v << std::endl;
|
std::cerr << "v " << std::hexfloat << v << std::endl;
|
||||||
std::cerr << "v2 " << std::hexfloat << v2 << std::endl;
|
std::cerr << "v2 " << std::hexfloat << v2 << std::endl;
|
||||||
std::cerr << "midv " << std::hexfloat << midv << std::endl;
|
std::cerr << "midv " << std::hexfloat << midv << std::endl;
|
||||||
std::cerr << "expected_midv " << std::hexfloat << expected_midv << std::endl;
|
std::cerr << "expected_midv " << std::hexfloat << expected_midv
|
||||||
std::cerr << "I got " << std::hexfloat << result_value << " but I was expecting " << v
|
<< std::endl;
|
||||||
<< std::endl;
|
std::cerr << "I got " << std::hexfloat << result_value
|
||||||
|
<< " but I was expecting " << v << std::endl;
|
||||||
return false;
|
return false;
|
||||||
} else if (result_value != str_answer) {
|
} else if (result_value != str_answer) {
|
||||||
std::cerr << "no match ? " << buffer << std::endl;
|
std::cerr << "no match ? " << buffer << std::endl;
|
||||||
std::cerr << "v " << std::hexfloat << v << std::endl;
|
std::cerr << "v " << std::hexfloat << v << std::endl;
|
||||||
std::cerr << "v2 " << std::hexfloat << v2 << std::endl;
|
std::cerr << "v2 " << std::hexfloat << v2 << std::endl;
|
||||||
std::cerr << "midv " << std::hexfloat << midv << std::endl;
|
std::cerr << "midv " << std::hexfloat << midv << std::endl;
|
||||||
std::cerr << "expected_midv " << std::hexfloat << expected_midv << std::endl;
|
std::cerr << "expected_midv " << std::hexfloat << expected_midv
|
||||||
|
<< std::endl;
|
||||||
std::cout << "started with " << std::hexfloat << midv << std::endl;
|
std::cout << "started with " << std::hexfloat << midv << std::endl;
|
||||||
std::cout << "round down to " << std::hexfloat << str_answer << std::endl;
|
std::cout << "round down to " << std::hexfloat << str_answer
|
||||||
|
<< std::endl;
|
||||||
std::cout << "got back " << std::hexfloat << result_value << std::endl;
|
std::cout << "got back " << std::hexfloat << result_value << std::endl;
|
||||||
std::cout << std::dec;
|
std::cout << std::dec;
|
||||||
return false;
|
return false;
|
||||||
@ -134,15 +155,25 @@ bool allvalues() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void Assert(bool Assertion) {
|
inline void Assert(bool Assertion) {
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
if (!Assertion) { std::cerr << "Omitting hard failure on msys/cygwin/sun systems."; }
|
defined(sun) || defined(__sun)
|
||||||
|
if (!Assertion) {
|
||||||
|
std::cerr << "Omitting hard failure on msys/cygwin/sun systems.";
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
if (!Assertion) { throw std::runtime_error("bug"); }
|
if (!Assertion) {
|
||||||
|
throw std::runtime_error("bug");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
std::cout << "Warning: msys/cygwin or solaris detected. This particular test is likely to generate false failures due to our reliance on the underlying runtime library as a gold standard." << std::endl;
|
defined(sun) || defined(__sun)
|
||||||
|
std::cout << "Warning: msys/cygwin or solaris detected. This particular test "
|
||||||
|
"is likely to generate false failures due to our reliance on "
|
||||||
|
"the underlying runtime library as a gold standard."
|
||||||
|
<< std::endl;
|
||||||
#endif
|
#endif
|
||||||
Assert(allvalues());
|
Assert(allvalues());
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|||||||
1481
tests/fast_int.cpp
1481
tests/fast_int.cpp
File diff suppressed because it is too large
Load Diff
70
tests/fixedwidthtest.cpp
Normal file
70
tests/fixedwidthtest.cpp
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstring>
|
||||||
|
#include "fast_float/fast_float.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#if __cplusplus >= 202300L
|
||||||
|
#include <stdfloat>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Write some testcases for the parsing of floating point numbers in the
|
||||||
|
// float32_t type. We use the from_chars function defined in this library.
|
||||||
|
#ifdef __STDCPP_FLOAT32_T__
|
||||||
|
std::vector<std::float32_t> const float32_test_expected{
|
||||||
|
123.456f, -78.9f, 0.0001f, 3.40282e+038f};
|
||||||
|
std::vector<std::string_view> const float32_test{"123.456", "-78.9", "0.0001",
|
||||||
|
"3.40282e+038"};
|
||||||
|
std::cout << "runing float32 test" << std::endl;
|
||||||
|
for (std::size_t i = 0; i < float32_test.size(); ++i) {
|
||||||
|
auto const &f = float32_test[i];
|
||||||
|
std::float32_t result;
|
||||||
|
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
|
||||||
|
|
||||||
|
if (answer.ec != std::errc()) {
|
||||||
|
std::cerr << "Failed to parse: \"" << f << "\"" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (result != float32_test_expected[i]) {
|
||||||
|
std::cerr << "Test failed for input: \"" << f << "\" expected "
|
||||||
|
<< float32_test_expected[i] << " got " << result << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
std::cout << "No std::float32_t type available." << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __STDCPP_FLOAT64_T__
|
||||||
|
// Test cases for std::float64_t
|
||||||
|
std::vector<std::float64_t> const float64_test_expected{
|
||||||
|
1.23e4, -5.67e-8, 1.7976931348623157e+308, -1.7976931348623157e+308};
|
||||||
|
std::vector<std::string_view> const float64_test{"1.23e4", "-5.67e-8",
|
||||||
|
"1.7976931348623157e+308",
|
||||||
|
"-1.7976931348623157e+308"};
|
||||||
|
std::cout << "runing float64 test" << std::endl;
|
||||||
|
for (std::size_t i = 0; i < float64_test.size(); ++i) {
|
||||||
|
auto const &f = float64_test[i];
|
||||||
|
std::float64_t result;
|
||||||
|
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
|
||||||
|
|
||||||
|
if (answer.ec != std::errc()) {
|
||||||
|
std::cerr << "Failed to parse: \"" << f << "\"" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (result != float64_test_expected[i]) {
|
||||||
|
std::cerr << "Test failed for input: \"" << f << "\" expected "
|
||||||
|
<< float64_test_expected[i] << " got " << result << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
std::cout << "No std::float64_t type available." << std::endl;
|
||||||
|
#endif
|
||||||
|
std::cout << "All tests passed successfully." << std::endl;
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@ -4,66 +4,72 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#define FASTFLOAT_ALLOWS_LEADING_PLUS
|
|
||||||
|
|
||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
|
|
||||||
int main_readme() {
|
int main_readme() {
|
||||||
const std::string input = "1d+4";
|
std::string const input = "1d+4";
|
||||||
double result;
|
double result;
|
||||||
fast_float::parse_options options{ fast_float::chars_format::fortran };
|
fast_float::parse_options options{
|
||||||
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
|
fast_float::chars_format::fortran |
|
||||||
if((answer.ec != std::errc()) || ((result != 10000))) { std::cerr << "parsing failure\n" << result <<"\n"; return EXIT_FAILURE; }
|
fast_float::chars_format::allow_leading_plus};
|
||||||
std::cout << "parsed the number " << result << std::endl;
|
auto answer = fast_float::from_chars_advanced(
|
||||||
return EXIT_SUCCESS;
|
input.data(), input.data() + input.size(), result, options);
|
||||||
|
if ((answer.ec != std::errc()) || ((result != 10000))) {
|
||||||
|
std::cerr << "parsing failure\n" << result << "\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
std::cout << "parsed the number " << result << std::endl;
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main ()
|
int main() {
|
||||||
{
|
std::vector<double> const expected{10000, 1000, 100, 10, 1,
|
||||||
const std::vector<double> expected{ 10000, 1000, 100, 10, 1, .1, .01, .001, .0001 };
|
.1, .01, .001, .0001};
|
||||||
const std::vector<std::string> fmt1{ "1+4", "1+3", "1+2", "1+1", "1+0", "1-1", "1-2",
|
std::vector<std::string> const fmt1{"1+4", "1+3", "1+2", "1+1", "1+0",
|
||||||
"1-3", "1-4" };
|
"1-1", "1-2", "1-3", "1-4"};
|
||||||
const std::vector<std::string> fmt2{ "1d+4", "1d+3", "1d+2", "1d+1", "1d+0", "1d-1",
|
std::vector<std::string> const fmt2{"1d+4", "1d+3", "1d+2", "1d+1", "1d+0",
|
||||||
"1d-2", "1d-3", "1d-4" };
|
"1d-1", "1d-2", "1d-3", "1d-4"};
|
||||||
const std::vector<std::string> fmt3{ "+1+4", "+1+3", "+1+2", "+1+1", "+1+0", "+1-1",
|
std::vector<std::string> const fmt3{"+1+4", "+1+3", "+1+2", "+1+1", "+1+0",
|
||||||
"+1-2", "+1-3", "+1-4" };
|
"+1-1", "+1-2", "+1-3", "+1-4"};
|
||||||
const fast_float::parse_options options{ fast_float::chars_format::fortran };
|
fast_float::parse_options const options{
|
||||||
|
fast_float::chars_format::fortran |
|
||||||
|
fast_float::chars_format::allow_leading_plus};
|
||||||
|
|
||||||
for ( auto const& f : fmt1 ) {
|
for (auto const &f : fmt1) {
|
||||||
auto d{ std::distance( &fmt1[0], &f ) };
|
auto d{std::distance(&fmt1[0], &f)};
|
||||||
double result;
|
double result;
|
||||||
auto answer{ fast_float::from_chars_advanced( f.data(), f.data()+f.size(), result,
|
auto answer{fast_float::from_chars_advanced(f.data(), f.data() + f.size(),
|
||||||
options ) };
|
result, options)};
|
||||||
if ( answer.ec != std::errc() || result != expected[std::size_t(d)] ) {
|
if (answer.ec != std::errc() || result != expected[std::size_t(d)]) {
|
||||||
std::cerr << "parsing failure on " << f << std::endl;
|
std::cerr << "parsing failure on " << f << std::endl;
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( auto const& f : fmt2 ) {
|
for (auto const &f : fmt2) {
|
||||||
auto d{ std::distance( &fmt2[0], &f ) };
|
auto d{std::distance(&fmt2[0], &f)};
|
||||||
double result;
|
double result;
|
||||||
auto answer{ fast_float::from_chars_advanced( f.data(), f.data()+f.size(), result,
|
auto answer{fast_float::from_chars_advanced(f.data(), f.data() + f.size(),
|
||||||
options ) };
|
result, options)};
|
||||||
if ( answer.ec != std::errc() || result != expected[std::size_t(d)] ) {
|
if (answer.ec != std::errc() || result != expected[std::size_t(d)]) {
|
||||||
std::cerr << "parsing failure on " << f << std::endl;
|
std::cerr << "parsing failure on " << f << std::endl;
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( auto const& f : fmt3 ) {
|
for (auto const &f : fmt3) {
|
||||||
auto d{ std::distance( &fmt3[0], &f ) };
|
auto d{std::distance(&fmt3[0], &f)};
|
||||||
double result;
|
double result;
|
||||||
auto answer{ fast_float::from_chars_advanced( f.data(), f.data()+f.size(), result,
|
auto answer{fast_float::from_chars_advanced(f.data(), f.data() + f.size(),
|
||||||
options ) };
|
result, options)};
|
||||||
if ( answer.ec != std::errc() || result != expected[std::size_t(d)] ) {
|
if (answer.ec != std::errc() || result != expected[std::size_t(d)]) {
|
||||||
std::cerr << "parsing failure on " << f << std::endl;
|
std::cerr << "parsing failure on " << f << std::endl;
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(main_readme() != EXIT_SUCCESS) { return EXIT_FAILURE; }
|
if (main_readme() != EXIT_SUCCESS) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ project(test_install VERSION 0.1.0 LANGUAGES CXX)
|
|||||||
|
|
||||||
set(FASTFLOAT_CXX_STANDARD 17 CACHE STRING "the C++ standard to use for fastfloat")
|
set(FASTFLOAT_CXX_STANDARD 17 CACHE STRING "the C++ standard to use for fastfloat")
|
||||||
set(CMAKE_CXX_STANDARD ${FASTFLOAT_CXX_STANDARD})
|
set(CMAKE_CXX_STANDARD ${FASTFLOAT_CXX_STANDARD})
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
if(MSVC_VERSION GREATER 1910)
|
if(MSVC_VERSION GREATER 1910)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -permissive-")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -permissive-")
|
||||||
@ -18,10 +17,10 @@ file(WRITE main.cpp "
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
const std::string input = \"3.1416 xyz \";
|
std::string input = \"3.1416 xyz \";
|
||||||
double result;
|
double result;
|
||||||
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
|
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
|
||||||
if(answer.ec != std::errc()) { std::cerr << \"parsing failure\\n\"; return EXIT_FAILURE; }
|
if (answer.ec != std::errc()) { std::cerr << \"parsing failure\\n\"; return EXIT_FAILURE; }
|
||||||
std::cout << \"parsed the number \" << result << std::endl;
|
std::cout << \"parsed the number \" << result << std::endl;
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}")
|
}")
|
||||||
|
|||||||
@ -2,70 +2,169 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// test that this option is ignored
|
|
||||||
#define FASTFLOAT_ALLOWS_LEADING_PLUS
|
|
||||||
|
|
||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
int main_readme() {
|
int main_readme() {
|
||||||
const std::string input = "+.1"; // not valid
|
std::string const input = "+.1"; // not valid
|
||||||
double result;
|
double result;
|
||||||
fast_float::parse_options options{ fast_float::chars_format::json };
|
fast_float::parse_options options{
|
||||||
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
|
fast_float::chars_format::json |
|
||||||
if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; }
|
fast_float::chars_format::allow_leading_plus}; // should be ignored
|
||||||
return EXIT_SUCCESS;
|
auto answer = fast_float::from_chars_advanced(
|
||||||
|
input.data(), input.data() + input.size(), result, options);
|
||||||
|
if (answer.ec == std::errc()) {
|
||||||
|
std::cerr << "should have failed\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int main_readme2() {
|
int main_readme2() {
|
||||||
const std::string input = "inf"; // not valid in JSON
|
std::string const input = "inf"; // not valid in JSON
|
||||||
double result;
|
double result;
|
||||||
fast_float::parse_options options{ fast_float::chars_format::json };
|
fast_float::parse_options options{
|
||||||
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
|
fast_float::chars_format::json |
|
||||||
if(answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; }
|
fast_float::chars_format::allow_leading_plus}; // should be ignored
|
||||||
return EXIT_SUCCESS;
|
auto answer = fast_float::from_chars_advanced(
|
||||||
|
input.data(), input.data() + input.size(), result, options);
|
||||||
|
if (answer.ec == std::errc()) {
|
||||||
|
std::cerr << "should have failed\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main_readme3() {
|
int main_readme3() {
|
||||||
const std::string input = "inf"; // not valid in JSON but we allow it with json_or_infnan
|
std::string const input =
|
||||||
double result;
|
"inf"; // not valid in JSON but we allow it with json_or_infnan
|
||||||
fast_float::parse_options options{ fast_float::chars_format::json_or_infnan };
|
double result;
|
||||||
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
|
fast_float::parse_options options{
|
||||||
if(answer.ec != std::errc() || (!std::isinf(result))) { std::cerr << "should have parsed infinity\n"; return EXIT_FAILURE; }
|
fast_float::chars_format::json_or_infnan |
|
||||||
return EXIT_SUCCESS;
|
fast_float::chars_format::allow_leading_plus}; // should be ignored
|
||||||
|
auto answer = fast_float::from_chars_advanced(
|
||||||
|
input.data(), input.data() + input.size(), result, options);
|
||||||
|
if (answer.ec != std::errc() || (!std::isinf(result))) {
|
||||||
|
std::cerr << "should have parsed infinity\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main()
|
struct ExpectedResult {
|
||||||
{
|
double value;
|
||||||
const std::vector<double> expected{ -0.2, 0.02, 0.002, 1., 0., std::numeric_limits<double>::infinity() };
|
std::string junk_chars;
|
||||||
const std::vector<std::string> accept{ "-0.2", "0.02", "0.002", "1e+0000", "0e-2", "inf" };
|
};
|
||||||
const std::vector<std::string> reject{ "-.2", "00.02", "0.e+1", "00.e+1", ".25", "+0.25", "inf", "nan(snan)" };
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < accept.size(); ++i)
|
struct AcceptedValue {
|
||||||
{
|
std::string input;
|
||||||
const auto& f = accept[i];
|
ExpectedResult expected;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RejectReason {
|
||||||
|
fast_float::parse_error error;
|
||||||
|
intptr_t location_offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RejectedValue {
|
||||||
|
std::string input;
|
||||||
|
RejectReason reason;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::vector<AcceptedValue> const accept{
|
||||||
|
{"-0.2", {-0.2, ""}},
|
||||||
|
{"0.02", {0.02, ""}},
|
||||||
|
{"0.002", {0.002, ""}},
|
||||||
|
{"1e+0000", {1., ""}},
|
||||||
|
{"0e-2", {0., ""}},
|
||||||
|
{"1e", {1., "e"}},
|
||||||
|
{"1e+", {1., "e+"}},
|
||||||
|
{"inf", {std::numeric_limits<double>::infinity(), ""}}};
|
||||||
|
std::vector<RejectedValue> const reject{
|
||||||
|
{"-.2", {fast_float::parse_error::missing_integer_after_sign, 1}},
|
||||||
|
{"00.02", {fast_float::parse_error::leading_zeros_in_integer_part, 0}},
|
||||||
|
{"0.e+1", {fast_float::parse_error::no_digits_in_fractional_part, 2}},
|
||||||
|
{"00.e+1", {fast_float::parse_error::leading_zeros_in_integer_part, 0}},
|
||||||
|
{".25", {fast_float::parse_error::no_digits_in_integer_part, 0}},
|
||||||
|
// The following cases already start as invalid JSON, so they are
|
||||||
|
// handled as trailing junk and the error is for not having digits in the
|
||||||
|
// empty string before the invalid token.
|
||||||
|
{"+0.25", {fast_float::parse_error::no_digits_in_integer_part, 0}},
|
||||||
|
{"inf", {fast_float::parse_error::no_digits_in_integer_part, 0}},
|
||||||
|
{"nan(snan)", {fast_float::parse_error::no_digits_in_integer_part, 0}}};
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < accept.size(); ++i) {
|
||||||
|
auto const &s = accept[i].input;
|
||||||
|
auto const &expected = accept[i].expected;
|
||||||
double result;
|
double result;
|
||||||
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, fast_float::chars_format::json_or_infnan);
|
auto answer =
|
||||||
if (answer.ec != std::errc() || result != expected[i]) {
|
fast_float::from_chars(s.data(), s.data() + s.size(), result,
|
||||||
std::cerr << "json fmt rejected valid json " << f << std::endl;
|
fast_float::chars_format::json_or_infnan);
|
||||||
|
if (answer.ec != std::errc()) {
|
||||||
|
std::cerr << "json fmt rejected valid json " << s << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (result != expected.value) {
|
||||||
|
std::cerr << "json fmt gave wrong result " << s << " (expected "
|
||||||
|
<< expected.value << " got " << result << ")" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (std::string(answer.ptr) != expected.junk_chars) {
|
||||||
|
std::cerr << "json fmt has wrong trailing characters " << s
|
||||||
|
<< " (expected " << expected.junk_chars << " got " << answer.ptr
|
||||||
|
<< ")" << std::endl;
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::size_t i = 0; i < reject.size(); ++i)
|
for (std::size_t i = 0; i < reject.size(); ++i) {
|
||||||
{
|
auto const &s = reject[i].input;
|
||||||
const auto& f = reject[i];
|
|
||||||
double result;
|
double result;
|
||||||
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, fast_float::chars_format::json);
|
auto answer = fast_float::from_chars(s.data(), s.data() + s.size(), result,
|
||||||
|
fast_float::chars_format::json);
|
||||||
if (answer.ec == std::errc()) {
|
if (answer.ec == std::errc()) {
|
||||||
std::cerr << "json fmt accepted invalid json " << f << std::endl;
|
std::cerr << "json fmt accepted invalid json " << s << std::endl;
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(main_readme() != EXIT_SUCCESS) { return EXIT_FAILURE; }
|
for (std::size_t i = 0; i < reject.size(); ++i) {
|
||||||
if(main_readme2() != EXIT_SUCCESS) { return EXIT_FAILURE; }
|
auto const &f = reject[i].input;
|
||||||
|
auto const &expected_reason = reject[i].reason;
|
||||||
|
auto answer = fast_float::parse_number_string<true>(
|
||||||
|
f.data(), f.data() + f.size(),
|
||||||
|
fast_float::parse_options(
|
||||||
|
fast_float::chars_format::json |
|
||||||
|
fast_float::chars_format::allow_leading_plus)); // should be ignored
|
||||||
|
if (answer.valid) {
|
||||||
|
std::cerr << "json parse accepted invalid json " << f << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (answer.error != expected_reason.error) {
|
||||||
|
std::cerr << "json parse failure had invalid error reason " << f
|
||||||
|
<< std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
intptr_t error_location = answer.lastmatch - f.data();
|
||||||
|
if (error_location != expected_reason.location_offset) {
|
||||||
|
std::cerr << "json parse failure had invalid error location " << f
|
||||||
|
<< " (expected " << expected_reason.location_offset << " got "
|
||||||
|
<< error_location << ")" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (main_readme() != EXIT_SUCCESS) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (main_readme2() != EXIT_SUCCESS) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
#ifndef __FAST_MATH__
|
||||||
|
if (main_readme3() != EXIT_SUCCESS) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
@ -9,8 +9,7 @@
|
|||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
|
||||||
template <typename T> char *to_string(T d, char *buffer) {
|
template <typename T> char *to_string(T d, char *buffer) {
|
||||||
auto written = std::snprintf(buffer, 128, "%.*e",
|
auto written = std::snprintf(buffer, 128, "%.*e", 64, d);
|
||||||
64, d);
|
|
||||||
return buffer + written;
|
return buffer + written;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,13 +25,15 @@ void allvalues() {
|
|||||||
memcpy(&v, &word, sizeof(v));
|
memcpy(&v, &word, sizeof(v));
|
||||||
|
|
||||||
{
|
{
|
||||||
const char *string_end = to_string(v, buffer);
|
char const *string_end = to_string(v, buffer);
|
||||||
float result_value;
|
float result_value;
|
||||||
auto result = fast_float::from_chars(buffer, string_end, result_value);
|
auto result = fast_float::from_chars(buffer, string_end, result_value);
|
||||||
// Starting with version 4.0 for fast_float, we return result_out_of_range if the
|
// Starting with version 4.0 for fast_float, we return result_out_of_range
|
||||||
// value is either too small (too close to zero) or too large (effectively infinity).
|
// if the value is either too small (too close to zero) or too large
|
||||||
// So std::errc::result_out_of_range is normal for well-formed input strings.
|
// (effectively infinity). So std::errc::result_out_of_range is normal for
|
||||||
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
// well-formed input strings.
|
||||||
|
if (result.ec != std::errc() &&
|
||||||
|
result.ec != std::errc::result_out_of_range) {
|
||||||
std::cerr << "parsing error ? " << buffer << std::endl;
|
std::cerr << "parsing error ? " << buffer << std::endl;
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
@ -41,13 +42,14 @@ void allvalues() {
|
|||||||
std::cerr << "not nan" << buffer << std::endl;
|
std::cerr << "not nan" << buffer << std::endl;
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
} else if(copysign(1,result_value) != copysign(1,v)) {
|
} else if (copysign(1, result_value) != copysign(1, v)) {
|
||||||
std::cerr << buffer << std::endl;
|
std::cerr << buffer << std::endl;
|
||||||
std::cerr << "I got " << std::hexfloat << result_value << " but I was expecting " << v
|
std::cerr << "I got " << std::hexfloat << result_value
|
||||||
<< std::endl;
|
<< " but I was expecting " << v << std::endl;
|
||||||
abort();
|
abort();
|
||||||
} else if (result_value != v) {
|
} else if (result_value != v) {
|
||||||
std::cerr << "no match ? " << buffer << " got " << result_value << " expected " << v << std::endl;
|
std::cerr << "no match ? " << buffer << " got " << result_value
|
||||||
|
<< " expected " << v << std::endl;
|
||||||
std::cout << "started with " << std::hexfloat << v << std::endl;
|
std::cout << "started with " << std::hexfloat << v << std::endl;
|
||||||
std::cout << "got back " << std::hexfloat << result_value << std::endl;
|
std::cout << "got back " << std::hexfloat << result_value << std::endl;
|
||||||
std::cout << std::dec;
|
std::cout << std::dec;
|
||||||
|
|||||||
@ -7,8 +7,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
template <typename T> char *to_string(T d, char *buffer) {
|
template <typename T> char *to_string(T d, char *buffer) {
|
||||||
auto written = std::snprintf(buffer, 128, "%.*e",
|
auto written = std::snprintf(buffer, 128, "%.*e", 64, d);
|
||||||
64, d);
|
|
||||||
return buffer + written;
|
return buffer + written;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,13 +24,15 @@ void all_32bit_values() {
|
|||||||
double v = v32;
|
double v = v32;
|
||||||
|
|
||||||
{
|
{
|
||||||
const char *string_end = to_string(v, buffer);
|
char const *string_end = to_string(v, buffer);
|
||||||
double result_value;
|
double result_value;
|
||||||
auto result = fast_float::from_chars(buffer, string_end, result_value);
|
auto result = fast_float::from_chars(buffer, string_end, result_value);
|
||||||
// Starting with version 4.0 for fast_float, we return result_out_of_range if the
|
// Starting with version 4.0 for fast_float, we return result_out_of_range
|
||||||
// value is either too small (too close to zero) or too large (effectively infinity).
|
// if the value is either too small (too close to zero) or too large
|
||||||
// So std::errc::result_out_of_range is normal for well-formed input strings.
|
// (effectively infinity). So std::errc::result_out_of_range is normal for
|
||||||
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
// well-formed input strings.
|
||||||
|
if (result.ec != std::errc() &&
|
||||||
|
result.ec != std::errc::result_out_of_range) {
|
||||||
std::cerr << "parsing error ? " << buffer << std::endl;
|
std::cerr << "parsing error ? " << buffer << std::endl;
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
@ -40,9 +41,9 @@ void all_32bit_values() {
|
|||||||
std::cerr << "not nan" << buffer << std::endl;
|
std::cerr << "not nan" << buffer << std::endl;
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
} else if(copysign(1,result_value) != copysign(1,v)) {
|
} else if (copysign(1, result_value) != copysign(1, v)) {
|
||||||
std::cerr << "I got " << std::hexfloat << result_value << " but I was expecting " << v
|
std::cerr << "I got " << std::hexfloat << result_value
|
||||||
<< std::endl;
|
<< " but I was expecting " << v << std::endl;
|
||||||
abort();
|
abort();
|
||||||
} else if (std::isnan(v)) {
|
} else if (std::isnan(v)) {
|
||||||
if (!std::isnan(result_value)) {
|
if (!std::isnan(result_value)) {
|
||||||
|
|||||||
@ -8,8 +8,7 @@
|
|||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
|
||||||
template <typename T> char *to_string(T d, char *buffer) {
|
template <typename T> char *to_string(T d, char *buffer) {
|
||||||
auto written = std::snprintf(buffer, 128, "%.*e",
|
auto written = std::snprintf(buffer, 128, "%.*e", 64, d);
|
||||||
64, d);
|
|
||||||
return buffer + written;
|
return buffer + written;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +32,8 @@ static inline void lehmer64_seed(uint64_t seed) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline uint64_t lehmer64() {
|
static inline uint64_t lehmer64() {
|
||||||
fast_float::value128 v = fast_float::full_multiplication(g_lehmer64_state.low,UINT64_C(0xda942042e4dd58b5));
|
fast_float::value128 v = fast_float::full_multiplication(
|
||||||
|
g_lehmer64_state.low, UINT64_C(0xda942042e4dd58b5));
|
||||||
v.high += g_lehmer64_state.high * UINT64_C(0xda942042e4dd58b5);
|
v.high += g_lehmer64_state.high * UINT64_C(0xda942042e4dd58b5);
|
||||||
g_lehmer64_state = v;
|
g_lehmer64_state = v;
|
||||||
return v.high;
|
return v.high;
|
||||||
@ -53,13 +53,15 @@ void random_values(size_t N) {
|
|||||||
double v;
|
double v;
|
||||||
memcpy(&v, &word, sizeof(v));
|
memcpy(&v, &word, sizeof(v));
|
||||||
{
|
{
|
||||||
const char *string_end = to_string(v, buffer);
|
char const *string_end = to_string(v, buffer);
|
||||||
double result_value;
|
double result_value;
|
||||||
auto result = fast_float::from_chars(buffer, string_end, result_value);
|
auto result = fast_float::from_chars(buffer, string_end, result_value);
|
||||||
// Starting with version 4.0 for fast_float, we return result_out_of_range if the
|
// Starting with version 4.0 for fast_float, we return result_out_of_range
|
||||||
// value is either too small (too close to zero) or too large (effectively infinity).
|
// if the value is either too small (too close to zero) or too large
|
||||||
// So std::errc::result_out_of_range is normal for well-formed input strings.
|
// (effectively infinity). So std::errc::result_out_of_range is normal for
|
||||||
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
// well-formed input strings.
|
||||||
|
if (result.ec != std::errc() &&
|
||||||
|
result.ec != std::errc::result_out_of_range) {
|
||||||
std::cerr << "parsing error ? " << buffer << std::endl;
|
std::cerr << "parsing error ? " << buffer << std::endl;
|
||||||
errors++;
|
errors++;
|
||||||
if (errors > 10) {
|
if (errors > 10) {
|
||||||
@ -75,10 +77,10 @@ void random_values(size_t N) {
|
|||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(copysign(1,result_value) != copysign(1,v)) {
|
} else if (copysign(1, result_value) != copysign(1, v)) {
|
||||||
std::cerr << buffer << std::endl;
|
std::cerr << buffer << std::endl;
|
||||||
std::cerr << "I got " << std::hexfloat << result_value << " but I was expecting " << v
|
std::cerr << "I got " << std::hexfloat << result_value
|
||||||
<< std::endl;
|
<< " but I was expecting " << v << std::endl;
|
||||||
abort();
|
abort();
|
||||||
} else if (result_value != v) {
|
} else if (result_value != v) {
|
||||||
std::cerr << "no match ? '" << buffer << "'" << std::endl;
|
std::cerr << "no match ? '" << buffer << "'" << std::endl;
|
||||||
@ -97,7 +99,8 @@ void random_values(size_t N) {
|
|||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
errors = 0;
|
errors = 0;
|
||||||
size_t N = size_t(1) << (sizeof(size_t) * 4); // shift: 32 for 64bit, 16 for 32bit
|
size_t N =
|
||||||
|
size_t(1) << (sizeof(size_t) * 4); // shift: 32 for 64bit, 16 for 32bit
|
||||||
random_values(N);
|
random_values(N);
|
||||||
if (errors == 0) {
|
if (errors == 0) {
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|||||||
@ -8,39 +8,44 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
inline void Assert(bool Assertion) {
|
inline void Assert(bool Assertion) {
|
||||||
if (!Assertion) { throw std::runtime_error("bug"); }
|
if (!Assertion) {
|
||||||
|
throw std::runtime_error("bug");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T> bool test() {
|
||||||
bool test() {
|
std::string input = "0.156250000000000000000000000000000000000000 "
|
||||||
std::string input = "0.156250000000000000000000000000000000000000 3.14159265358979323846264338327950288419716939937510 2.71828182845904523536028747135266249775724709369995";
|
"3.14159265358979323846264338327950288419716939937510 "
|
||||||
std::vector<T> answers = {T(0.15625), T(3.141592653589793), T(2.718281828459045)};
|
"2.71828182845904523536028747135266249775724709369995";
|
||||||
const char * begin = input.data();
|
std::vector<T> answers = {T(0.15625), T(3.141592653589793),
|
||||||
const char * end = input.data() + input.size();
|
T(2.718281828459045)};
|
||||||
for(size_t i = 0; i < answers.size(); i++) {
|
char const *begin = input.data();
|
||||||
|
char const *end = input.data() + input.size();
|
||||||
|
for (size_t i = 0; i < answers.size(); i++) {
|
||||||
T result_value;
|
T result_value;
|
||||||
while((begin < end) && (std::isspace(*begin))) { begin++; }
|
while ((begin < end) && (std::isspace(*begin))) {
|
||||||
auto result = fast_float::from_chars(begin, end,
|
begin++;
|
||||||
result_value);
|
}
|
||||||
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
auto result = fast_float::from_chars(begin, end, result_value);
|
||||||
|
if (result.ec != std::errc() &&
|
||||||
|
result.ec != std::errc::result_out_of_range) {
|
||||||
printf("parsing %.*s\n", int(end - begin), begin);
|
printf("parsing %.*s\n", int(end - begin), begin);
|
||||||
std::cerr << " I could not parse " << std::endl;
|
std::cerr << " I could not parse " << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(result_value != answers[i]) {
|
if (result_value != answers[i]) {
|
||||||
printf("parsing %.*s\n", int(end - begin), begin);
|
printf("parsing %.*s\n", int(end - begin), begin);
|
||||||
std::cerr << " Mismatch " << std::endl;
|
std::cerr << " Mismatch " << std::endl;
|
||||||
std::cerr << " Expected " << answers[i] << std::endl;
|
std::cerr << " Expected " << answers[i] << std::endl;
|
||||||
std::cerr << " Got " << result_value << std::endl;
|
std::cerr << " Got " << result_value << std::endl;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
begin = result.ptr;
|
begin = result.ptr;
|
||||||
}
|
}
|
||||||
if(begin != end) {
|
if (begin != end) {
|
||||||
std::cerr << " bad ending " << std::endl;
|
std::cerr << " bad ending " << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
16
tests/p2497.cpp
Normal file
16
tests/p2497.cpp
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#include "fast_float/fast_float.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::string input = "3.1416 xyz ";
|
||||||
|
double result;
|
||||||
|
if (auto answer = fast_float::from_chars(
|
||||||
|
input.data(), input.data() + input.size(), result)) {
|
||||||
|
std::cout << "parsed the number " << result << std::endl;
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
std::cerr << "failed to parse " << result << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
@ -9,45 +9,55 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
defined(sun) || defined(__sun)
|
||||||
// Anything at all that is related to cygwin, msys and so forth will
|
// Anything at all that is related to cygwin, msys and so forth will
|
||||||
// always use this fallback because we cannot rely on it behaving as normal
|
// always use this fallback because we cannot rely on it behaving as normal
|
||||||
// gcc.
|
// gcc.
|
||||||
#include <locale>
|
#include <locale>
|
||||||
|
|
||||||
// workaround for CYGWIN
|
// workaround for CYGWIN
|
||||||
double cygwin_strtod_l(const char* start, char** end) {
|
double cygwin_strtod_l(char const *start, char **end) {
|
||||||
double d;
|
double d;
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss.imbue(std::locale::classic());
|
ss.imbue(std::locale::classic());
|
||||||
ss << start;
|
ss << start;
|
||||||
ss >> d;
|
ss >> d;
|
||||||
if(ss.fail()) { *end = nullptr; }
|
if (ss.fail()) {
|
||||||
if(ss.eof()) { ss.clear(); }
|
*end = nullptr;
|
||||||
auto nread = ss.tellg();
|
}
|
||||||
*end = const_cast<char*>(start) + nread;
|
if (ss.eof()) {
|
||||||
return d;
|
ss.clear();
|
||||||
|
}
|
||||||
|
auto nread = ss.tellg();
|
||||||
|
*end = const_cast<char *>(start) + nread;
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
float cygwin_strtof_l(const char* start, char** end) {
|
|
||||||
float d;
|
float cygwin_strtof_l(char const *start, char **end) {
|
||||||
std::stringstream ss;
|
float d;
|
||||||
ss.imbue(std::locale::classic());
|
std::stringstream ss;
|
||||||
ss << start;
|
ss.imbue(std::locale::classic());
|
||||||
ss >> d;
|
ss << start;
|
||||||
if(ss.fail()) { *end = nullptr; }
|
ss >> d;
|
||||||
if(ss.eof()) { ss.clear(); }
|
if (ss.fail()) {
|
||||||
auto nread = ss.tellg();
|
*end = nullptr;
|
||||||
*end = const_cast<char*>(start) + nread;
|
}
|
||||||
return d;
|
if (ss.eof()) {
|
||||||
|
ss.clear();
|
||||||
|
}
|
||||||
|
auto nread = ss.tellg();
|
||||||
|
*end = const_cast<char *>(start) + nread;
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
std::pair<double, bool> strtod_from_string(char const *st) {
|
||||||
std::pair<double, bool> strtod_from_string(const char *st) {
|
|
||||||
double d;
|
double d;
|
||||||
char *pr;
|
char *pr;
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
d = cygwin_strtod_l(st, &pr);
|
defined(sun) || defined(__sun)
|
||||||
|
d = cygwin_strtod_l(st, &pr);
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
||||||
d = _strtod_l(st, &pr, c_locale);
|
d = _strtod_l(st, &pr, c_locale);
|
||||||
@ -65,7 +75,8 @@ std::pair<double, bool> strtod_from_string(const char *st) {
|
|||||||
std::pair<float, bool> strtof_from_string(char *st) {
|
std::pair<float, bool> strtof_from_string(char *st) {
|
||||||
float d;
|
float d;
|
||||||
char *pr;
|
char *pr;
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
|
defined(sun) || defined(__sun)
|
||||||
d = cygwin_strtof_l(st, &pr);
|
d = cygwin_strtof_l(st, &pr);
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
||||||
@ -103,9 +114,11 @@ bool tester() {
|
|||||||
std::pair<double, bool> expected_double =
|
std::pair<double, bool> expected_double =
|
||||||
strtod_from_string(to_be_parsed.c_str());
|
strtod_from_string(to_be_parsed.c_str());
|
||||||
double result_value;
|
double result_value;
|
||||||
auto result =
|
auto result = fast_float::from_chars(
|
||||||
fast_float::from_chars(to_be_parsed.data(), to_be_parsed.data() + to_be_parsed.size(), result_value);
|
to_be_parsed.data(), to_be_parsed.data() + to_be_parsed.size(),
|
||||||
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
result_value);
|
||||||
|
if (result.ec != std::errc() &&
|
||||||
|
result.ec != std::errc::result_out_of_range) {
|
||||||
std::cout << to_be_parsed << std::endl;
|
std::cout << to_be_parsed << std::endl;
|
||||||
std::cerr << " I could not parse " << std::endl;
|
std::cerr << " I could not parse " << std::endl;
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -14,7 +14,6 @@ template <typename T> char *to_string(T d, char *buffer) {
|
|||||||
return buffer + written;
|
return buffer + written;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static fast_float::value128 g_lehmer64_state;
|
static fast_float::value128 g_lehmer64_state;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,7 +34,8 @@ static inline void lehmer64_seed(uint64_t seed) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline uint64_t lehmer64() {
|
static inline uint64_t lehmer64() {
|
||||||
fast_float::value128 v = fast_float::full_multiplication(g_lehmer64_state.low,UINT64_C(0xda942042e4dd58b5));
|
fast_float::value128 v = fast_float::full_multiplication(
|
||||||
|
g_lehmer64_state.low, UINT64_C(0xda942042e4dd58b5));
|
||||||
v.high += g_lehmer64_state.high * UINT64_C(0xda942042e4dd58b5);
|
v.high += g_lehmer64_state.high * UINT64_C(0xda942042e4dd58b5);
|
||||||
g_lehmer64_state = v;
|
g_lehmer64_state = v;
|
||||||
return v.high;
|
return v.high;
|
||||||
@ -56,13 +56,15 @@ void random_values(size_t N) {
|
|||||||
memcpy(&v, &word, sizeof(v));
|
memcpy(&v, &word, sizeof(v));
|
||||||
// if (!std::isnormal(v))
|
// if (!std::isnormal(v))
|
||||||
{
|
{
|
||||||
const char *string_end = to_string(v, buffer);
|
char const *string_end = to_string(v, buffer);
|
||||||
double result_value;
|
double result_value;
|
||||||
auto result = fast_float::from_chars(buffer, string_end, result_value);
|
auto result = fast_float::from_chars(buffer, string_end, result_value);
|
||||||
// Starting with version 4.0 for fast_float, we return result_out_of_range if the
|
// Starting with version 4.0 for fast_float, we return result_out_of_range
|
||||||
// value is either too small (too close to zero) or too large (effectively infinity).
|
// if the value is either too small (too close to zero) or too large
|
||||||
// So std::errc::result_out_of_range is normal for well-formed input strings.
|
// (effectively infinity). So std::errc::result_out_of_range is normal for
|
||||||
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
// well-formed input strings.
|
||||||
|
if (result.ec != std::errc() &&
|
||||||
|
result.ec != std::errc::result_out_of_range) {
|
||||||
std::cerr << "parsing error ? " << buffer << std::endl;
|
std::cerr << "parsing error ? " << buffer << std::endl;
|
||||||
errors++;
|
errors++;
|
||||||
if (errors > 10) {
|
if (errors > 10) {
|
||||||
@ -78,10 +80,10 @@ void random_values(size_t N) {
|
|||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(copysign(1,result_value) != copysign(1,v)) {
|
} else if (copysign(1, result_value) != copysign(1, v)) {
|
||||||
std::cerr << buffer << std::endl;
|
std::cerr << buffer << std::endl;
|
||||||
std::cerr << "I got " << std::hexfloat << result_value << " but I was expecting " << v
|
std::cerr << "I got " << std::hexfloat << result_value
|
||||||
<< std::endl;
|
<< " but I was expecting " << v << std::endl;
|
||||||
abort();
|
abort();
|
||||||
} else if (result_value != v) {
|
} else if (result_value != v) {
|
||||||
std::cerr << "no match ? " << buffer << std::endl;
|
std::cerr << "no match ? " << buffer << std::endl;
|
||||||
@ -100,7 +102,8 @@ void random_values(size_t N) {
|
|||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
errors = 0;
|
errors = 0;
|
||||||
size_t N = size_t(1) << (sizeof(size_t) * 4); // shift: 32 for 64bit, 16 for 32bit
|
size_t N =
|
||||||
|
size_t(1) << (sizeof(size_t) * 4); // shift: 32 for 64bit, 16 for 32bit
|
||||||
random_values(N);
|
random_values(N);
|
||||||
if (errors == 0) {
|
if (errors == 0) {
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|||||||
@ -7,57 +7,74 @@
|
|||||||
#include <system_error>
|
#include <system_error>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
|
defined(sun) || defined(__sun)
|
||||||
// Anything at all that is related to cygwin, msys and so forth will
|
// Anything at all that is related to cygwin, msys and so forth will
|
||||||
// always use this fallback because we cannot rely on it behaving as normal
|
// always use this fallback because we cannot rely on it behaving as normal
|
||||||
// gcc.
|
// gcc.
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
// workaround for CYGWIN
|
// workaround for CYGWIN
|
||||||
double cygwin_strtod_l(const char* start, char** end) {
|
double cygwin_strtod_l(char const *start, char **end) {
|
||||||
double d;
|
double d;
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss.imbue(std::locale::classic());
|
ss.imbue(std::locale::classic());
|
||||||
ss << start;
|
ss << start;
|
||||||
ss >> d;
|
ss >> d;
|
||||||
if(ss.fail()) { *end = nullptr; }
|
if (ss.fail()) {
|
||||||
if(ss.eof()) { ss.clear(); }
|
*end = nullptr;
|
||||||
auto nread = ss.tellg();
|
}
|
||||||
*end = const_cast<char*>(start) + nread;
|
if (ss.eof()) {
|
||||||
return d;
|
ss.clear();
|
||||||
|
}
|
||||||
|
auto nread = ss.tellg();
|
||||||
|
*end = const_cast<char *>(start) + nread;
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
float cygwin_strtof_l(const char* start, char** end) {
|
|
||||||
float d;
|
float cygwin_strtof_l(char const *start, char **end) {
|
||||||
std::stringstream ss;
|
float d;
|
||||||
ss.imbue(std::locale::classic());
|
std::stringstream ss;
|
||||||
ss << start;
|
ss.imbue(std::locale::classic());
|
||||||
ss >> d;
|
ss << start;
|
||||||
if(ss.fail()) { *end = nullptr; }
|
ss >> d;
|
||||||
if(ss.eof()) { ss.clear(); }
|
if (ss.fail()) {
|
||||||
auto nread = ss.tellg();
|
*end = nullptr;
|
||||||
*end = const_cast<char*>(start) + nread;
|
}
|
||||||
return d;
|
if (ss.eof()) {
|
||||||
|
ss.clear();
|
||||||
|
}
|
||||||
|
auto nread = ss.tellg();
|
||||||
|
*end = const_cast<char *>(start) + nread;
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class RandomEngine {
|
class RandomEngine {
|
||||||
public:
|
public:
|
||||||
RandomEngine() = delete;
|
RandomEngine() = delete;
|
||||||
RandomEngine(uint64_t new_seed) : wyhash64_x_(new_seed) {};
|
RandomEngine(uint64_t new_seed) : wyhash64_x_(new_seed){};
|
||||||
|
|
||||||
uint64_t next() {
|
uint64_t next() {
|
||||||
// Adapted from https://github.com/wangyi-fudan/wyhash/blob/master/wyhash.h
|
// Adapted from https://github.com/wangyi-fudan/wyhash/blob/master/wyhash.h
|
||||||
// Inspired from
|
// Inspired from
|
||||||
// https://github.com/lemire/testingRNG/blob/master/source/wyhash.h
|
// https://github.com/lemire/testingRNG/blob/master/source/wyhash.h
|
||||||
wyhash64_x_ += UINT64_C(0x60bee2bee120fc15);
|
wyhash64_x_ += UINT64_C(0x60bee2bee120fc15);
|
||||||
fast_float::value128 tmp = fast_float::full_multiplication(wyhash64_x_, UINT64_C(0xa3b195354a39b70d));
|
fast_float::value128 tmp = fast_float::full_multiplication(
|
||||||
|
wyhash64_x_, UINT64_C(0xa3b195354a39b70d));
|
||||||
uint64_t m1 = (tmp.high) ^ tmp.low;
|
uint64_t m1 = (tmp.high) ^ tmp.low;
|
||||||
tmp = fast_float::full_multiplication(m1, UINT64_C(0x1b03738712fad5c9));
|
tmp = fast_float::full_multiplication(m1, UINT64_C(0x1b03738712fad5c9));
|
||||||
uint64_t m2 = (tmp.high) ^ tmp.low;
|
uint64_t m2 = (tmp.high) ^ tmp.low;
|
||||||
return m2;
|
return m2;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool next_bool() { return (next() & 1) == 1; }
|
bool next_bool() { return (next() & 1) == 1; }
|
||||||
|
|
||||||
int next_int() { return static_cast<int>(next()); }
|
int next_int() { return static_cast<int>(next()); }
|
||||||
|
|
||||||
char next_char() { return static_cast<char>(next()); }
|
char next_char() { return static_cast<char>(next()); }
|
||||||
|
|
||||||
double next_double() { return static_cast<double>(next()); }
|
double next_double() { return static_cast<double>(next()); }
|
||||||
|
|
||||||
int next_ranged_int(int min, int max) { // min and max are included
|
int next_ranged_int(int min, int max) { // min and max are included
|
||||||
@ -68,7 +85,7 @@ public:
|
|||||||
}*/
|
}*/
|
||||||
uint64_t s = uint64_t(max - min + 1);
|
uint64_t s = uint64_t(max - min + 1);
|
||||||
uint64_t x = next();
|
uint64_t x = next();
|
||||||
fast_float::value128 m = fast_float::full_multiplication(x, s);
|
fast_float::value128 m = fast_float::full_multiplication(x, s);
|
||||||
uint64_t l = m.low;
|
uint64_t l = m.low;
|
||||||
if (l < s) {
|
if (l < s) {
|
||||||
uint64_t t = -s % s;
|
uint64_t t = -s % s;
|
||||||
@ -80,6 +97,7 @@ public:
|
|||||||
}
|
}
|
||||||
return int(m.high) + min;
|
return int(m.high) + min;
|
||||||
}
|
}
|
||||||
|
|
||||||
int next_digit() { return next_ranged_int(0, 9); }
|
int next_digit() { return next_ranged_int(0, 9); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -92,8 +110,9 @@ size_t build_random_string(RandomEngine &rand, char *buffer) {
|
|||||||
buffer[pos++] = '-';
|
buffer[pos++] = '-';
|
||||||
}
|
}
|
||||||
int number_of_digits = rand.next_ranged_int(1, 100);
|
int number_of_digits = rand.next_ranged_int(1, 100);
|
||||||
if(number_of_digits == 100) {
|
if (number_of_digits == 100) {
|
||||||
// With low probability, we want to allow very long strings just to stress the system.
|
// With low probability, we want to allow very long strings just to stress
|
||||||
|
// the system.
|
||||||
number_of_digits = rand.next_ranged_int(1, 2000);
|
number_of_digits = rand.next_ranged_int(1, 2000);
|
||||||
}
|
}
|
||||||
int location_of_decimal_separator = rand.next_ranged_int(1, number_of_digits);
|
int location_of_decimal_separator = rand.next_ranged_int(1, number_of_digits);
|
||||||
@ -103,7 +122,8 @@ size_t build_random_string(RandomEngine &rand, char *buffer) {
|
|||||||
}
|
}
|
||||||
buffer[pos] = char(rand.next_digit() + '0');
|
buffer[pos] = char(rand.next_digit() + '0');
|
||||||
// We can have a leading zero only if location_of_decimal_separator = 1.
|
// We can have a leading zero only if location_of_decimal_separator = 1.
|
||||||
while(i == 0 && 1 != size_t(location_of_decimal_separator) && buffer[pos] == '0') {
|
while (i == 0 && 1 != size_t(location_of_decimal_separator) &&
|
||||||
|
buffer[pos] == '0') {
|
||||||
buffer[pos] = char(rand.next_digit() + '0');
|
buffer[pos] = char(rand.next_digit() + '0');
|
||||||
}
|
}
|
||||||
pos++;
|
pos++;
|
||||||
@ -133,8 +153,9 @@ size_t build_random_string(RandomEngine &rand, char *buffer) {
|
|||||||
std::pair<double, bool> strtod_from_string(char *st) {
|
std::pair<double, bool> strtod_from_string(char *st) {
|
||||||
double d;
|
double d;
|
||||||
char *pr;
|
char *pr;
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
d = cygwin_strtod_l(st, &pr);
|
defined(sun) || defined(__sun)
|
||||||
|
d = cygwin_strtod_l(st, &pr);
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
||||||
d = _strtod_l(st, &pr, c_locale);
|
d = _strtod_l(st, &pr, c_locale);
|
||||||
@ -152,8 +173,9 @@ std::pair<double, bool> strtod_from_string(char *st) {
|
|||||||
std::pair<float, bool> strtof_from_string(char *st) {
|
std::pair<float, bool> strtof_from_string(char *st) {
|
||||||
float d;
|
float d;
|
||||||
char *pr;
|
char *pr;
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
d = cygwin_strtof_l(st, &pr);
|
defined(sun) || defined(__sun)
|
||||||
|
d = cygwin_strtof_l(st, &pr);
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
||||||
d = _strtof_l(st, &pr, c_locale);
|
d = _strtof_l(st, &pr, c_locale);
|
||||||
@ -176,14 +198,18 @@ bool tester(uint64_t seed, size_t volume) {
|
|||||||
char buffer[4096]; // large buffer (can't overflow)
|
char buffer[4096]; // large buffer (can't overflow)
|
||||||
RandomEngine rand(seed);
|
RandomEngine rand(seed);
|
||||||
for (size_t i = 0; i < volume; i++) {
|
for (size_t i = 0; i < volume; i++) {
|
||||||
if((i%100000) == 0) { std::cout << "."; std::cout.flush(); }
|
if ((i % 100000) == 0) {
|
||||||
|
std::cout << ".";
|
||||||
|
std::cout.flush();
|
||||||
|
}
|
||||||
size_t length = build_random_string(rand, buffer);
|
size_t length = build_random_string(rand, buffer);
|
||||||
std::pair<double, bool> expected_double = strtod_from_string(buffer);
|
std::pair<double, bool> expected_double = strtod_from_string(buffer);
|
||||||
if (expected_double.second) {
|
if (expected_double.second) {
|
||||||
double result_value;
|
double result_value;
|
||||||
auto result =
|
auto result =
|
||||||
fast_float::from_chars(buffer, buffer + length, result_value);
|
fast_float::from_chars(buffer, buffer + length, result_value);
|
||||||
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
if (result.ec != std::errc() &&
|
||||||
|
result.ec != std::errc::result_out_of_range) {
|
||||||
printf("parsing %.*s\n", int(length), buffer);
|
printf("parsing %.*s\n", int(length), buffer);
|
||||||
std::cerr << " I could not parse " << std::endl;
|
std::cerr << " I could not parse " << std::endl;
|
||||||
return false;
|
return false;
|
||||||
@ -206,7 +232,8 @@ bool tester(uint64_t seed, size_t volume) {
|
|||||||
float result_value;
|
float result_value;
|
||||||
auto result =
|
auto result =
|
||||||
fast_float::from_chars(buffer, buffer + length, result_value);
|
fast_float::from_chars(buffer, buffer + length, result_value);
|
||||||
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
if (result.ec != std::errc() &&
|
||||||
|
result.ec != std::errc::result_out_of_range) {
|
||||||
printf("parsing %.*s\n", int(length), buffer);
|
printf("parsing %.*s\n", int(length), buffer);
|
||||||
std::cerr << " I could not parse " << std::endl;
|
std::cerr << " I could not parse " << std::endl;
|
||||||
return false;
|
return false;
|
||||||
@ -230,7 +257,8 @@ bool tester(uint64_t seed, size_t volume) {
|
|||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
|
defined(sun) || defined(__sun)
|
||||||
std::cout << "Warning: msys/cygwin or solaris detected." << std::endl;
|
std::cout << "Warning: msys/cygwin or solaris detected." << std::endl;
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
#else
|
#else
|
||||||
|
|||||||
@ -2,91 +2,70 @@
|
|||||||
* See https://github.com/eddelbuettel/rcppfastfloat/issues/4
|
* See https://github.com/eddelbuettel/rcppfastfloat/issues/4
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define FASTFLOAT_ALLOWS_LEADING_PLUS 1
|
|
||||||
#define FASTFLOAT_SKIP_WHITE_SPACE 1 // important !
|
|
||||||
#include "fast_float/fast_float.h"
|
#include "fast_float/fast_float.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
struct test_data {
|
||||||
|
std::string input;
|
||||||
|
bool expected_success;
|
||||||
|
double expected_result;
|
||||||
|
};
|
||||||
|
|
||||||
bool eddelbuettel() {
|
bool eddelbuettel() {
|
||||||
std::vector<std::string> inputs = {"infinity",
|
std::vector<test_data> const test_datas = {
|
||||||
" \r\n\t\f\v3.16227766016838 \r\n\t\f\v",
|
{"infinity", true, std::numeric_limits<double>::infinity()},
|
||||||
" \r\n\t\f\v3 \r\n\t\f\v",
|
{" \r\n\t\f\v3.16227766016838 \r\n\t\f\v", true, 3.16227766016838},
|
||||||
" 1970-01-01",
|
{" \r\n\t\f\v3 \r\n\t\f\v", true, 3.0},
|
||||||
"-NaN",
|
{" 1970-01-01", false, 0.0},
|
||||||
"-inf",
|
{"-NaN", true, std::numeric_limits<double>::quiet_NaN()},
|
||||||
" \r\n\t\f\v2.82842712474619 \r\n\t\f\v",
|
{"-inf", true, -std::numeric_limits<double>::infinity()},
|
||||||
"nan",
|
{" \r\n\t\f\v2.82842712474619 \r\n\t\f\v", true, 2.82842712474619},
|
||||||
" \r\n\t\f\v2.44948974278318 \r\n\t\f\v",
|
{"nan", true, std::numeric_limits<double>::quiet_NaN()},
|
||||||
"Inf",
|
{" \r\n\t\f\v2.44948974278318 \r\n\t\f\v", true, 2.44948974278318},
|
||||||
" \r\n\t\f\v2 \r\n\t\f\v",
|
{"Inf", true, std::numeric_limits<double>::infinity()},
|
||||||
"-infinity",
|
{" \r\n\t\f\v2 \r\n\t\f\v", true, 2.0},
|
||||||
" \r\n\t\f\v0 \r\n\t\f\v",
|
{"-infinity", true, -std::numeric_limits<double>::infinity()},
|
||||||
" \r\n\t\f\v1.73205080756888 \r\n\t\f\v",
|
{" \r\n\t\f\v0 \r\n\t\f\v", true, 0.0},
|
||||||
" \r\n\t\f\v1 \r\n\t\f\v",
|
{" \r\n\t\f\v1.73205080756888 \r\n\t\f\v", true, 1.73205080756888},
|
||||||
" \r\n\t\f\v1.4142135623731 \r\n\t\f\v",
|
{" \r\n\t\f\v1 \r\n\t\f\v", true, 1.0},
|
||||||
" \r\n\t\f\v2.23606797749979 \r\n\t\f\v",
|
{" \r\n\t\f\v1.4142135623731 \r\n\t\f\v", true, 1.4142135623731},
|
||||||
"1970-01-02 ",
|
{" \r\n\t\f\v2.23606797749979 \r\n\t\f\v", true, 2.23606797749979},
|
||||||
" \r\n\t\f\v2.64575131106459 \r\n\t\f\v",
|
{"1970-01-02 ", false, 0.0},
|
||||||
"inf",
|
{" \r\n\t\f\v2.64575131106459 \r\n\t\f\v", true, 2.64575131106459},
|
||||||
"-nan",
|
{"inf", true, std::numeric_limits<double>::infinity()},
|
||||||
"NaN",
|
{"-nan", true, std::numeric_limits<double>::quiet_NaN()},
|
||||||
"",
|
{"NaN", true, std::numeric_limits<double>::quiet_NaN()},
|
||||||
"-Inf",
|
{"", false, 0.0},
|
||||||
"+2.2",
|
{"-Inf", true, -std::numeric_limits<double>::infinity()},
|
||||||
"1d+4",
|
{"+2.2", true, 2.2},
|
||||||
"1d-1",
|
{"1d+4", false, 0.0},
|
||||||
"0.",
|
{"1d-1", false, 0.0},
|
||||||
"-.1",
|
{"0.", true, 0.0},
|
||||||
"+.1",
|
{"-.1", true, -0.1},
|
||||||
"1e+1",
|
{"+.1", true, 0.1},
|
||||||
"+1e1"};
|
{"1e+1", true, 10.0},
|
||||||
std::vector<std::pair<bool, double>> expected_results = {
|
{"+1e1", true, 10.0},
|
||||||
{true, std::numeric_limits<double>::infinity()},
|
{"-+0", false, 0.0},
|
||||||
{true, 3.16227766016838},
|
{"-+inf", false, 0.0},
|
||||||
{true, 3},
|
{"-+nan", false, 0.0},
|
||||||
{false, -1},
|
};
|
||||||
{true, std::numeric_limits<double>::quiet_NaN()},
|
for (size_t i = 0; i < test_datas.size(); i++) {
|
||||||
{true, -std::numeric_limits<double>::infinity()},
|
auto const &input = test_datas[i].input;
|
||||||
{true, 2.82842712474619},
|
auto const expected_success = test_datas[i].expected_success;
|
||||||
{true, std::numeric_limits<double>::quiet_NaN()},
|
auto const expected_result = test_datas[i].expected_result;
|
||||||
{true, 2.44948974278318},
|
|
||||||
{true, std::numeric_limits<double>::infinity()},
|
|
||||||
{true, 2},
|
|
||||||
{true, -std::numeric_limits<double>::infinity()},
|
|
||||||
{true, 0},
|
|
||||||
{true, 1.73205080756888},
|
|
||||||
{true, 1},
|
|
||||||
{true, 1.4142135623731},
|
|
||||||
{true, 2.23606797749979},
|
|
||||||
{false, -1},
|
|
||||||
{true, 2.64575131106459},
|
|
||||||
{true, std::numeric_limits<double>::infinity()},
|
|
||||||
{true, std::numeric_limits<double>::quiet_NaN()},
|
|
||||||
{true, std::numeric_limits<double>::quiet_NaN()},
|
|
||||||
{false, -1},
|
|
||||||
{true, -std::numeric_limits<double>::infinity()},
|
|
||||||
{true, 2.2},
|
|
||||||
{false, -1},
|
|
||||||
{false, -1},
|
|
||||||
{true, 0},
|
|
||||||
{true, -0.1},
|
|
||||||
{true, 0.1},
|
|
||||||
{true, 10},
|
|
||||||
{true, 10},
|
|
||||||
};
|
|
||||||
for (size_t i = 0; i < inputs.size(); i++) {
|
|
||||||
const std::string &input = inputs[i];
|
|
||||||
std::pair<bool, double> expected = expected_results[i];
|
|
||||||
double result;
|
double result;
|
||||||
// answer contains a error code and a pointer to the end of the
|
// answer contains a error code and a pointer to the end of the
|
||||||
// parsed region (on success).
|
// parsed region (on success).
|
||||||
auto answer = fast_float::from_chars(input.data(),
|
auto const answer = fast_float::from_chars(
|
||||||
input.data() + input.size(), result);
|
input.data(), input.data() + input.size(), result,
|
||||||
|
fast_float::chars_format::general |
|
||||||
|
fast_float::chars_format::allow_leading_plus |
|
||||||
|
fast_float::chars_format::skip_white_space);
|
||||||
if (answer.ec != std::errc()) {
|
if (answer.ec != std::errc()) {
|
||||||
std::cout << "could not parse" << std::endl;
|
std::cout << "could not parse" << std::endl;
|
||||||
if (expected.first) {
|
if (expected_success) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@ -94,9 +73,9 @@ bool eddelbuettel() {
|
|||||||
bool non_space_trailing_content = false;
|
bool non_space_trailing_content = false;
|
||||||
if (answer.ptr != input.data() + input.size()) {
|
if (answer.ptr != input.data() + input.size()) {
|
||||||
// check that there is no content left
|
// check that there is no content left
|
||||||
for (const char *leftover = answer.ptr;
|
for (char const *leftover = answer.ptr;
|
||||||
leftover != input.data() + input.size(); leftover++) {
|
leftover != input.data() + input.size(); leftover++) {
|
||||||
if (!fast_float::is_space(uint8_t(*leftover))) {
|
if (!fast_float::is_space(*leftover)) {
|
||||||
non_space_trailing_content = true;
|
non_space_trailing_content = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -104,24 +83,20 @@ bool eddelbuettel() {
|
|||||||
}
|
}
|
||||||
if (non_space_trailing_content) {
|
if (non_space_trailing_content) {
|
||||||
std::cout << "found trailing content " << std::endl;
|
std::cout << "found trailing content " << std::endl;
|
||||||
}
|
if (!expected_success) {
|
||||||
|
|
||||||
if (non_space_trailing_content) {
|
|
||||||
if (!expected.first) {
|
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::cout << "parsed " << result << std::endl;
|
std::cout << "parsed " << result << std::endl;
|
||||||
if (!expected.first) {
|
if (!expected_success) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (result != expected.second) {
|
if (result != expected_result &&
|
||||||
if (std::isnan(result) && std::isnan(expected.second)) {
|
!(std::isnan(result) && std::isnan(expected_result))) {
|
||||||
continue;
|
std::cout << "results do not match. Expected " << expected_result
|
||||||
}
|
<< std::endl;
|
||||||
std::cout << "results do not match. Expected "<< expected.second << std::endl;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,57 +7,74 @@
|
|||||||
#include <system_error>
|
#include <system_error>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
|
defined(sun) || defined(__sun)
|
||||||
// Anything at all that is related to cygwin, msys and so forth will
|
// Anything at all that is related to cygwin, msys and so forth will
|
||||||
// always use this fallback because we cannot rely on it behaving as normal
|
// always use this fallback because we cannot rely on it behaving as normal
|
||||||
// gcc.
|
// gcc.
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
// workaround for CYGWIN
|
// workaround for CYGWIN
|
||||||
double cygwin_strtod_l(const char* start, char** end) {
|
double cygwin_strtod_l(char const *start, char **end) {
|
||||||
double d;
|
double d;
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss.imbue(std::locale::classic());
|
ss.imbue(std::locale::classic());
|
||||||
ss << start;
|
ss << start;
|
||||||
ss >> d;
|
ss >> d;
|
||||||
if(ss.fail()) { *end = nullptr; }
|
if (ss.fail()) {
|
||||||
if(ss.eof()) { ss.clear(); }
|
*end = nullptr;
|
||||||
auto nread = ss.tellg();
|
}
|
||||||
*end = const_cast<char*>(start) + nread;
|
if (ss.eof()) {
|
||||||
return d;
|
ss.clear();
|
||||||
|
}
|
||||||
|
auto nread = ss.tellg();
|
||||||
|
*end = const_cast<char *>(start) + nread;
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
float cygwin_strtof_l(const char* start, char** end) {
|
|
||||||
float d;
|
float cygwin_strtof_l(char const *start, char **end) {
|
||||||
std::stringstream ss;
|
float d;
|
||||||
ss.imbue(std::locale::classic());
|
std::stringstream ss;
|
||||||
ss << start;
|
ss.imbue(std::locale::classic());
|
||||||
ss >> d;
|
ss << start;
|
||||||
if(ss.fail()) { *end = nullptr; }
|
ss >> d;
|
||||||
if(ss.eof()) { ss.clear(); }
|
if (ss.fail()) {
|
||||||
auto nread = ss.tellg();
|
*end = nullptr;
|
||||||
*end = const_cast<char*>(start) + nread;
|
}
|
||||||
return d;
|
if (ss.eof()) {
|
||||||
|
ss.clear();
|
||||||
|
}
|
||||||
|
auto nread = ss.tellg();
|
||||||
|
*end = const_cast<char *>(start) + nread;
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class RandomEngine {
|
class RandomEngine {
|
||||||
public:
|
public:
|
||||||
RandomEngine() = delete;
|
RandomEngine() = delete;
|
||||||
RandomEngine(uint64_t new_seed) : wyhash64_x_(new_seed) { };
|
RandomEngine(uint64_t new_seed) : wyhash64_x_(new_seed){};
|
||||||
|
|
||||||
uint64_t next() {
|
uint64_t next() {
|
||||||
// Adapted from https://github.com/wangyi-fudan/wyhash/blob/master/wyhash.h
|
// Adapted from https://github.com/wangyi-fudan/wyhash/blob/master/wyhash.h
|
||||||
// Inspired from
|
// Inspired from
|
||||||
// https://github.com/lemire/testingRNG/blob/master/source/wyhash.h
|
// https://github.com/lemire/testingRNG/blob/master/source/wyhash.h
|
||||||
wyhash64_x_ += UINT64_C(0x60bee2bee120fc15);
|
wyhash64_x_ += UINT64_C(0x60bee2bee120fc15);
|
||||||
fast_float::value128 tmp = fast_float::full_multiplication(wyhash64_x_, UINT64_C(0xa3b195354a39b70d));
|
fast_float::value128 tmp = fast_float::full_multiplication(
|
||||||
|
wyhash64_x_, UINT64_C(0xa3b195354a39b70d));
|
||||||
uint64_t m1 = (tmp.high) ^ tmp.low;
|
uint64_t m1 = (tmp.high) ^ tmp.low;
|
||||||
tmp = fast_float::full_multiplication(m1, UINT64_C(0x1b03738712fad5c9));
|
tmp = fast_float::full_multiplication(m1, UINT64_C(0x1b03738712fad5c9));
|
||||||
uint64_t m2 = (tmp.high) ^ tmp.low;
|
uint64_t m2 = (tmp.high) ^ tmp.low;
|
||||||
return m2;
|
return m2;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool next_bool() { return (next() & 1) == 1; }
|
bool next_bool() { return (next() & 1) == 1; }
|
||||||
|
|
||||||
int next_int() { return static_cast<int>(next()); }
|
int next_int() { return static_cast<int>(next()); }
|
||||||
|
|
||||||
char next_char() { return static_cast<char>(next()); }
|
char next_char() { return static_cast<char>(next()); }
|
||||||
|
|
||||||
double next_double() { return static_cast<double>(next()); }
|
double next_double() { return static_cast<double>(next()); }
|
||||||
|
|
||||||
int next_ranged_int(int min, int max) { // min and max are included
|
int next_ranged_int(int min, int max) { // min and max are included
|
||||||
@ -68,7 +85,7 @@ public:
|
|||||||
}*/
|
}*/
|
||||||
uint64_t s = uint64_t(max - min + 1);
|
uint64_t s = uint64_t(max - min + 1);
|
||||||
uint64_t x = next();
|
uint64_t x = next();
|
||||||
fast_float::value128 m = fast_float::full_multiplication(x, s);
|
fast_float::value128 m = fast_float::full_multiplication(x, s);
|
||||||
uint64_t l = m.low;
|
uint64_t l = m.low;
|
||||||
if (l < s) {
|
if (l < s) {
|
||||||
uint64_t t = -s % s;
|
uint64_t t = -s % s;
|
||||||
@ -80,6 +97,7 @@ public:
|
|||||||
}
|
}
|
||||||
return int(m.high) + min;
|
return int(m.high) + min;
|
||||||
}
|
}
|
||||||
|
|
||||||
int next_digit() { return next_ranged_int(0, 9); }
|
int next_digit() { return next_ranged_int(0, 9); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -99,7 +117,8 @@ size_t build_random_string(RandomEngine &rand, char *buffer) {
|
|||||||
}
|
}
|
||||||
buffer[pos] = char(rand.next_digit() + '0');
|
buffer[pos] = char(rand.next_digit() + '0');
|
||||||
// We can have a leading zero only if location_of_decimal_separator = 1.
|
// We can have a leading zero only if location_of_decimal_separator = 1.
|
||||||
while(i == 0 && 1 != size_t(location_of_decimal_separator) && buffer[pos] == '0') {
|
while (i == 0 && 1 != size_t(location_of_decimal_separator) &&
|
||||||
|
buffer[pos] == '0') {
|
||||||
buffer[pos] = char(rand.next_digit() + '0');
|
buffer[pos] = char(rand.next_digit() + '0');
|
||||||
}
|
}
|
||||||
pos++;
|
pos++;
|
||||||
@ -129,8 +148,9 @@ size_t build_random_string(RandomEngine &rand, char *buffer) {
|
|||||||
std::pair<double, bool> strtod_from_string(char *st) {
|
std::pair<double, bool> strtod_from_string(char *st) {
|
||||||
double d;
|
double d;
|
||||||
char *pr;
|
char *pr;
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
d = cygwin_strtod_l(st, &pr);
|
defined(sun) || defined(__sun)
|
||||||
|
d = cygwin_strtod_l(st, &pr);
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
||||||
d = _strtod_l(st, &pr, c_locale);
|
d = _strtod_l(st, &pr, c_locale);
|
||||||
@ -148,8 +168,9 @@ std::pair<double, bool> strtod_from_string(char *st) {
|
|||||||
std::pair<float, bool> strtof_from_string(char *st) {
|
std::pair<float, bool> strtof_from_string(char *st) {
|
||||||
float d;
|
float d;
|
||||||
char *pr;
|
char *pr;
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
d = cygwin_strtof_l(st, &pr);
|
defined(sun) || defined(__sun)
|
||||||
|
d = cygwin_strtof_l(st, &pr);
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
static _locale_t c_locale = _create_locale(LC_ALL, "C");
|
||||||
d = _strtof_l(st, &pr, c_locale);
|
d = _strtof_l(st, &pr, c_locale);
|
||||||
@ -172,14 +193,18 @@ bool tester(uint64_t seed, size_t volume) {
|
|||||||
char buffer[4096]; // large buffer (can't overflow)
|
char buffer[4096]; // large buffer (can't overflow)
|
||||||
RandomEngine rand(seed);
|
RandomEngine rand(seed);
|
||||||
for (size_t i = 0; i < volume; i++) {
|
for (size_t i = 0; i < volume; i++) {
|
||||||
if((i%1000000) == 0) { std::cout << "."; std::cout.flush(); }
|
if ((i % 1000000) == 0) {
|
||||||
|
std::cout << ".";
|
||||||
|
std::cout.flush();
|
||||||
|
}
|
||||||
size_t length = build_random_string(rand, buffer);
|
size_t length = build_random_string(rand, buffer);
|
||||||
std::pair<double, bool> expected_double = strtod_from_string(buffer);
|
std::pair<double, bool> expected_double = strtod_from_string(buffer);
|
||||||
if (expected_double.second) {
|
if (expected_double.second) {
|
||||||
double result_value;
|
double result_value;
|
||||||
auto result =
|
auto result =
|
||||||
fast_float::from_chars(buffer, buffer + length, result_value);
|
fast_float::from_chars(buffer, buffer + length, result_value);
|
||||||
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
if (result.ec != std::errc() &&
|
||||||
|
result.ec != std::errc::result_out_of_range) {
|
||||||
printf("parsing %.*s\n", int(length), buffer);
|
printf("parsing %.*s\n", int(length), buffer);
|
||||||
std::cerr << " I could not parse " << std::endl;
|
std::cerr << " I could not parse " << std::endl;
|
||||||
return false;
|
return false;
|
||||||
@ -202,7 +227,8 @@ bool tester(uint64_t seed, size_t volume) {
|
|||||||
float result_value;
|
float result_value;
|
||||||
auto result =
|
auto result =
|
||||||
fast_float::from_chars(buffer, buffer + length, result_value);
|
fast_float::from_chars(buffer, buffer + length, result_value);
|
||||||
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
|
if (result.ec != std::errc() &&
|
||||||
|
result.ec != std::errc::result_out_of_range) {
|
||||||
printf("parsing %.*s\n", int(length), buffer);
|
printf("parsing %.*s\n", int(length), buffer);
|
||||||
std::cerr << " I could not parse " << std::endl;
|
std::cerr << " I could not parse " << std::endl;
|
||||||
return false;
|
return false;
|
||||||
@ -225,8 +251,12 @@ bool tester(uint64_t seed, size_t volume) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(sun) || defined(__sun)
|
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
|
||||||
std::cout << "Warning: msys/cygwin detected. This particular test is likely to generate false failures due to our reliance on the underlying runtime library." << std::endl;
|
defined(sun) || defined(__sun)
|
||||||
|
std::cout << "Warning: msys/cygwin detected. This particular test is likely "
|
||||||
|
"to generate false failures due to our reliance on the "
|
||||||
|
"underlying runtime library."
|
||||||
|
<< std::endl;
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
#else
|
#else
|
||||||
if (tester(1234344, 100000000)) {
|
if (tester(1234344, 100000000)) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
53
tests/supported_chars_test.cpp
Normal file
53
tests/supported_chars_test.cpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#include "fast_float/fast_float.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
|
template <typename UC> bool test(std::string s, double expected) {
|
||||||
|
std::basic_string<UC> input(s.begin(), s.end());
|
||||||
|
double result;
|
||||||
|
auto answer =
|
||||||
|
fast_float::from_chars(input.data(), input.data() + input.size(), result);
|
||||||
|
if (answer.ec != std::errc()) {
|
||||||
|
std::cerr << "parsing of \"" << s << "\" should succeed\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (result != expected && !(std::isnan(result) && std::isnan(expected))) {
|
||||||
|
std::cerr << "parsing of \"" << s << "\" succeeded, expected " << expected
|
||||||
|
<< " got " << result << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
if (!test<char>("4.2", 4.2)) {
|
||||||
|
std::cout << "test failure for char" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!test<wchar_t>("4.2", 4.2)) {
|
||||||
|
std::cout << "test failure for wchar_t" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cpp_char8_t
|
||||||
|
if (!test<char8_t>("4.2", 4.2)) {
|
||||||
|
std::cout << "test failure for char8_t" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!test<char16_t>("4.2", 4.2)) {
|
||||||
|
std::cout << "test failure for char16_t" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!test<char32_t>("4.2", 4.2)) {
|
||||||
|
std::cout << "test failure for char32_t" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "all ok" << std::endl;
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
57
tests/wide_char_test.cpp
Normal file
57
tests/wide_char_test.cpp
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#include "fast_float/fast_float.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
|
bool tester(std::string s, double expected,
|
||||||
|
fast_float::chars_format fmt = fast_float::chars_format::general) {
|
||||||
|
std::wstring input(s.begin(), s.end());
|
||||||
|
double result;
|
||||||
|
auto answer = fast_float::from_chars(
|
||||||
|
input.data(), input.data() + input.size(), result, fmt);
|
||||||
|
if (answer.ec != std::errc()) {
|
||||||
|
std::cerr << "parsing of \"" << s << "\" should succeed\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (result != expected && !(std::isnan(result) && std::isnan(expected))) {
|
||||||
|
std::cerr << "parsing of \"" << s << "\" succeeded, expected " << expected
|
||||||
|
<< " got " << result << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
input[0] += 256;
|
||||||
|
answer = fast_float::from_chars(input.data(), input.data() + input.size(),
|
||||||
|
result, fmt);
|
||||||
|
if (answer.ec == std::errc()) {
|
||||||
|
std::cerr << "parsing of altered \"" << s << "\" should fail\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_minus() { return tester("-42", -42); }
|
||||||
|
|
||||||
|
bool test_plus() {
|
||||||
|
return tester("+42", 42,
|
||||||
|
fast_float::chars_format::general |
|
||||||
|
fast_float::chars_format::allow_leading_plus);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_space() {
|
||||||
|
return tester(" 42", 42,
|
||||||
|
fast_float::chars_format::general |
|
||||||
|
fast_float::chars_format::skip_white_space);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_nan() {
|
||||||
|
return tester("nan", std::numeric_limits<double>::quiet_NaN());
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
if (test_minus() && test_plus() && test_space() && test_nan()) {
|
||||||
|
std::cout << "all ok" << std::endl;
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "test failure" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user