Compare commits

...

294 Commits
v6.0.0 ... main

Author SHA1 Message Date
Daniel Lemire
5830594b99
Merge pull request #351 from fastfloat/simpler_benchmark
simplify the IP benchmark
2025-12-23 14:11:06 -05:00
Daniel Lemire
75d01f02e2 display the inner count (check) 2025-12-23 12:07:15 -05:00
Daniel Lemire
bfa7bccea1 lint 2025-12-23 11:46:17 -05:00
Daniel Lemire
b5ae54cb21 adding a memcpy benchmark and ensure inlining. 2025-12-22 22:18:39 -05:00
Daniel Lemire
55723db171 add a memcpy baseline 2025-12-22 22:01:50 -05:00
Daniel Lemire
62ed60e95f simplify the benchmark 2025-12-22 21:08:59 -05:00
Daniel Lemire
62fb615be9
Merge pull request #350 from fastfloat/add_bench_ip
adding IP address benchmark
2025-12-22 16:35:24 -05:00
Daniel Lemire
c54e4a7aba updating the deps 2025-12-22 16:05:51 -05:00
Daniel Lemire
5304b3d611 saving... 2025-12-22 14:06:43 -05:00
Daniel Lemire
a6685b2a98 missing header 2025-12-22 12:02:23 -05:00
Daniel Lemire
6952ffeec0 lint 2025-12-22 11:58:36 -05:00
Daniel Lemire
ca1057e303
Merge pull request #345 from fastfloat/better_doc
documenting better which types we support
2025-12-22 11:55:00 -05:00
Daniel Lemire
0fa058eebb adding bench_ip
Co-authored-by: Shikhar <shikharish05@gmail.com>
2025-12-22 11:52:48 -05:00
Daniel Lemire
157deaeba5
Merge pull request #348 from arthurscchan/oss-fuzz-build-add-unit-test
oss-fuzz: Add unit test build
2025-12-18 22:41:49 -05:00
Arthur Chan
b2535ce14c oss-fuzz: Add unit test build
Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>
2025-12-19 03:26:50 +00:00
Daniel Lemire
1cc6cf5a09
Revise reference for Mushtak and Lemire paper
Updated reference to include publication details and link.
2025-12-16 20:18:15 -05:00
Daniel Lemire
6b72e26ba7 documenting better which types we support 2025-12-15 10:28:06 -05:00
Daniel Lemire
f8c573d741
Merge pull request #338 from Gravecat/mingw-fix
Fixes compilation on GCC/MinGW
2025-11-23 16:25:56 -05:00
Raine 'Gravecat' Simmons
9d78a01ff7
Fixed formatting with clang-format 2025-11-22 21:53:37 +00:00
Raine 'Gravecat' Simmons
409d6215b4
Fixes compilation on GCC/MinGW 2025-11-22 16:11:06 +00:00
Daniel Lemire
7b21183a93
Merge pull request #336 from fastfloat/dependabot/github_actions/github-actions-625a8519cf
Bump the github-actions group across 1 directory with 4 updates
2025-10-30 22:04:57 -04:00
dependabot[bot]
8ac72791a9
Bump the github-actions group across 1 directory with 4 updates
Bumps the github-actions group with 4 updates in the / directory: [actions/checkout](https://github.com/actions/checkout), [actions/upload-artifact](https://github.com/actions/upload-artifact), [github/codeql-action](https://github.com/github/codeql-action) and [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/checkout` from 4 to 5
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

Updates `actions/upload-artifact` from 4 to 5
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

Updates `github/codeql-action` from 3 to 4
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

Updates `actions/setup-node` from 4.4.0 to 6.0.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](49933ea528...2028fbc5c2)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-node
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 00:14:47 +00:00
Daniel Lemire
e77e2bca7e
Update README.md 2025-10-10 09:23:01 -04:00
Daniel Lemire
24fa687d45
Merge pull request #334 from toughengineer/max-infinity_corner_cases
Added corner cases around max value/infinity
2025-10-04 09:50:30 -04:00
Pavel Novikov
88f6c5e367
Added corner cases around max value/infinity 2025-10-04 14:39:00 +03:00
Daniel Lemire
83a4f696d7
Merge pull request #333 from toughengineer/made_function_non-template
Made function non-template
2025-09-30 10:25:59 -04:00
Pavel Novikov
1ea4d2563e
made function non-template
+fixed a couple of typos
2025-09-30 12:18:29 +03:00
Daniel Lemire
7262d9454e lint 2025-09-29 15:08:24 -04:00
Daniel Lemire
23f16adad4
Merge pull request #328 from toughengineer/int_multiplication_by_power_of_10
Added template overloads for `integer_times_pow10()`
2025-09-29 15:07:54 -04:00
Daniel Lemire
fd98fd6689
specialize for std::float32_t and std::float64_t explicitly
credit: @lemire
2025-09-29 21:43:36 +03:00
Pavel Novikov
197c0ffca7
clang format 2025-09-29 21:43:35 +03:00
Pavel Novikov
e9438e64ba
fixed copy&paste error and minor mess 2025-09-29 19:54:25 +03:00
Pavel Novikov
7abb574ffc
added doc to README and examples 2025-09-29 13:00:40 +03:00
Anders Dalvander
04e8e545d8
Merge pull request #330 from jbicha/repology-badge
README.md: update repology badge
2025-09-24 22:34:00 +02:00
Jeremy Bícha
e4b949e55b README.md: update repology badge 2025-09-24 16:08:34 -04:00
Pavel Novikov
01e505797b
added tests + some refactoring 2025-09-18 21:29:25 +03:00
Pavel Novikov
13345cab65
added template overload for integer_times_pow10() 2025-09-18 21:29:25 +03:00
Daniel Lemire
88b1e5321c version 8.1.0 2025-09-18 09:38:45 -06:00
Daniel Lemire
2aa6d0ba72
Merge pull request #326 from fastfloat/patch803
Release candidate 8.0.3
2025-09-18 09:37:20 -06:00
Daniel Lemire
0b6d911220 format 2025-09-18 08:30:28 -06:00
Daniel Lemire
2d6574483d
Merge pull request #327 from toughengineer/int_multiplication_by_power_of_10
Minor fix of forward declaration
2025-09-18 08:29:57 -06:00
Pavel Novikov
7a77227521
minor fix of forward declaration 2025-09-18 17:02:29 +03:00
Daniel Lemire
e20c952456
Merge pull request #320 from toughengineer/int_multiplication_by_power_of_10
Implemented multiplication of integer by a power of 10
2025-09-18 07:48:09 -06:00
Daniel Lemire
bb956b29db release candidate 8.0.3 2025-09-18 07:44:53 -06:00
Daniel Lemire
48fc5404d4 compatibility fix 2025-09-18 07:44:05 -06:00
Daniel Lemire
e5612e9f72
Merge pull request #325 from InvalidUsernameException/emoji-parses-as-int
Do not mis-parse certain wide-character emojis as integer
2025-09-18 07:39:59 -06:00
Daniel Lemire
fb384a4205
Add Blender to the list of projects using fast_float 2025-09-17 23:09:25 -06:00
InvalidUsernameException
9d81c71aef Do not mis-parse certain wide-character emojis as integer
When calling ch_to_digit() with a UTF-16 or UTF-32 code unit, it simply
truncates away any data stored in the non-low byte(s) of the code unit.
It then uses a lookup table to determine whether the low byte
corresponds to an ASCII digit. This is incorrect because as soon as any
bit outside the low byte is set, the number will never correspond to a
ASCII digit anymore.

To fix this, we produce a mask that is all zeroes if any bit outside the
low byte is set in the code unit, all ones otherwise. Anding this mask
with the original code unit forces the table lookup to return the
sentinel value from the zero-index if any high bit was set and causes
the code unit not to be parsed as integer.

This bug was discovered when loading Mastodon posts inside the Ladybird
browser where some of Mastodon's JavaScript would trigger the code path
that erroneously parsed the emoji as integer. It had the visible effect
that some digits inside the posts would get rendered as one of the
emojis that parsed to that digit. For more details see this issue:
https://github.com/LadybirdBrowser/ladybird/issues/6205

The emojis in the test case are simply all the emojis used on Mastodon
that caused the bug. They can be found here:
06803422da/app/javascript/mastodon/features/emoji/emoji_map.json
2025-09-15 23:12:28 +02:00
Daniel Lemire
fec4082f01
Merge pull request #322 from leiwen2025/rv-support
float_common.h: Support RISC-V
2025-09-12 15:11:45 -04:00
Daniel Lemire
e89e248bd7
Update risc.yml 2025-09-11 22:14:59 -04:00
Daniel Lemire
96fc38fb5a adding risc ci 2025-09-11 22:11:05 -04:00
WenLei
6677924083 float_common.h: Support RISC-V 2025-09-11 11:11:30 +08:00
Pavel Novikov
0a230326ab
now finally got the anti-ambiguity overloads right, right? 2025-09-06 02:22:43 +03:00
Pavel Novikov
7ae62ee0d5
finally got the anti-ambiguity overloads right? 2025-09-06 02:10:55 +03:00
Daniel Lemire
4bb6fd1271
Merge pull request #314 from fastfloat/P2497R0
implementation of p2497
2025-09-05 18:24:48 -04:00
Pavel Novikov
e12463583f
added lacking overloads to avoid potential ambiguity 2025-09-06 00:12:41 +03:00
Pavel Novikov
6702cd4244
added doc section in the README,
added example code test executable
2025-09-05 13:36:23 +03:00
Pavel Novikov
20a7383442
renamed the function, cleaned up return type 2025-09-05 13:36:23 +03:00
Pavel Novikov
763558b9ac
cleaned up tests 2025-09-05 13:34:48 +03:00
Daniel Lemire
ce511cb9ee
Merge pull request #317 from fastfloat/dependabot/github_actions/github-actions-a331d3ec2d
Bump actions/checkout from 4 to 5 in the github-actions group
2025-09-03 12:07:06 -04:00
Daniel Lemire
42db9ac1de
Merge branch 'main' into P2497R0 2025-09-03 12:04:36 -04:00
Pavel Novikov
6be07d66a8
inlining Clinger's fast path because why not,
and it seems to bring performance to the level before the changes somewhat
2025-09-03 16:59:40 +03:00
dependabot[bot]
42836b48eb
Bump actions/checkout from 4 to 5 in the github-actions group
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 4 to 5
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-02 21:31:49 +00:00
Daniel Lemire
a9ac1b40c9
Merge pull request #316 from fastfloat/emscripten
adding emscripten build test
2025-09-02 17:29:37 -04:00
Daniel Lemire
c0582c27f5 typos in the comments 2025-09-02 17:29:12 -04:00
Pavel Novikov
cc90f240ee
added some tests 2025-09-02 23:03:38 +03:00
Pavel Novikov
a134561e4b
added missing inline specifiers 2025-09-02 23:03:38 +03:00
Pavel Novikov
7b8f04500a
implemented multiplication of integer by power of 10 2025-09-02 13:20:36 +03:00
Daniel Lemire
0a9257e825
Merge pull request #318 from SirLynix/patch-1
README: Add xmake repository reference
2025-08-25 09:27:45 -04:00
Jérôme Leclercq
e1c2c806ed
README: Add xmake repository reference 2025-08-25 13:42:53 +02:00
Daniel Lemire
6b10835aa9 update 2025-06-04 21:14:09 -04:00
Daniel Lemire
3d09138b1b trimming 2025-06-04 20:45:56 -04:00
Daniel Lemire
3ee7eb018d updating CI 2025-06-03 18:19:33 -04:00
Daniel Lemire
2d2b42bb38 forked doctest 2025-06-03 18:15:52 -04:00
Daniel Lemire
73b27b7d68 hmmm 2025-06-02 09:52:34 -04:00
Daniel Lemire
fbc85aa706 adding cmake policy for doctest 2025-06-02 09:25:42 -04:00
Daniel Lemire
5a4b793818 adding emscripten build test 2025-06-02 09:15:06 -04:00
Daniel Lemire
e5d93c993e updating workflow 2025-05-19 18:32:56 -04:00
Daniel Lemire
a1e272f515 lint 2025-05-19 18:16:14 -04:00
Daniel Lemire
447ee0bc82 updating documentation 2025-05-19 18:12:35 -04:00
Daniel Lemire
0458c20061 adding missing file 2025-05-19 18:09:34 -04:00
Daniel Lemire
81b8306c5f implementation of https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2497r0.html 2025-05-19 18:08:36 -04:00
Daniel Lemire
b8085ba363
Update README.md 2025-05-19 11:55:15 -04:00
Daniel Lemire
c5a3ca37c4
Merge pull request #312 from fastfloat/dependabot/github_actions/github-actions-ce77f7460b
Bump jidicula/clang-format-action from 4.14.0 to 4.15.0 in the github-actions group
2025-03-25 09:58:53 -04:00
dependabot[bot]
d42004eca2
Bump jidicula/clang-format-action in the github-actions group
Bumps the github-actions group with 1 update: [jidicula/clang-format-action](https://github.com/jidicula/clang-format-action).


Updates `jidicula/clang-format-action` from 4.14.0 to 4.15.0
- [Release notes](https://github.com/jidicula/clang-format-action/releases)
- [Commits](d05cecd4a1...4726374d1a)

---
updated-dependencies:
- dependency-name: jidicula/clang-format-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 00:53:11 +00:00
Daniel Lemire
3f7ff422bf
Merge pull request #311 from ADKaster/reference-ak
README: Reference SerenityOS/Ladybird Browser implementation
2025-03-21 13:00:42 -04:00
Andrew Kaster
b3fe1e63cf README: Reference SerenityOS/Ladybird Browser implementation 2025-03-21 10:29:13 -06:00
Daniel Lemire
50a80a73ab v8.0.2 2025-03-11 09:51:53 -04:00
Anders Dalvander
f9bed3f7b1
Merge pull request #309 from fastfloat/benchvs
supporting benchmarks under Windows
2025-03-11 14:35:15 +01:00
Daniel Lemire
c0affad8b4
Update benchmark.cpp 2025-03-11 09:19:28 -04:00
Daniel Lemire
7597ca61aa supporting benchmarks under Windows 2025-03-10 19:44:18 -04:00
Daniel Lemire
1bf7010153 v8.0.1 2025-03-10 09:04:05 -04:00
Daniel Lemire
e019768d49
Merge pull request #308 from fastfloat/turing_json_into_macro_parameter
turning json option into macro parameter
2025-03-10 09:02:53 -04:00
Daniel Lemire
c6732cd28b lint 2025-03-10 09:02:38 -04:00
Daniel Lemire
b29208f93d adding FASTFLOAT_IF_CONSTEXPR17 2025-03-09 17:10:55 -04:00
Daniel Lemire
ba92c88901
Update pull_request_template.md 2025-03-09 15:33:11 -04:00
Daniel Lemire
0305a7d336
Update README.md 2025-03-09 15:31:45 -04:00
Daniel Lemire
e720395b61
Update README.md 2025-03-09 15:31:11 -04:00
Daniel Lemire
8e5e92b6e2
Merge pull request #305 from dankmeme01/main
fix /permissive- flag causing a compile error on clang for windows
2025-03-09 15:15:35 -04:00
Daniel Lemire
6f0049a2e7 lint 2025-03-09 15:14:52 -04:00
Daniel Lemire
95dedd0aed turning json option into macro parameter 2025-03-09 15:13:43 -04:00
Daniel Lemire
1504792a9f Merge branch 'main' of github.com:fastfloat/fast_float 2025-03-09 14:27:38 -04:00
Daniel Lemire
864e790d22 allowing custom file parsing 2025-03-09 14:27:07 -04:00
Daniel Lemire
3c5b166f6c
Merge pull request #306 from fastfloat/dependabot/github_actions/github-actions-559242db6c
Bump uraimo/run-on-arch-action from 2 to 3 in the github-actions group
2025-03-03 15:58:21 +00:00
dependabot[bot]
c584342aa5
Bump uraimo/run-on-arch-action from 2 to 3 in the github-actions group
Bumps the github-actions group with 1 update: [uraimo/run-on-arch-action](https://github.com/uraimo/run-on-arch-action).


Updates `uraimo/run-on-arch-action` from 2 to 3
- [Release notes](https://github.com/uraimo/run-on-arch-action/releases)
- [Commits](https://github.com/uraimo/run-on-arch-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: uraimo/run-on-arch-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 00:21:03 +00:00
dankmeme01
b7b17e6cac improve check for /permissive- flag 2025-02-26 14:49:12 +01:00
dankmeme01
810a750306 fix /permissive- flag casuing a compile error on clang for windows 2025-02-26 12:18:16 +01:00
Anders Dalvander
d28a3320c2
Merge pull request #303 from biojppm/fix_wundef
Fix -Wundef warnings seen in gcc 4.8
2025-02-13 13:24:47 +01:00
Joao Paulo Magalhaes
3e9dc96eb4 Fix -Wundef warnings seen in gcc 4.8 and 4.9 2025-02-13 08:45:11 +00:00
Daniel Lemire
96067ad107
Merge pull request #302 from biojppm/fix_wundef
fix warning from -Wundef
2025-02-12 19:41:56 -05:00
Joao Paulo Magalhaes
9cfa5ff4a0 fix warnings from -Wundef 2025-02-12 23:51:44 +00:00
Daniel Lemire
77cc847c84 pushing version change 2025-02-08 11:49:58 -05:00
Daniel Lemire
717112d257 lint 2025-02-06 20:25:09 -05:00
Daniel Lemire
f0c709e3e4 ignoring failures 2025-02-06 20:24:43 -05:00
Daniel Lemire
7a5ee5af60
Merge pull request #296 from dalle/dalle/float16
16-bit float support
2025-02-06 19:41:37 -05:00
Daniel Lemire
1d50f57dd9 better wording. 2025-02-06 00:16:28 -05:00
Daniel Lemire
3a74bfb500
Merge pull request #299 from fastfloat/document_bench
documenting benchmarks
2025-02-05 19:53:33 -05:00
Daniel Lemire
babfa8b85c documenting benchmarks 2025-02-05 19:52:55 -05:00
Daniel Lemire
482cc1f251
Merge pull request #298 from jrade/main
Silenced Visual Studio compiler warning
2025-01-16 15:55:12 -05:00
Johan Råde
d4d5748ec8 Silenced Visual Studio compiler warning
This code caused a C4127 "conditional expression is constant" compiler warning
when compiled with Visual Studio at warning level 4 with T a signed integer type.
2025-01-16 16:12:34 +01:00
Daniel Lemire
be9a8453c4 more iterations 2024-12-13 22:54:04 -05:00
Anders Dalvander
87aae63ed7 readme updates for std::float16_t and std::bfloat16_t 2024-12-04 21:12:25 +01:00
Anders Dalvander
dc39efa49d readme fix for skip_white_space and allow_leading_plus 2024-12-04 21:03:11 +01:00
Anders Dalvander
ce274f7051 include and order types largerst to smallest 2024-12-04 10:17:05 +01:00
Anders Dalvander
7226c00490 cleanup 2024-12-04 09:35:32 +01:00
Anders Dalvander
2a6c2604b5 check for IEEE 754 compliance 2024-12-04 09:34:34 +01:00
Anders Dalvander
f23ced2e4e fix for supplemental 2024-12-04 01:02:20 +01:00
Anders Dalvander
baaf58d2dd fix -Werror=maybe-uninitialized 2024-12-04 00:13:20 +01:00
Anders Dalvander
62f526dfd3 fix -Werror=conversion 2024-12-03 23:52:57 +01:00
Anders Dalvander
63bbefad6b templates and types 2024-12-03 23:47:21 +01:00
Anders Dalvander
ac453a091a overly precise tests for imprecise floats 2024-12-03 23:23:35 +01:00
Anders Dalvander
da819feb74 back to std::bit_cast 2024-12-03 23:23:35 +01:00
Anders Dalvander
3b9ff76143 duplicate tests for both float and double 2024-12-03 23:23:34 +01:00
Anders Dalvander
c62b853648 float.rounds_to_nearest 2024-12-03 23:23:34 +01:00
Anders Dalvander
b3acae22ea fix parse_zero and parse_negative_zero output 2024-12-03 23:23:34 +01:00
Anders Dalvander
74e00e1401 fix double test in float region in basictest 2024-12-03 23:23:34 +01:00
Anders Dalvander
558bec8b9b fix logging in basictest 2024-12-03 23:23:34 +01:00
Anders Dalvander
8cee025082 add powers_of_ten/max_mantissa for float16_t/bfloat16_t 2024-12-03 23:23:34 +01:00
Daniel Lemire
6f8fd6728d make it build 2024-12-03 23:23:34 +01:00
Daniel Lemire
c526899951 cleaning. 2024-12-03 23:23:34 +01:00
Daniel Lemire
bfcff49c83 16-bit float support 2024-12-03 23:23:34 +01:00
Daniel Lemire
31cc0d1a0e
Merge pull request #295 from fastfloat/dependabot/github_actions/github-actions-d1805d1051
Bump jidicula/clang-format-action from 4.13.0 to 4.14.0 in the github-actions group
2024-12-02 13:22:05 -05:00
dependabot[bot]
832307e7b6
Bump jidicula/clang-format-action in the github-actions group
Bumps the github-actions group with 1 update: [jidicula/clang-format-action](https://github.com/jidicula/clang-format-action).


Updates `jidicula/clang-format-action` from 4.13.0 to 4.14.0
- [Release notes](https://github.com/jidicula/clang-format-action/releases)
- [Commits](c74383674b...d05cecd4a1)

---
updated-dependencies:
- dependency-name: jidicula/clang-format-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 01:02:18 +00:00
Anders Dalvander
49aed7782e
Merge pull request #294 from dalle/dalle/equiv_uint_t
* introduce equiv_uint_t helper
* harmonize ifdef checks
* formatted code
2024-12-01 17:01:25 +01:00
Anders Dalvander
3775a81ced formatted code 2024-12-01 16:39:28 +01:00
Anders Dalvander
0a1bf11560 harmonize ifdef checks 2024-12-01 16:36:45 +01:00
Anders Dalvander
3146e686d0 introduce equiv_uint_t 2024-12-01 16:36:17 +01:00
Daniel Lemire
82865ad475
Update README.md 2024-11-26 10:01:42 -05:00
Daniel Lemire
0a17150096
Update README.md 2024-11-25 15:49:40 -05:00
Daniel Lemire
7178b7dddd
Merge pull request #293 from fastfloat/add_benchmarks
adding actual benchmarks to the project
2024-11-25 14:13:18 -05:00
Daniel Lemire
8832c532b8 lint 2024-11-25 11:59:48 -05:00
Daniel Lemire
3f2cd66c1c adding actual benchmarks to the project 2024-11-25 11:59:03 -05:00
Anders Dalvander
ac33f96a60
Merge pull request #292 from dalle/dalle/unicode-tests
Add basic tests for all supported char types (char, wchar_t, char8_t, char16_t, char32_t)
2024-11-25 16:01:22 +01:00
Anders Dalvander
396feb6353 add char8_t test 2024-11-25 15:43:51 +01:00
Anders Dalvander
2c4b48d4e0 add unicode_test 2024-11-25 15:39:04 +01:00
Anders Dalvander
321d3a78b8
Merge pull request #290 from dalle/dalle/fix-scripts
* fixed version updates in release script
* formatted Python scripts with `black` - The uncompromising code formatter (https://black.readthedocs.io/)
* f-string all the things
2024-11-23 11:46:07 +01:00
Anders Dalvander
49068fbfda fix newline in readme 2024-11-23 11:32:39 +01:00
Anders Dalvander
b9793e1cd4 update scripts
* fixed version updates in release script
* formatted Python scripts with `black` - The uncompromising code formatter (https://black.readthedocs.io/)
* f-string all the things
2024-11-23 11:24:39 +01:00
Anders Dalvander
6801b0ca20
Merge pull request #289 from dalle/issue288-char8-support
Support char8_t on C++20
2024-11-23 10:40:32 +01:00
Anders Dalvander
d3f7113d6e add char8_t support 2024-11-23 10:21:48 +01:00
Anders Dalvander
65911af27c
Merge pull request #287 from dalle/fix-clang-warning-unused-function
* Fix clang warning unused function
* `east const` formatting
2024-11-23 09:58:46 +01:00
Anders Dalvander
1a15c66fb9 harmonize files to use "east const"
manually checked modified files for errors, but not committing .clang-format due to the following warning:
>>> Setting `QualifierAlignment` to something other than `Leave`, COULD lead to incorrect code formatting due to incorrect decisions made due to clang-formats lack of complete semantic information. As such extra care should be taken to review code changes made by the use of this option.
2024-11-23 09:46:18 +01:00
Anders Dalvander
7f476cd259 fix clang unused-function warning 2024-11-23 09:04:46 +01:00
Daniel Lemire
54782eb30c
Merge pull request #285 from fastfloat/fix_release_script
the release script will update the header file with the new version numbers (automatically!!!)
2024-11-22 13:38:58 -05:00
Daniel Lemire
f436996266 the release script will update the header file with the new version numbers (automatically!!!) 2024-11-22 13:22:36 -05:00
Daniel Lemire
cf771eaa83
Merge pull request #283 from dalle/issue282-better-error-messages
Better compile time error messages for unsupported types
2024-11-22 13:09:49 -05:00
Daniel Lemire
3d33762eed
Merge pull request #284 from dalle/issue273-version-macros
Add version macros
2024-11-22 12:23:48 -05:00
Anders Dalvander
35d0c8c179 add version macros 2024-11-22 16:44:56 +01:00
Anders Dalvander
74829bb77d better compile time error messages for unsupported types 2024-11-22 16:25:31 +01:00
Daniel Lemire
cb1d42aaa1 version 7 2024-11-21 13:18:48 -05:00
Daniel Lemire
b9661b41af
Merge pull request #280 from dalle/issue275-deprecate-feature-macros
Add allow_leading_plus and skip_white_space in chars_format
2024-11-21 13:02:17 -05:00
Anders Dalvander
4ed0177782 update and reformat README.md 2024-11-21 18:50:42 +01:00
Anders Dalvander
dead62d440 disable test if fast-math is enabled 2024-11-21 00:09:10 +01:00
Anders Dalvander
50ee38af65 fix for fastfloat_strncasecmp for wchar_t and larger char types 2024-11-21 00:09:05 +01:00
Anders Dalvander
7ff885d45c fix for is_space for wchar_t and larger char types 2024-11-21 00:09:00 +01:00
Anders Dalvander
3e26cf4cea add failing test for wide chars 2024-11-21 00:08:55 +01:00
Anders Dalvander
1e188d9715 add missing json test 2024-11-21 00:08:47 +01:00
Anders Dalvander
43a428d658 remove feature macro from tests 2024-11-21 00:08:43 +01:00
Anders Dalvander
cd28b563fc update rcppfastfloat_test 2024-11-21 00:08:33 +01:00
Anders Dalvander
d80be1a5b0 RFC 8259 obsoletes RFC 7159 2024-11-21 00:08:23 +01:00
Anders Dalvander
48252a6483 check feature macros in once place 2024-11-21 00:08:19 +01:00
Anders Dalvander
0bbba960f4 move from feature macros to format flags 2024-11-21 00:06:08 +01:00
Daniel Lemire
724834fe8e
Merge pull request #279 from dalle/issue278-scoped-enum
enum class chars_format
2024-11-20 17:16:55 -05:00
Anders Dalvander
b3526da935 uint64_t as enum base for chars_format 2024-11-20 23:01:56 +01:00
Anders Dalvander
b8b5da75a5 enum class chars_format 2024-11-20 09:33:16 +01:00
Daniel Lemire
42fded19ae
Update vs17-arm-ci.yml 2024-11-19 10:54:10 -05:00
Daniel Lemire
8e5f76ce73
Merge pull request #277 from dalle/issue276-plus-minus-infnan
Issue276 plus/minus handling in parse_infnan
2024-11-19 10:39:49 -05:00
Anders Dalvander
b635dec11a clang format 2024-11-19 10:32:37 +01:00
Anders Dalvander
23787fc71a fix #276: parse_infnan handles FASTFLOAT_ALLOWS_LEADING_PLUS correctly 2024-11-17 16:23:01 +01:00
Anders Dalvander
72b2a7382a const ness 2024-11-17 16:22:05 +01:00
Anders Dalvander
1df71f1e9d add failing test 2024-11-17 16:20:44 +01:00
Anders Dalvander
269867fa43 spelling 2024-11-17 16:17:35 +01:00
Daniel Lemire
e800cabe48
Merge pull request #274 from BYVoid/bazel
Add Bazel build rules.
2024-11-01 17:01:12 -04:00
Carbo Kuo
d65285a48c Add Bazel build rules. 2024-11-01 09:16:24 -04:00
Daniel Lemire
7665574628
Merge pull request #272 from jwakely/trailing-whitespace
Prevent amalgamate.py from adding trailing whitespace
2024-10-24 12:02:33 -04:00
Jonathan Wakely
a60ef3b2e1 Prevent amalgamate.py from adding trailing whitespace
When adding the license text, only add leading whitespace to non-empty
lines, otherwise it becomes trailing whitespace.
2024-10-24 10:47:17 +01:00
Daniel Lemire
9ab35254a8
fixing issue https://github.com/fastfloat/fast_float/issues/271 2024-10-16 14:21:02 -04:00
Daniel Lemire
4266c34620
[no-ci] Update README.md 2024-09-15 10:56:20 -04:00
Daniel Lemire
00c8c7b0d5 version 6.1.6 2024-09-08 13:36:17 -04:00
Daniel Lemire
fe53e769e4
Merge pull request #270 from pranav-sivaraman/add-find-package
doctest: if SYSTEM_DOCTEST add a `find_package(doctest REQUIRED)`
2024-09-08 13:35:34 -04:00
Pranav Sivaraman
a7ed4e89c7 doctest: if SYSTEM_DOCTEST add a find_package(doctest REQUIRED) 2024-09-08 12:34:44 -04:00
Daniel Lemire
564af6d074 version 6.1.5 2024-09-02 15:32:58 -04:00
Daniel Lemire
8c911d9b71
Merge pull request #269 from eukarpov/support-aarch64-w64-mingw32-target
Support aarch64-w64-mingw32 target
2024-09-02 15:32:14 -04:00
Evgeny Karpov
c9f8339668 Fix the formatting and remove the previous condition 2024-09-02 18:13:01 +02:00
Evgeny Karpov
2609d5fd4b The patch resolves GCC compilation issues for the C++ language targeting
aarch64-w64-mingw32.

More information could be found here:
https://gcc.gnu.org/pipermail/gcc-patches/2024-September/662020.html
2024-09-02 17:24:33 +02:00
Daniel Lemire
9117ec4f69 formatting 2024-08-31 18:27:44 -04:00
Daniel Lemire
c8abf94560
Merge pull request #268 from yfeldblum/fix-deprecated-cxx17-cexpr-static-data-defn-out-of-line-redundant
remove out-of-line defns of constexpr static data members under c++17
2024-08-31 18:26:51 -04:00
Yedidya Feldblum
159589d356 remove out-of-line defns of constexpr static data members under c++17
Address warnings of this form under c++17 when building with `-Werror -Wdeprecated`:
```
fast_float/float_common.h:446:58: error: out-of-line definition of constexpr static data member is redundant in C++17 and is deprecated [-Werror,-Wdeprecated]
constexpr double binary_format_lookup_tables<double, U>::powers_of_ten[];
                                                         ^
```
2024-08-30 10:30:40 -05:00
Daniel Lemire
f03b76f0e6
Update README.md 2024-08-27 08:25:45 -04:00
Daniel Lemire
6385dca660
Update README.md 2024-08-26 10:49:31 -04:00
Daniel Lemire
cba388b1dc
Update README.md 2024-08-26 10:47:39 -04:00
Daniel Lemire
bc042d6318
Update README.md 2024-08-26 10:18:35 -04:00
Daniel Lemire
56153ba9d8
Update README.md 2024-08-26 10:15:53 -04:00
Daniel Lemire
4c303f4759
Merge pull request #266 from fastfloat/dependabot/github_actions/github-actions-65ba76e7df
Bump the github-actions group with 3 updates
2024-08-24 13:10:10 -04:00
dependabot[bot]
ce0eb58dac
Bump the github-actions group with 3 updates
Bumps the github-actions group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [actions/upload-artifact](https://github.com/actions/upload-artifact) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `actions/checkout` from 1 to 4
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v1...v4)

Updates `actions/upload-artifact` from 3 to 4
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

Updates `github/codeql-action` from 2 to 3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-24 16:19:19 +00:00
Daniel Lemire
202965c7e7
Merge pull request #265 from cclauss/patch-1
Keep GitHub Actions up to date with GitHub's Dependabot
2024-08-24 12:13:18 -04:00
Christian Clauss
63b75cf1dd
Keep GitHub Actions up to date with GitHub's Dependabot
Fixes software supply chain safety warnings like at the bottom right of
https://github.com/fastfloat/fast_float/actions/runs/10534472547

* [Keeping your actions up to date with Dependabot](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot)
* [Configuration options for the dependabot.yml file - package-ecosystem](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem)
2024-08-24 16:25:23 +02:00
Daniel Lemire
00f25932b0
Merge pull request #263 from fastfloat/doc_issue_261
documentation for issue 261
2024-08-23 19:31:05 -04:00
Daniel Lemire
dab0827f97 documentation for issue 261 2024-08-23 19:29:16 -04:00
Daniel Lemire
3642e92a36
[no-ci] Update README.md 2024-08-14 10:05:20 -04:00
Daniel Lemire
3e57d8dcfb v6.1.4 2024-08-14 09:58:41 -04:00
Daniel Lemire
5ad6aae0b1 lint 2024-08-14 09:57:47 -04:00
Daniel Lemire
8f3dae6b9f
Merge pull request #260 from fastfloat/intrin-for-umul
Include intrin.h for __umulh
2024-08-14 09:54:41 -04:00
Leszek Swirski
74e3c29215 Include intrin.h for __umulh
Arm64 uses __umulh, so add the same condition for including the intrin.h
header.
2024-08-14 15:10:14 +02:00
Daniel Lemire
d57ca3da1f
Merge pull request #259 from fastfloat/issue258
issue 258
2024-08-03 16:38:10 -04:00
Daniel Lemire
0e7a10ad80
Merge pull request #252 from LeszekSwirski/parse-error
Record parse failure reason and location
2024-08-03 10:08:48 -04:00
Daniel Lemire
3838b00751
Merge pull request #257 from fastfloat/lint
add linter
2024-08-03 10:08:14 -04:00
Daniel Lemire
207d66162a issue 258 2024-08-03 10:02:42 -04:00
Leszek Swirski
b6ce2c4de6 Record parse failure reason and location
In parse_number_string, if there is a parse error, report the specific
error as one of the values in a new parse_error enum, and update
lastmatch to match the error location. This allows users of the library
to print more helpful error messages for invalid inputs.
2024-07-23 18:11:10 +02:00
Daniel Lemire
77b8aa0a33 add linter 2024-07-23 10:42:54 -04:00
Daniel Lemire
d7417618f9 patch release 2024-07-23 10:29:14 -04:00
Daniel Lemire
97036e362c
Merge pull request #256 from fastfloat/json-accept-empty-exponent
Allow JSON empty exponent as trailing junk
2024-07-23 10:28:36 -04:00
Leszek Swirski
e6777f2eef Allow JSON empty exponent as trailing junk
Revert #251, and instead allow an invalid trailing empty exponent to be
treated as junk data in JSON parsing. Expand the test suite to test this
case, including testing the trailing junk.
2024-07-23 14:56:42 +02:00
Daniel Lemire
129f0e807e
Merge pull request #255 from fastfloat/version612
preparing patch release
2024-07-22 11:42:50 -04:00
Daniel Lemire
0460ab8bd6 preparing patch release 2024-07-22 09:56:25 -04:00
Daniel Lemire
28e7560c23
Merge pull request #253 from fastfloat/update_ci_to_ubuntu24
update CI to ubuntu 24 + safe a shift value to a variable (for elegance)
2024-07-22 09:41:38 -04:00
Daniel Lemire
d65638bf0f [no-ci] renaming CI 2024-07-22 09:39:57 -04:00
Daniel Lemire
ef9a60d516 update CI to ubuntu 24 + safe a shift value to a variable (for elegance) 2024-07-22 09:36:14 -04:00
Daniel Lemire
9468d50c89
Merge pull request #251 from LeszekSwirski/json-empty-exponent
Disallow empty exponent in JSON parsing
2024-07-22 09:07:08 -04:00
Leszek Swirski
b57207c811 Disallow empty exponent in JSON parsing
The JSON spec requires at least one digit in the exponential part, if
specified.
2024-07-22 12:34:58 +02:00
Daniel Lemire
92c79d078b
Merge pull request #249 from zejal/main
from_chars_advanced overload function taking parsed_number_string_t
2024-07-16 10:17:08 -04:00
Daniel Lemire
c8b3ca0d12
Merge pull request #167 from deadalnix/smallest_power_of_ten
Change smallest_power_of_ten to -64 for floats.
2024-06-27 19:03:59 -04:00
zejal
85911abb59 added couple of comments 2024-05-13 19:55:46 +02:00
zejal
1f9b1a6706 from_chars_advanced overload function taking parsed_number_string_t 2024-05-13 14:24:48 +02:00
Daniel Lemire
1fc3ac3932
Merge pull request #245 from fastfloat/removing_dead_function
removing unused function
2024-03-20 16:27:44 -04:00
Daniel Lemire
56a0092895 removing unused function 2024-03-20 14:24:16 -04:00
Daniel Lemire
7c19cafa93
Update README.md 2024-03-19 17:07:43 -04:00
Daniel Lemire
f476bc713f patch 2024-03-17 09:41:36 -04:00
Daniel Lemire
3b54a78805
Merge pull request #243 from matthew-wozniczka/patch-1
Add a missing check that __uint128_t exists before using it.
2024-03-17 09:40:39 -04:00
Matthew Wozniczka
9ab4ac837b
Add a missing check that __uint128_t exists before using it.
I noticed a compilation error when building a 64-bit binary with this library while using xlclang on AIX, and this change seems to fix it.
2024-03-16 12:18:20 -07:00
Daniel Lemire
bafd9d9c5f adding release script. 2024-03-08 18:15:20 -05:00
Daniel Lemire
9befad4626 adding release script 2024-03-08 18:10:15 -05:00
Daniel Lemire
2f3ed44e06
Merge pull request #241 from carlosal1015/fix/version-numbering
Use version 6.1.0 instead 6.0.0
2024-03-08 11:28:25 -05:00
Carlos Aznarán
131b1f9768
Use version 6.1.0 instead 6.0.0 2024-03-08 08:46:50 -05:00
Daniel Lemire
ff7cb44263
Create SECURITY.md 2024-02-09 17:18:36 -05:00
Daniel Lemire
4b035185e7
Merge pull request #239 from wojdyr/main
check C++ standard version before including <stdfloat>
2024-02-03 11:30:06 -05:00
Marcin Wojdyr
55a5b3c8e1 check C++ standard version before including <stdfloat>
fixes #238
2024-02-03 15:05:30 +01:00
Daniel Lemire
8378916ed8
Merge pull request #237 from fastfloat/release610_candidate
preparing version 6.1.0
2024-01-28 16:17:51 -05:00
Daniel Lemire
797e3e0bc2 more fixes 2024-01-28 16:01:46 -05:00
Daniel Lemire
12ece3c84f removing leftover print 2024-01-28 15:20:41 -05:00
Daniel Lemire
a0ea962bf5 more fixes 2024-01-28 15:18:40 -05:00
Daniel Lemire
4dcbd30d3f More robust tests for C++23 2024-01-28 15:00:27 -05:00
Daniel Lemire
eb584f748e explicit cast 2024-01-28 13:29:55 -05:00
Daniel Lemire
9da74ca5fb minor fixes 2024-01-28 12:49:00 -05:00
Daniel Lemire
3ab68b69d1 preparing version 6.1.0 2024-01-28 11:51:28 -05:00
Daniel Lemire
fade2357ba fix missing typename 2024-01-28 11:46:27 -05:00
Daniel Lemire
c7e45fea9f
Merge pull request #229 from MTahaK/main
Support for float32_t and float64_t
2024-01-28 11:43:54 -05:00
Daniel Lemire
a642af5235
Merge pull request #236 from fastfloat/issue235
Verify and fix issue 235
2024-01-28 11:43:35 -05:00
Daniel Lemire
7977ec6054 fix 2024-01-28 11:11:19 -05:00
Daniel Lemire
9be7de5998 adding sanitized tests 2024-01-28 10:56:20 -05:00
Daniel Lemire
5334e2ba94 fix 2024-01-28 10:55:02 -05:00
Daniel Lemire
1d97749791 Verify and fix issue 235 2024-01-28 10:47:21 -05:00
Daniel Lemire
620376ad39
Merge branch 'main' into main 2024-01-28 10:09:56 -05:00
Daniel Lemire
f320619216
Merge pull request #227 from fastfloat/extra_test
adding a single test
2024-01-28 10:08:49 -05:00
Daniel Lemire
7cbbcee6ed
Merge pull request #233 from StefanBruens/optional_supplemental_tests
Make tests depending on supplemental_test_files optional
2024-01-28 10:08:33 -05:00
Daniel Lemire
68b9475585
Merge pull request #234 from beached/patch-1
Update float_common.h
2023-12-28 08:16:38 -05:00
Darrell Wright
b43f808190
Update float_common.h
The construct !! is a no-op for a bool, op< for uint64_t's.  Removed it and made it an explicit cast to match the operations being performed
2023-12-27 19:43:56 -05:00
Daniel Lemire
b90ba259ef
Update README.md 2023-12-26 16:10:05 -05:00
Daniel Lemire
5c2a4a026a
Update README.md 2023-12-26 16:09:07 -05:00
StefanBruens
7f46adc19c Make tests depending on supplemental_test_files optional
As the supplemental_test_files are quite large, it is useful to make
running the tests depending on it optional.

By default, the tests are kept enabled, but can be switched of by setting
`FASTFLOAT_SUPPLEMENTAL_TEST=OFF`.

Fixes: #232
2023-12-26 02:23:30 +01:00
MTahaK
9c8891eed1 Update contributors 2023-12-13 00:41:28 -05:00
MTahaK
96c0059fe9 Revised implementation of float32_t and float64_t support as template specializations, added test 2023-12-13 00:33:16 -05:00
MTahaK
ae99db48e6 Updated CMakeLists with new option for C++23 fixed width tests, added new basic fixedwidthtest file. 2023-12-12 19:19:06 -05:00
MTahaK
4d8a2f4000 Fixed typo. 2023-12-12 19:02:10 -05:00
MTahaK
f330ec1e9d Reverted CMakeLists changes, modifed workflow for g++13 & C++23. 2023-12-12 18:59:59 -05:00
MTahaK
d10980bc9f Added conditions to include stdfloat, and new workflow for GCC 13 2023-12-12 18:46:38 -05:00
MTahaK
62a4129a1c Fixed ifdef macro 2023-12-12 18:18:13 -05:00
MTahaK
eb844a1ad9 Modified cmakelist to use C++23. Fixed ifdef for fixed-width float types 2023-12-12 18:16:27 -05:00
MTahaK
b0f4535781 Update test/CMakeLists.txt to use C++23 for fixed-width float types 2023-12-12 18:11:09 -05:00
MTahaK
cbbbc75c88 Fixed ifdef checks (incorrect syntax) 2023-12-12 17:59:04 -05:00
MTahaK
d8dbbc54ab Updated comment for float32_t and float64_t conversions in from_chars 2023-12-12 17:56:14 -05:00
MTahaK
f74e338e0a Added trivial support for float32_t and float64_t 2023-12-12 16:38:48 -05:00
Daniel Lemire
604424b624 adding a test 2023-12-06 21:17:09 -05:00
deadalnix
5167a5029d Change smallest_power_of_ten to -64 for floats.
`18446744073709551615e-65` rounds to `0`, and it is the largest possible value with exponent `-65`. Therefore, we can assume the minimal exponent for which we don't round to zero actually is `-64`.
2023-07-12 14:27:46 +00:00
96 changed files with 9075 additions and 3591 deletions

1
.bazelrc Normal file
View File

@ -0,0 +1 @@
build --cxxopt="--std=c++17"

4
.clang-format Normal file
View File

@ -0,0 +1,4 @@
BasedOnStyle: LLVM
SortIncludes: false
SeparateDefinitionBlocks: Always
MaxEmptyLinesToKeep: 1

13
.github/dependabot.yml vendored Normal file
View 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
View 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.

View File

@ -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

View File

@ -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 &&

View File

@ -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
View 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

View 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'

View File

@ -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

View File

@ -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

View File

@ -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
View 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)

View File

@ -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:

View File

@ -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

View File

@ -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 .

View File

@ -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

View File

@ -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
View 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

View File

@ -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 &&

View File

@ -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
View 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 )

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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
View File

@ -0,0 +1,6 @@
cc_library(
name = "fast_float",
hdrs = glob(["include/fast_float/*.h"]),
strip_include_prefix = "include",
visibility = ["//visibility:public"],
)

View File

@ -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)

View File

@ -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
View 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)

471
README.md
View File

@ -1,44 +1,54 @@
## fast_float number parsing library: 4x faster than strtod ## fast_float number parsing library: 4x faster than strtod
[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fast_float.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:fast_float)
[![Ubuntu 22.04 CI (GCC 11)](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml/badge.svg)](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml) [![Ubuntu 22.04 CI (GCC 11)](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml/badge.svg)](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml)
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.
@ -47,9 +57,10 @@ 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; }
@ -58,9 +69,29 @@ int main() {
} }
``` ```
Though the C++17 standard has you do a comparison with `std::errc()` to check whether the conversion worked, you can avoid it by casting the result to a `bool` like so:
```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;
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()) {
@ -85,69 +116,131 @@ You can parse delimited numbers:
// 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.
@ -165,16 +258,28 @@ 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; }
@ -185,20 +290,19 @@ int main() {
## 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);
@ -208,14 +312,14 @@ int main() {
} }
``` ```
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);
@ -225,15 +329,14 @@ int main() {
} }
``` ```
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);
@ -245,20 +348,19 @@ int main() {
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
------
[![Packaging status](https://repology.org/badge/vertical-allrepos/fast-float.svg)](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
[![Go Systems 2020](http://img.youtube.com/vi/AVXgvlMeIm4/0.jpg)](http://www.youtube.com/watch?v=AVXgvlMeIm4)<br /> [![Go Systems 2020](https://img.youtube.com/vi/AVXgvlMeIm4/0.jpg)](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
[![Star History Chart](https://api.star-history.com/svg?repos=fastfloat/fast_float&type=Date)](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
View 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
View 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
View 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
View 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
View File

View File

@ -0,0 +1,4 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR riscv64)
set(CMAKE_CROSSCOMPILING_EMULATOR "qemu-riscv64-static")

View File

@ -6,3 +6,7 @@ $CXX $CFLAGS $CXXFLAGS \
$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

View File

@ -19,16 +19,20 @@ fast_float::chars_format arbitrary_format(FuzzedDataProvider &fdp) {
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;
} }

View File

@ -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,20 +36,16 @@ 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) {
@ -70,10 +65,9 @@ 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
const __m128i packed = _mm_packus_epi16(data, data); __m128i const 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
@ -85,28 +79,26 @@ FASTFLOAT_SIMD_DISABLE_WARNINGS
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(_mm_loadu_si128(reinterpret_cast<const __m128i*>(chars))); return simd_read8_to_u64(
_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
uint64_t simd_read8_to_u64(const uint16x8_t data) {
FASTFLOAT_SIMD_DISABLE_WARNINGS 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(vld1q_u16(reinterpret_cast<const uint16_t*>(chars))); return simd_read8_to_u64(
vld1q_u16(reinterpret_cast<uint16_t const *>(chars)));
FASTFLOAT_SIMD_RESTORE_WARNINGS FASTFLOAT_SIMD_RESTORE_WARNINGS
} }
@ -123,97 +115,80 @@ 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
} }
@ -231,29 +206,52 @@ 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};
@ -261,41 +259,58 @@ struct parsed_number_string_t {
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 else {
return answer; 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);
} }
} }
} }
@ -307,21 +322,26 @@ 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;
@ -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! else if (digit_count == 0) { // we must have encountered at least one integer!
return answer; return report_parse_error<UC>(p, parse_error::no_digits_in_mantissa);
} }
int64_t exp_number = 0; // explicit exponential part int64_t exp_number = 0; // explicit exponential part
if ( ((fmt & chars_format::scientific) && if ((uint64_t(fmt & chars_format::scientific) && (p != pend) &&
(p != pend) && ((UC('e') == *p) || (UC('E') == *p))) ||
((UC('e') == *p) || (UC('E') == *p))) (uint64_t(fmt & detail::basic_fortran_fmt) && (p != pend) &&
|| ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) ||
((fmt & FASTFLOAT_FORTRANFMT) && (UC('D') == *p)))) {
(p != pend) &&
((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || (UC('D') == *p)))) {
UC const *location_of_e = p; UC const *location_of_e = p;
if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || (UC('D') == *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;
@ -403,27 +432,28 @@ parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, par
// 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)) {
@ -441,32 +471,41 @@ 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) {
chars_format const fmt = detail::adjust_for_feature_macros(options.format);
int const base = options.base;
from_chars_result_t<UC> answer; from_chars_result_t<UC> answer;
UC const *const first = p; UC const *const first = p;
bool negative = (*p == UC('-')); 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')) {
while (p != pend && *p == UC('0')) {
++p; ++p;
} }
const bool has_leading_zeros = p > start_num;
bool const has_leading_zeros = p > start_num;
UC const *const start_digits = p; UC const *const start_digits = p;
@ -490,8 +529,7 @@ 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;
} }
@ -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;
@ -526,15 +565,19 @@ from_chars_result_t<UC> parse_int_string(UC const* p, UC const* pend, T& value,
#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;

View File

@ -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;
@ -58,10 +57,12 @@ struct stackvec {
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);
@ -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.
@ -126,6 +129,7 @@ struct stackvec {
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,8 +211,8 @@ 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)
@ -225,8 +231,8 @@ 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.
@ -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,8 +299,8 @@ 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) {
@ -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,16 +374,37 @@ 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[] = {
@ -392,14 +417,15 @@ struct pow5_tables {
#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
@ -410,8 +436,9 @@ struct bigint : pow5_tables<> {
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;
@ -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()) {
@ -513,7 +541,7 @@ struct bigint : pow5_tables<> {
} 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;
@ -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;

View File

@ -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

View File

@ -12,25 +12,32 @@
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]");
constexpr uint64_t precision_mask =
(bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision)
: uint64_t(0xFFFFFFFFFFFFFFFF); : uint64_t(0xFFFFFFFFFFFFFFFF);
if((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower) if ((firstproduct.high & precision_mask) ==
// regarding the second product, we only need secondproduct.high, but our expectation is that the compiler will optimize this extra work away if needed. precision_mask) { // could further guard with (lower + w < lower)
value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]); // 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++;
@ -63,35 +70,37 @@ 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,19 +169,25 @@ 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()) &&
(q <= binary::max_exponent_round_to_even()) &&
((answer.mantissa & 3) == 1)) { // we may fall between two floats! ((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
// back!!!
if ((answer.mantissa << shift) == product.high) {
answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up
} }
} }

View File

@ -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.
@ -256,7 +275,8 @@ void parse_mantissa(bigint& result, parsed_number_string_t<UC>& num, size_t max_
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,18 +338,22 @@ 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);
}); });
}); });
@ -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,11 +395,12 @@ 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(
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
(void)__; // not needed, since we've done our comparison (void)__; // not needed, since we've done our comparison
if (ord > 0) { if (ord > 0) {
@ -388,7 +416,7 @@ adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int
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;

View File

@ -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 from_chars_result_t<UC>
from_chars_advanced(UC const *first, UC const *last, T &value,
parse_options_t<UC> options) noexcept;
/**
* 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
* 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_advanced(UC const * first, UC const * last, typename std::enable_if<is_supported_float_type<T>::value, T>::type
T &value, parse_options_t<UC> options) noexcept; 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. * from_chars for integer types.
*/ */
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_integer_type<T>::value)>
from_chars_result_t<UC> from_chars(UC const * first, UC const * last, T& value, int base = 10) noexcept; 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

View File

@ -29,12 +29,14 @@ namespace fast_float {
* infinite in binary64 so we never need to worry about powers * infinite in binary64 so we never need to worry about powers
* of 5 greater than 308. * of 5 greater than 308.
*/ */
template <class unused = void> template <class unused = void> struct powers_template {
struct powers_template {
constexpr static int smallest_power_of_five = binary_format<double>::smallest_power_of_ten(); constexpr static int smallest_power_of_five =
constexpr static int largest_power_of_five = binary_format<double>::largest_power_of_ten(); binary_format<double>::smallest_power_of_ten();
constexpr static int number_of_entries = 2 * (largest_power_of_five - smallest_power_of_five + 1); constexpr static int largest_power_of_five =
binary_format<double>::largest_power_of_ten();
constexpr static int number_of_entries =
2 * (largest_power_of_five - smallest_power_of_five + 1);
// Powers of five from 5^-342 all the way to 5^308 rounded toward one. // Powers of five from 5^-342 all the way to 5^308 rounded toward one.
constexpr static uint64_t power_of_five_128[number_of_entries] = { constexpr static uint64_t power_of_five_128[number_of_entries] = {
0xeef453d6923bd65a, 0x113faa2906a13b3f, 0xeef453d6923bd65a, 0x113faa2906a13b3f,
@ -687,11 +689,17 @@ constexpr static uint64_t power_of_five_128[number_of_entries] = {
0x91d28b7416cdd27e, 0x4cdc331d57fa5441, 0x91d28b7416cdd27e, 0x4cdc331d57fa5441,
0xb6472e511c81471d, 0xe0133fe4adf8e952, 0xb6472e511c81471d, 0xe0133fe4adf8e952,
0xe3d8f9e563a198e5, 0x58180fddd97723a6, 0xe3d8f9e563a198e5, 0x58180fddd97723a6,
0x8e679c2f5e44ff8f,0x570f09eaa7ea7648,}; 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648,
};
}; };
#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE
template <class unused> template <class unused>
constexpr uint64_t powers_template<unused>::power_of_five_128[number_of_entries]; constexpr uint64_t
powers_template<unused>::power_of_five_128[number_of_entries];
#endif
using powers = powers_template<>; using powers = powers_template<>;

File diff suppressed because it is too large Load Diff

View File

@ -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
if ((*first == UC('-')) ||
(uint64_t(fmt & chars_format::allow_leading_plus) &&
(*first == UC('+')))) {
++first; ++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();
// 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).
if (first != last && *first == UC('(')) { if (first != last && *first == UC('(')) {
for (UC const *ptr = first + 1; ptr != last; ++ptr) { 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,13 +90,13 @@ 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:
@ -113,7 +116,8 @@ fastfloat_really_inline bool rounds_to_nearest() noexcept {
#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
// https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013
#elif defined(__clang__) #elif defined(__clang__)
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wfloat-equal" #pragma clang diagnostic ignored "-Wfloat-equal"
@ -133,51 +137,68 @@ fastfloat_really_inline bool rounds_to_nearest() noexcept {
} // 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);
} }
};
template<typename T, typename UC> #ifdef __STDCPP_FLOAT32_T__
FASTFLOAT_CONSTEXPR20 template <> struct from_chars_caller<std::float32_t> {
from_chars_result_t<UC> from_chars_advanced(UC const * first, UC const * last, template <typename UC>
T &value, parse_options_t<UC> options) noexcept { FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
call(UC const *first, UC const *last, std::float32_t &value,
static_assert (is_supported_float_type<T>(), "only float and double are supported"); parse_options_t<UC> options) noexcept {
static_assert (is_supported_char_type<UC>(), "only char, wchar_t, char16_t and char32_t are supported"); // 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
from_chars_result_t<UC> answer; // float32_t
#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default float val;
while ((first != last) && fast_float::is_space(uint8_t(*first))) { auto ret = from_chars_advanced(first, last, val, options);
first++; value = val;
return ret;
} }
};
#endif #endif
if (first == last) {
answer.ec = std::errc::invalid_argument; #ifdef __STDCPP_FLOAT64_T__
answer.ptr = first; template <> struct from_chars_caller<std::float64_t> {
return answer; template <typename UC>
} FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC>
parsed_number_string_t<UC> pns = parse_number_string<UC>(first, last, options); call(UC const *first, UC const *last, std::float64_t &value,
if (!pns.valid) { parse_options_t<UC> options) noexcept {
if (options.format & chars_format::no_infnan) { // if std::float64_t is defined, and we are in C++23 mode; macro set for
answer.ec = std::errc::invalid_argument; // float64; set value as double due to equivalence between double and
answer.ptr = first; // float64_t
return answer; double val;
} else { auto ret = from_chars_advanced(first, last, val, options);
return detail::parse_infnan(first, last, value); 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));
} }
answer.ec = std::errc(); // be optimistic template <typename T>
answer.ptr = pns.lastmatch; fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool
clinger_fast_path_impl(uint64_t mantissa, int64_t exponent, bool is_negative,
T &value) noexcept {
// 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.
// //
@ -188,66 +209,277 @@ from_chars_result_t<UC> from_chars_advanced(UC const * first, UC const * 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) {
value = -value;
}
return true;
}
}
}
return false;
}
/**
* 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; return answer;
}
} adjusted_mantissa am =
} compute_float<binary_format<T>>(pns.exponent, pns.mantissa);
adjusted_mantissa am = compute_float<binary_format<T>>(pns.exponent, pns.mantissa);
if (pns.too_many_digits && am.power2 >= 0) { if (pns.too_many_digits && am.power2 >= 0) {
if (am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) { 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++;
} }
#endif }
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++;
}
}
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

View File

@ -2,96 +2,121 @@
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()
if len(line):
line = " " + line
text += "//" + line + "\n"
processed_files[filename] = text 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",
"ascii_number.h",
"fast_table.h",
"decimal_to_binary.h",
"bigint.h",
"digit_comparison.h",
"parse_number.h",
]:
with open("include/fast_float/" + filename, encoding="utf8") as f:
text = ""
for line in f: for line in f:
if line.startswith('#include "'): continue if line.startswith('#include "'):
continue
text += line text += line
processed_files[filename] = '\n' + text 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': if license_arg == "DUAL":
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 at your option. This file may not be copied,\n', "// MIT License at your option. 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 in ('DUAL', 'TRIPLE', 'MIT'): if license_arg in ("DUAL", "TRIPLE", "MIT"):
result.append('// MIT License Notice\n//\n') result.append("// MIT License Notice\n//\n")
result.append(processed_files['LICENSE-MIT']) result.append(processed_files["LICENSE-MIT"])
result.append('//\n') result.append("//\n")
if license_arg in ('DUAL', 'TRIPLE', 'APACHE'): if license_arg in ("DUAL", "TRIPLE", "APACHE"):
result.append('// Apache License (Version 2.0) Notice\n//\n') result.append("// Apache License (Version 2.0) Notice\n//\n")
result.append(processed_files['LICENSE-APACHE']) result.append(processed_files["LICENSE-APACHE"])
result.append('//\n') result.append("//\n")
if license_arg in ('TRIPLE', 'BOOST'): if license_arg in ("TRIPLE", "BOOST"):
result.append('// BOOST License Notice\n//\n') result.append("// BOOST License Notice\n//\n")
result.append(processed_files['LICENSE-BOOST']) result.append(processed_files["LICENSE-BOOST"])
result.append('//\n') result.append("//\n")
return result return result
text = ''.join([
processed_files['AUTHORS'], processed_files['CONTRIBUTORS'], text = "".join(
[
processed_files["AUTHORS"],
processed_files["CONTRIBUTORS"],
*license_content(args.license), *license_content(args.license),
processed_files['constexpr_feature_detect.h'], processed_files["constexpr_feature_detect.h"],
processed_files['float_common.h'], processed_files['fast_float.h'], processed_files["float_common.h"],
processed_files['ascii_number.h'], processed_files['fast_table.h'], processed_files["fast_float.h"],
processed_files['decimal_to_binary.h'], processed_files['bigint.h'], processed_files["ascii_number.h"],
processed_files['digit_comparison.h'], processed_files['parse_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)

View File

@ -1,9 +1,11 @@
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
@ -16,7 +18,7 @@ for q in range(1,17+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)
@ -28,7 +30,7 @@ for q in range(18, 344+1):
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)

View File

@ -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 = []
@ -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,7 +59,12 @@ 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

183
script/release.py Executable file
View 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
View 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

View File

@ -3,12 +3,13 @@ def format(number):
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
View 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",
],
)

View File

@ -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() endif()
if (FASTFLOAT_SUPPLEMENTAL_TESTS)
FetchContent_Declare(supplemental_test_files FetchContent_Declare(supplemental_test_files
GIT_REPOSITORY https://github.com/fastfloat/supplemental_test_files.git GIT_REPOSITORY https://github.com/fastfloat/supplemental_test_files.git
GIT_TAG origin/main) GIT_TAG origin/main)
endif()
# 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)
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") 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)

File diff suppressed because it is too large Load Diff

View File

@ -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 = fast_float::from_chars(input, input + strlen(input), result_value); auto result =
if (result.ec != std::errc()) { return 1; } fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) {
return 1;
}
return result_value; return result_value;
} }

View File

@ -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 = 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);
if (result.ec != std::errc()) {
return 10;
}
return result_value; return result_value;
} }

View File

@ -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 = fast_float::from_chars(input, input + strlen(input), result_value); auto result =
if (result.ec != std::errc()) { return 2; } fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) {
return 2;
}
return result_value; return result_value;
} }

View File

@ -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 = fast_float::from_chars(input, input + strlen(input), result_value); auto result =
if (result.ec != std::errc()) { return 3; } fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) {
return 3;
}
return result_value; return result_value;
} }

View File

@ -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 = fast_float::from_chars(input, input + strlen(input), result_value); auto result =
if (result.ec != std::errc()) { return 4; } fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) {
return 4;
}
return result_value; return result_value;
} }

View File

@ -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 = fast_float::from_chars(input, input + strlen(input), result_value); auto result =
if (result.ec != std::errc()) { return 5; } fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) {
return 5;
}
return result_value; return result_value;
} }

View File

@ -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 = fast_float::from_chars(input, input + strlen(input), result_value); auto result =
if (result.ec != std::errc()) { return 6; } fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) {
return 6;
}
return result_value; return result_value;
} }

View File

@ -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 = fast_float::from_chars(input, input + strlen(input), result_value); auto result =
if (result.ec != std::errc()) { return 7; } fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) {
return 7;
}
return result_value; return result_value;
} }

View File

@ -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 = fast_float::from_chars(input, input + strlen(input), result_value); auto result =
if (result.ec != std::errc()) { return 8; } fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) {
return 8;
}
return result_value; return result_value;
} }

View File

@ -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 = fast_float::from_chars(input, input + strlen(input), result_value); auto result =
if (result.ec != std::errc()) { return 9; } fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) {
return 9;
}
return result_value; return result_value;
} }

View File

@ -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);
} }

View File

@ -1,9 +1,12 @@
#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);
if (result.ec != std::errc()) {
return 10;
}
return result_value; return result_value;
} }

View File

@ -1,2 +1,3 @@
#include "test.h" #include "test.h"
void foo() {} void foo() {}

View File

@ -1,2 +1,3 @@
#include "test.h" #include "test.h"
int main() { return 0; } int main() { return 0; }

View File

@ -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);
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;
} }

View 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();
}

View File

@ -4,30 +4,47 @@
#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);
@ -47,24 +64,85 @@ 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);
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;
#ifdef __STDCPP_FLOAT16_T__
printf("16-bit float\n");
// Parse as 16-bit float
std::float16_t parsed_16{};
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
if (!small()) {
printf("Bug\n");
return EXIT_FAILURE;
}
if (!large()) {
printf("Bug\n");
return EXIT_FAILURE;
}
if (!many()) { if (!many()) {
printf("Bug\n"); printf("Bug\n");

View File

@ -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();
} }
@ -43,8 +45,8 @@ void allvalues() {
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;

View File

@ -16,7 +16,6 @@ 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(),
@ -32,13 +31,13 @@ bool basic_test_64bit(std::string vals, double val) {
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,7 +58,7 @@ 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;

View File

@ -14,27 +14,37 @@
// 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;
}
if (ss.eof()) {
ss.clear();
}
auto nread = ss.tellg(); auto nread = ss.tellg();
*end = const_cast<char *>(start) + nread; *end = const_cast<char *>(start) + nread;
return d; return d;
} }
float cygwin_strtof_l(const char* start, char** end) {
float cygwin_strtof_l(char const *start, char **end) {
float d; float 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;
}
if (ss.eof()) {
ss.clear();
}
auto nread = ss.tellg(); auto nread = ss.tellg();
*end = const_cast<char *>(start) + nread; *end = const_cast<char *>(start) + nread;
return d; return d;
@ -47,9 +57,10 @@ 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__) || \
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");
@ -75,8 +86,12 @@ bool allvalues() {
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,7 +120,8 @@ 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)) {
@ -111,18 +129,21 @@ 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::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;

File diff suppressed because it is too large Load Diff

70
tests/fixedwidthtest.cpp Normal file
View 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;
}

View File

@ -4,38 +4,42 @@
#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};
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" << result << "\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;
} }
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;
@ -45,8 +49,8 @@ int main ()
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;
@ -56,14 +60,16 @@ int main ()
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;
} }

View File

@ -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,7 +17,7 @@ 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; }

View File

@ -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
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; 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
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; 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 =
"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{
auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); fast_float::chars_format::json_or_infnan |
if(answer.ec != std::errc() || (!std::isinf(result))) { std::cerr << "should have parsed infinity\n"; return EXIT_FAILURE; } 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; 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;
} }

View File

@ -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();
} }
@ -43,11 +44,12 @@ void allvalues() {
} }
} 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;

View File

@ -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();
} }
@ -41,8 +42,8 @@ void all_32bit_values() {
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)) {

View File

@ -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) {
@ -77,8 +79,8 @@ void random_values(size_t N) {
} }
} 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;

View File

@ -8,21 +8,27 @@
#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)};
char const *begin = input.data();
char const *end = input.data() + input.size();
for (size_t i = 0; i < answers.size(); i++) { 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;
@ -34,7 +40,6 @@ bool test() {
std::cerr << " Got " << result_value << std::endl; std::cerr << " Got " << result_value << std::endl;
return false; return false;
} }
begin = result.ptr; begin = result.ptr;
} }

16
tests/p2497.cpp Normal file
View 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;
}

View File

@ -9,44 +9,54 @@
#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;
}
if (ss.eof()) {
ss.clear();
}
auto nread = ss.tellg(); auto nread = ss.tellg();
*end = const_cast<char *>(start) + nread; *end = const_cast<char *>(start) + nread;
return d; return d;
} }
float cygwin_strtof_l(const char* start, char** end) {
float cygwin_strtof_l(char const *start, char **end) {
float d; float 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;
}
if (ss.eof()) {
ss.clear();
}
auto nread = ss.tellg(); auto nread = ss.tellg();
*end = const_cast<char *>(start) + nread; *end = const_cast<char *>(start) + nread;
return d; 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__) || \
defined(sun) || defined(__sun)
d = cygwin_strtod_l(st, &pr); 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");
@ -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;

View File

@ -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) {
@ -80,8 +82,8 @@ void random_values(size_t N) {
} }
} 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;

View File

@ -7,33 +7,44 @@
#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;
}
if (ss.eof()) {
ss.clear();
}
auto nread = ss.tellg(); auto nread = ss.tellg();
*end = const_cast<char *>(start) + nread; *end = const_cast<char *>(start) + nread;
return d; return d;
} }
float cygwin_strtof_l(const char* start, char** end) {
float cygwin_strtof_l(char const *start, char **end) {
float d; float 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;
}
if (ss.eof()) {
ss.clear();
}
auto nread = ss.tellg(); auto nread = ss.tellg();
*end = const_cast<char *>(start) + nread; *end = const_cast<char *>(start) + nread;
return d; return d;
@ -44,20 +55,26 @@ 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
@ -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:
@ -93,7 +111,8 @@ size_t build_random_string(RandomEngine &rand, char *buffer) {
} }
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,7 +153,8 @@ 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__) || \
defined(sun) || defined(__sun)
d = cygwin_strtod_l(st, &pr); 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");
@ -152,7 +173,8 @@ 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__) || \
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");
@ -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

View File

@ -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>
bool eddelbuettel() { struct test_data {
std::vector<std::string> inputs = {"infinity", std::string input;
" \r\n\t\f\v3.16227766016838 \r\n\t\f\v", bool expected_success;
" \r\n\t\f\v3 \r\n\t\f\v", double expected_result;
" 1970-01-01",
"-NaN",
"-inf",
" \r\n\t\f\v2.82842712474619 \r\n\t\f\v",
"nan",
" \r\n\t\f\v2.44948974278318 \r\n\t\f\v",
"Inf",
" \r\n\t\f\v2 \r\n\t\f\v",
"-infinity",
" \r\n\t\f\v0 \r\n\t\f\v",
" \r\n\t\f\v1.73205080756888 \r\n\t\f\v",
" \r\n\t\f\v1 \r\n\t\f\v",
" \r\n\t\f\v1.4142135623731 \r\n\t\f\v",
" \r\n\t\f\v2.23606797749979 \r\n\t\f\v",
"1970-01-02 ",
" \r\n\t\f\v2.64575131106459 \r\n\t\f\v",
"inf",
"-nan",
"NaN",
"",
"-Inf",
"+2.2",
"1d+4",
"1d-1",
"0.",
"-.1",
"+.1",
"1e+1",
"+1e1"};
std::vector<std::pair<bool, double>> expected_results = {
{true, std::numeric_limits<double>::infinity()},
{true, 3.16227766016838},
{true, 3},
{false, -1},
{true, std::numeric_limits<double>::quiet_NaN()},
{true, -std::numeric_limits<double>::infinity()},
{true, 2.82842712474619},
{true, std::numeric_limits<double>::quiet_NaN()},
{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]; bool eddelbuettel() {
std::pair<bool, double> expected = expected_results[i]; std::vector<test_data> const test_datas = {
{"infinity", true, std::numeric_limits<double>::infinity()},
{" \r\n\t\f\v3.16227766016838 \r\n\t\f\v", true, 3.16227766016838},
{" \r\n\t\f\v3 \r\n\t\f\v", true, 3.0},
{" 1970-01-01", false, 0.0},
{"-NaN", true, std::numeric_limits<double>::quiet_NaN()},
{"-inf", true, -std::numeric_limits<double>::infinity()},
{" \r\n\t\f\v2.82842712474619 \r\n\t\f\v", true, 2.82842712474619},
{"nan", true, std::numeric_limits<double>::quiet_NaN()},
{" \r\n\t\f\v2.44948974278318 \r\n\t\f\v", true, 2.44948974278318},
{"Inf", true, std::numeric_limits<double>::infinity()},
{" \r\n\t\f\v2 \r\n\t\f\v", true, 2.0},
{"-infinity", true, -std::numeric_limits<double>::infinity()},
{" \r\n\t\f\v0 \r\n\t\f\v", true, 0.0},
{" \r\n\t\f\v1.73205080756888 \r\n\t\f\v", true, 1.73205080756888},
{" \r\n\t\f\v1 \r\n\t\f\v", true, 1.0},
{" \r\n\t\f\v1.4142135623731 \r\n\t\f\v", true, 1.4142135623731},
{" \r\n\t\f\v2.23606797749979 \r\n\t\f\v", true, 2.23606797749979},
{"1970-01-02 ", false, 0.0},
{" \r\n\t\f\v2.64575131106459 \r\n\t\f\v", true, 2.64575131106459},
{"inf", true, std::numeric_limits<double>::infinity()},
{"-nan", true, std::numeric_limits<double>::quiet_NaN()},
{"NaN", true, std::numeric_limits<double>::quiet_NaN()},
{"", false, 0.0},
{"-Inf", true, -std::numeric_limits<double>::infinity()},
{"+2.2", true, 2.2},
{"1d+4", false, 0.0},
{"1d-1", false, 0.0},
{"0.", true, 0.0},
{"-.1", true, -0.1},
{"+.1", true, 0.1},
{"1e+1", true, 10.0},
{"+1e1", true, 10.0},
{"-+0", false, 0.0},
{"-+inf", false, 0.0},
{"-+nan", false, 0.0},
};
for (size_t i = 0; i < test_datas.size(); i++) {
auto const &input = test_datas[i].input;
auto const expected_success = test_datas[i].expected_success;
auto const expected_result = test_datas[i].expected_result;
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;
} }
} }

View File

@ -7,33 +7,44 @@
#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;
}
if (ss.eof()) {
ss.clear();
}
auto nread = ss.tellg(); auto nread = ss.tellg();
*end = const_cast<char *>(start) + nread; *end = const_cast<char *>(start) + nread;
return d; return d;
} }
float cygwin_strtof_l(const char* start, char** end) {
float cygwin_strtof_l(char const *start, char **end) {
float d; float 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;
}
if (ss.eof()) {
ss.clear();
}
auto nread = ss.tellg(); auto nread = ss.tellg();
*end = const_cast<char *>(start) + nread; *end = const_cast<char *>(start) + nread;
return d; return d;
@ -44,20 +55,26 @@ 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
@ -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,7 +148,8 @@ 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__) || \
defined(sun) || defined(__sun)
d = cygwin_strtod_l(st, &pr); 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");
@ -148,7 +168,8 @@ 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__) || \
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");
@ -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

View 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
View 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;
}