Merge branch 'main' of https://github.com/Pharago/fast_float into other_chars

This commit is contained in:
Daniel Lemire 2023-04-26 16:40:09 -04:00
commit 927eb9bcd2
71 changed files with 2893 additions and 1408 deletions

View File

@ -1,27 +1,47 @@
name: Alpine Linux name: Alpine Linux
'on': on:
- push - push
- pull_request - pull_request
jobs: jobs:
ubuntu-build: build:
name: Build on Alpine ${{ matrix.arch }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
arch:
- x86_64
- x86
- aarch64
- armv7
- ppc64le
- riscv64
steps: steps:
- uses: actions/checkout@v2 - name: Checkout repository
- name: start docker uses: actions/checkout@v1
- name: Install latest Alpine Linux for ${{ matrix.arch }}
uses: jirutka/setup-alpine@v1
with:
arch: ${{ matrix.arch }}
branch: ${{ matrix.arch == 'riscv64' && 'edge' || 'latest-stable' }}
packages: >
build-base
cmake
g++
linux-headers
git
bash
build-base
- name: Prepare
run: | run: |
docker run -w /src -dit --name alpine -v $PWD:/src alpine:latest cmake -DFASTFLOAT_TEST=ON -B build
echo 'docker exec alpine "$@";' > ./alpine.sh shell: alpine.sh {0}
chmod +x ./alpine.sh - name: Build
- name: install packages
run: | run: |
./alpine.sh apk update cmake --build build
./alpine.sh apk add build-base cmake g++ linux-headers git bash shell: alpine.sh {0}
- name: cmake - name: Test
run: | run: |
./alpine.sh cmake -DFASTFLOAT_TEST=ON -B build_for_alpine ctest --test-dir build -R basictest
- name: build shell: alpine.sh {0}
run: |
./alpine.sh cmake --build build_for_alpine
- name: test
run: |
./alpine.sh bash -c "cd build_for_alpine && ctest -R basictest"

View File

@ -1,20 +1,12 @@
name: Amalgamate Ubuntu 20.04 CI (GCC 9, 8) name: Amalgamate Ubuntu 20.04 CI (GCC 9)
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
ubuntu-build: ubuntu-build:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
include:
# Legacy/x86 compilers cause CI failures.
#- {cxx: -DCMAKE_CXX_COMPILER=g++-8, arch: }
- {cxx: , arch: } # default=gcc9
#- {cxx: , arch: -DCMAKE_CXX_FLAGS="-m32"} # default=gcc9
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Compile with amalgamation - name: Compile with amalgamation
run: | run: |
mkdir build && mkdir build &&

View File

@ -23,7 +23,7 @@ jobs:
CMAKE_GENERATOR: Ninja CMAKE_GENERATOR: Ninja
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- 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@v2 - uses: actions/checkout@v3
- uses: msys2/setup-msys2@v2 - uses: msys2/setup-msys2@v2
with: with:
update: true update: true

30
.github/workflows/s390x.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: Ubuntu s390x (GCC 11)
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: uraimo/run-on-arch-action@v2
name: Test
id: runcmd
with:
arch: s390x
githubToken: ${{ github.token }}
distro: ubuntu_latest
install: |
apt-get update -q -y
apt-get install -y cmake make g++
run: |
cmake -DCMAKE_BUILD_TYPE=Release -B build
cmake --build build -j=2
ctest --output-on-failure --test-dir build

View File

@ -1,21 +1,12 @@
name: Ubuntu 18.04 CI (GCC 7, 6, 5) name: Ubuntu 18.04 CI (GCC 7)
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
ubuntu-build: ubuntu-build:
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
strategy:
fail-fast: false
matrix:
include:
# Legacy/x86 compilers cause CI failures.
#- {cxx: -DCMAKE_CXX_COMPILER=g++-5, arch: }
#- {cxx: -DCMAKE_CXX_COMPILER=g++-6, arch: }
- {cxx: , arch: } # default=gcc7
#- {cxx: , arch: -DCMAKE_CXX_FLAGS="-m32"} # default=gcc7
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup cmake - name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1.4 uses: jwlawson/actions-setup-cmake@v1.4
with: with:

View File

@ -8,7 +8,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Use cmake - name: Use cmake
run: | run: |
mkdir build && mkdir build &&

16
.github/workflows/ubuntu20-fastmath.yml vendored Normal file
View File

@ -0,0 +1,16 @@
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

@ -5,16 +5,8 @@ on: [push, pull_request]
jobs: jobs:
ubuntu-build: ubuntu-build:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
include:
# Legacy/x86 compilers cause CI failures.
#- {cxx: -DCMAKE_CXX_COMPILER=g++-8, arch: }
- {cxx: , arch: } # default=gcc9
#- {cxx: , arch: -DCMAKE_CXX_FLAGS="-m32"} # default=gcc9
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Use cmake - name: Use cmake
run: | run: |
mkdir build && mkdir build &&

25
.github/workflows/ubuntu22-clang.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Ubuntu 22.04 CI (clang 14)
on: [push, pull_request]
jobs:
ubuntu-build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install clang++-14
run: sudo apt-get install -y clang++-14
- name: Use cmake
run: |
mkdir build &&
cd build &&
CXX=clang++-14 cmake -DFASTFLOAT_TEST=ON .. &&
cmake --build . &&
ctest --output-on-failure
- name: Use cmake CXX20
run: |
mkdir build20 &&
cd build20 &&
CXX=clang++-14 cmake -DFASTFLOAT_CONSTEXPR_TESTS=ON -DCMAKE_CXX_STANDARD=20 -DFASTFLOAT_TEST=ON .. &&
cmake --build . &&
ctest --output-on-failure

23
.github/workflows/ubuntu22-gcc12.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: Ubuntu 22.04 CI (GCC 12)
on: [push, pull_request]
jobs:
ubuntu-build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Use cmake
run: |
mkdir build &&
cd build &&
CXX=g++-12 CXXFLAGS=-Werror cmake -DFASTFLOAT_TEST=ON .. &&
cmake --build . &&
ctest --output-on-failure
- name: Use cmake CXX20
run: |
mkdir build20 &&
cd build20 &&
CXX=g++-12 CXXFLAGS=-Werror cmake -DFASTFLOAT_CONSTEXPR_TESTS=ON -DCMAKE_CXX_STANDARD=20 -DFASTFLOAT_TEST=ON .. &&
cmake --build . &&
ctest --output-on-failure

16
.github/workflows/ubuntu22.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Ubuntu 22.04 CI (GCC 11)
on: [push, pull_request]
jobs:
ubuntu-build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Use cmake
run: |
mkdir build &&
cd build &&
cmake -DFASTFLOAT_TEST=ON .. &&
cmake --build . &&
ctest --output-on-failure

View File

@ -1,29 +0,0 @@
name: VS15-CI
on: [push, pull_request]
jobs:
ci:
if: >-
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
! contains(toJSON(github.event.commits.*.message), '[skip github]')
name: windows-vs15
runs-on: windows-2016
strategy:
fail-fast: false
matrix:
include:
- {gen: Visual Studio 15 2017, arch: Win32}
- {gen: Visual Studio 15 2017, arch: x64}
steps:
- uses: actions/checkout@v2
- name: Configure
run: |
mkdir build
cd build && cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -DFASTFLOAT_TEST=ON ..
- name: Build
run: cmake --build build --verbose --config Release --parallel
- name: 'Run CTest'
run: |
cd build
ctest -C Release --output-on-failure -R basictest

View File

@ -1,21 +0,0 @@
name: VS16-ARM-CI
on: [push, pull_request]
jobs:
ci:
name: windows-vs16
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- {arch: ARM}
- {arch: ARM64}
steps:
- name: checkout
uses: actions/checkout@v2
- name: Use cmake
run: |
cmake -A ${{ matrix.arch }} -DCMAKE_CROSSCOMPILING=1 -DFASTFLOAT_TEST=ON -B build &&
cmake --build build --verbose

View File

@ -1,29 +0,0 @@
name: VS16-CI
on: [push, pull_request]
jobs:
ci:
name: windows-vs16
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- {gen: Visual Studio 16 2019, arch: Win32}
- {gen: Visual Studio 16 2019, arch: x64}
steps:
- name: checkout
uses: actions/checkout@v2
- name: Use cmake
run: |
mkdir build &&
cd build &&
cmake ${{matrix.cxx}} ${{matrix.arch}} -DFASTFLOAT_TEST=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
cmake --build . --verbose &&
ctest --output-on-failure &&
cmake --install . &&
cd ../tests/installation_tests/find &&
mkdir build && cd build && cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../build/destination .. && cmake --build . --verbose
cd ../../issue72_installation &&
mkdir build && cd build && cmake -DCMAKE_INSTALL_PREFIX:PATH=../../../build/destination .. && cmake --build . --verbose

View File

@ -1,27 +0,0 @@
name: VS16-CLANG-CI
on: [push, pull_request]
jobs:
ci:
name: windows-vs16
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- {gen: Visual Studio 16 2019, arch: Win32}
- {gen: Visual Studio 16 2019, arch: x64}
steps:
- name: checkout
uses: actions/checkout@v2
- name: Configure
run: |
mkdir build
cd build && cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -T ClangCL -DFASTFLOAT_TEST=ON ..
- name: Build
run: cmake --build build --config Release --parallel
- name: Run basic tests
run: |
cd build
ctest -C Release --output-on-failure -R basictest

View File

@ -1,24 +0,0 @@
name: VS16-CI C++20
on: [push, pull_request]
jobs:
ci:
name: windows-vs16
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- {gen: Visual Studio 16 2019, arch: Win32}
- {gen: Visual Studio 16 2019, arch: x64}
steps:
- name: checkout
uses: actions/checkout@v2
- name: Use cmake
run: |
mkdir build &&
cd build &&
cmake ${{matrix.cxx}} ${{matrix.arch}} -DCMAKE_CXX_STANDARD=20 -DFASTFLOAT_TEST=ON -DCMAKE_INSTALL_PREFIX:PATH=destination .. &&
cmake --build . --verbose &&
ctest --output-on-failure

30
.github/workflows/vs17-arm-ci.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: VS17-ARM-CI
on: [push, pull_request]
jobs:
ci:
name: vs17/${{matrix.arch}}/${{matrix.cfg}}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
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: Debug}
steps:
- name: checkout
uses: actions/checkout@v3
- name: configure
run: |
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DCMAKE_CROSSCOMPILING=1 -DFASTFLOAT_TEST=ON
- name: build
run: |
cmake --build build --verbose --config ${{matrix.cfg}} --parallel
# disabled because it requires a toolchain
#- name: test
# run: |
# cd build &&
# ctest --output-on-failure -C ${{matrix.cfg}}

43
.github/workflows/vs17-ci.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: VS17-CI
on: [push, pull_request]
jobs:
ci:
name: vs17/${{matrix.arch}}/${{matrix.cfg}}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- {gen: Visual Studio 17 2022, arch: Win32, cfg: Release}
- {gen: Visual Studio 17 2022, arch: Win32, cfg: Debug}
- {gen: Visual Studio 17 2022, arch: x64, cfg: Release}
- {gen: Visual Studio 17 2022, arch: x64, cfg: Debug}
steps:
- name: checkout
uses: actions/checkout@v3
- name: configure
run: |
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DFASTFLOAT_TEST=ON -DCMAKE_INSTALL_PREFIX:PATH=destination
- name: build
run: |
cmake --build build --verbose --config ${{matrix.cfg}} --parallel
- name: test
run: |
cd build &&
ctest --output-on-failure -C ${{matrix.cfg}}
- name: install
run: |
cd build &&
cmake --install .
- name: test install (find)
run: |
cd tests/installation_tests/find &&
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DCMAKE_INSTALL_PREFIX:PATH=../../../build/destination &&
cmake --build build --verbose --config ${{matrix.cfg}} --parallel
- name: test install (issue 72)
run: |
cd tests/installation_tests/issue72_installation &&
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -DCMAKE_INSTALL_PREFIX:PATH=../../../build/destination &&
cmake --build build --verbose --config ${{matrix.cfg}} --parallel

28
.github/workflows/vs17-clang-ci.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: VS17-CLANG-CI
on: [push, pull_request]
jobs:
ci:
name: vs17/${{matrix.arch}}/${{matrix.cfg}}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- {gen: Visual Studio 17 2022, arch: Win32, cfg: Release}
- {gen: Visual Studio 17 2022, arch: Win32, cfg: Debug}
- {gen: Visual Studio 17 2022, arch: x64, cfg: Release}
- {gen: Visual Studio 17 2022, arch: x64, cfg: Debug}
steps:
- name: checkout
uses: actions/checkout@v3
- name: Configure
run: |
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}} -T ClangCL -DFASTFLOAT_TEST=ON
- name: Build
run: cmake --build build --config ${{matrix.cfg}} --parallel --verbose
- name: Run basic tests
run: |
cd build
ctest -C ${{matrix.cfg}} --output-on-failure -R basictest

33
.github/workflows/vs17-cxx20.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: VS17-CI C++20
on: [push, pull_request]
jobs:
ci:
name: vs17/${{matrix.arch}}/${{matrix.cfg}}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- {gen: Visual Studio 17 2022, arch: Win32, cfg: Release}
- {gen: Visual Studio 17 2022, arch: Win32, cfg: Debug}
- {gen: Visual Studio 17 2022, arch: x64, cfg: Release}
- {gen: Visual Studio 17 2022, arch: x64, cfg: Debug}
steps:
- name: checkout
uses: actions/checkout@v3
- name: configure
run: >-
cmake -S . -B build -G "${{matrix.gen}}" -A ${{matrix.arch}}
-DCMAKE_CXX_STANDARD=20
-DFASTFLOAT_TEST=ON
-DFASTFLOAT_CONSTEXPR_TESTS=ON
-DCMAKE_INSTALL_PREFIX:PATH=destination
- name: build
run: |
cmake --build build --verbose --config ${{matrix.cfg}} --parallel
- name: test
run: |
cd build &&
ctest --output-on-failure -C ${{matrix.cfg}}

View File

@ -2,8 +2,6 @@ cmake_minimum_required(VERSION 3.9)
project(fast_float VERSION 3.10.1 LANGUAGES CXX) project(fast_float VERSION 3.10.1 LANGUAGES CXX)
option(FASTFLOAT_TEST "Enable tests" OFF) option(FASTFLOAT_TEST "Enable tests" OFF)
set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard to be used")
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(FASTFLOAT_TEST) if(FASTFLOAT_TEST)
enable_testing() enable_testing()
add_subdirectory(tests) add_subdirectory(tests)
@ -22,14 +20,21 @@ if (NOT CMAKE_BUILD_TYPE)
endif() endif()
endif() endif()
option(FASTFLOAT_INSTALL "Enable install" ON)
if(FASTFLOAT_INSTALL)
include(GNUInstallDirs)
endif()
add_library(fast_float INTERFACE) add_library(fast_float INTERFACE)
add_library(FastFloat::fast_float ALIAS fast_float)
target_include_directories( target_include_directories(
fast_float fast_float
INTERFACE INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
) )
target_compile_features(fast_float INTERFACE cxx_std_11)
if(FASTFLOAT_SANITIZE) if(FASTFLOAT_SANITIZE)
target_compile_options(fast_float INTERFACE -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=all) target_compile_options(fast_float INTERFACE -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=all)
target_link_libraries(fast_float INTERFACE -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=all) target_link_libraries(fast_float INTERFACE -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=all)
@ -42,24 +47,30 @@ if(MSVC_VERSION GREATER 1910)
endif() endif()
include(CMakePackageConfigHelpers) if(FASTFLOAT_INSTALL)
include(CMakePackageConfigHelpers)
set(FASTFLOAT_VERSION_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/module/FastFloatConfigVersion.cmake") set(FASTFLOAT_VERSION_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/module/FastFloatConfigVersion.cmake")
set(FASTFLOAT_PROJECT_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/module/FastFloatConfig.cmake") set(FASTFLOAT_PROJECT_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/module/FastFloatConfig.cmake")
set(FASTFLOAT_INSTALL_DIR "share/FastFloat") set(FASTFLOAT_CONFIG_INSTALL_DIR "${CMAKE_INSTALL_DATAROOTDIR}/cmake/FastFloat")
write_basic_package_version_file("${FASTFLOAT_VERSION_CONFIG}" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion) if(${CMAKE_VERSION} VERSION_LESS "3.14")
configure_package_config_file("cmake/config.cmake.in" write_basic_package_version_file("${FASTFLOAT_VERSION_CONFIG}" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion)
"${FASTFLOAT_PROJECT_CONFIG}" else()
INSTALL_DESTINATION "${FASTFLOAT_INSTALL_DIR}") write_basic_package_version_file("${FASTFLOAT_VERSION_CONFIG}" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ARCH_INDEPENDENT)
endif()
configure_package_config_file("cmake/config.cmake.in"
"${FASTFLOAT_PROJECT_CONFIG}"
INSTALL_DESTINATION "${FASTFLOAT_CONFIG_INSTALL_DIR}")
install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/fast_float" DESTINATION "include") install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/fast_float" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
install(FILES "${FASTFLOAT_PROJECT_CONFIG}" "${FASTFLOAT_VERSION_CONFIG}" DESTINATION "${FASTFLOAT_INSTALL_DIR}") install(FILES "${FASTFLOAT_PROJECT_CONFIG}" "${FASTFLOAT_VERSION_CONFIG}" DESTINATION "${FASTFLOAT_CONFIG_INSTALL_DIR}")
install(EXPORT ${PROJECT_NAME}-targets NAMESPACE FastFloat:: DESTINATION "${FASTFLOAT_INSTALL_DIR}") install(EXPORT ${PROJECT_NAME}-targets NAMESPACE FastFloat:: DESTINATION "${FASTFLOAT_CONFIG_INSTALL_DIR}")
install(TARGETS fast_float install(TARGETS fast_float
EXPORT ${PROJECT_NAME}-targets EXPORT ${PROJECT_NAME}-targets
RUNTIME DESTINATION bin RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
ARCHIVE DESTINATION lib ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION lib LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
) )
endif()

View File

@ -4,3 +4,5 @@ Marcin Wojdyr
Neal Richardson Neal Richardson
Tim Paine Tim Paine
Fabio Pellacini Fabio Pellacini
Lénárd Szolnoki
Jan Pharago

View File

@ -175,18 +175,7 @@
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work. Copyright 2021 The fast_float authors
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 The fast_float authors
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -1,3 +1,7 @@
MIT License
Copyright (c) 2021 The fast_float authors
Permission is hereby granted, free of charge, to any Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the documentation files (the "Software"), to deal in the

133
README.md
View File

@ -1,11 +1,8 @@
## fast_float number parsing library: 4x faster than strtod
![Ubuntu 20.04 CI (GCC 9)](https://github.com/lemire/fast_float/workflows/Ubuntu%2020.04%20CI%20(GCC%209)/badge.svg) ## fast_float number parsing library: 4x faster than strtod
![Ubuntu 18.04 CI (GCC 7)](https://github.com/lemire/fast_float/workflows/Ubuntu%2018.04%20CI%20(GCC%207)/badge.svg) [![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)
![Alpine Linux](https://github.com/lemire/fast_float/workflows/Alpine%20Linux/badge.svg) [![VS17-CI](https://github.com/fastfloat/fast_float/actions/workflows/vs17-ci.yml/badge.svg)](https://github.com/fastfloat/fast_float/actions/workflows/vs17-ci.yml)
![MSYS2-CI](https://github.com/lemire/fast_float/workflows/MSYS2-CI/badge.svg) [![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)
![VS16-CLANG-CI](https://github.com/lemire/fast_float/workflows/VS16-CLANG-CI/badge.svg)
[![VS16-CI](https://github.com/fastfloat/fast_float/actions/workflows/vs16-ci.yml/badge.svg)](https://github.com/fastfloat/fast_float/actions/workflows/vs16-ci.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++ from_chars
functions for `float` and `double` types. These functions convert ASCII strings representing functions for `float` and `double` types. These functions convert ASCII strings representing
@ -28,8 +25,8 @@ struct from_chars_result {
``` ```
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 floating-point numbers expecting
a locale-independent format equivalent to the C++17 from_chars function. a locale-independent format equivalent to the C++17 from_chars function.
The resulting floating-point value is the closest floating-point values (using either float or double), The resulting floating-point value is the closest floating-point values (using either float or double),
using the "round to even" convention for values that would otherwise fall right in-between two values. 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. That is, we provide exact parsing according to the IEEE standard.
@ -47,7 +44,7 @@ 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::string input = "3.1416 xyz "; const std::string input = "3.1416 xyz ";
double result; double result;
@ -60,39 +57,60 @@ int main() {
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 optional last argument of
the type `fast_float::chars_format`. It is a bitset value: we check whether 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 `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. 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 default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`.
The library seeks to follow the C++17 (see [20.19.3](http://eel.is/c++draft/charconv.from.chars).(7.1)) specification. The library seeks to follow the C++17 (see [20.19.3](http://eel.is/c++draft/charconv.from.chars).(7.1)) specification.
* The `from_chars` function does not skip leading white-space characters. * The `from_chars` function does not skip leading white-space characters.
* [A leading `+` sign](https://en.cppreference.com/w/cpp/utility/from_chars) is forbidden. * [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. * 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 only support `float` and `double` types at this time.
* 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. * 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`).
## C++20: compile-time evaluation (constexpr)
In C++20, you may use `fast_float::from_chars` to parse strings
at compile-time, as in the following example:
```C++
// consteval forces compile-time evaluation of the function in C++20.
consteval double parse(std::string_view input) {
double result;
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
if(answer.ec != std::errc()) { return -1.0; }
return result;
}
// This function should compile to a function which
// merely returns 3.1415.
constexpr double constexptest() {
return parse("3.1415 input");
}
```
## Using commas as decimal separator ## Using commas as decimal separator
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 users still want to use the `fast_float` library with in a locale-dependent some 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 users
to pass a `parse_options` instance which contains a custom decimal separator (e.g., to pass a `parse_options` instance which contains a custom decimal separator (e.g.,
the comma). You may use it as follows. 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 "; const std::string input = "3,1416 xyz ";
double result; double result;
@ -104,23 +122,62 @@ int main() {
} }
``` ```
You can parse delimited numbers:
```C++
const std::string input = "234532.3426362,7869234.9823,324562.645";
double result;
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
if(answer.ec != std::errc()) {
// check error
}
// we have result == 234532.3426362.
if(answer.ptr[0] != ',') {
// unexpected delimiter
}
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
if(answer.ec != std::errc()) {
// check error
}
// we have result == 7869234.9823.
if(answer.ptr[0] != ',') {
// unexpected delimiter
}
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
if(answer.ec != std::errc()) {
// check error
}
// we have result == 324562.645.
```
## Reference
- Daniel Lemire, [Number Parsing at a Gigabyte per Second](https://arxiv.org/abs/2101.11408), Software: Pratice and Experience 51 (8), 2021. ## Relation With Other Work
The fast_float library is part of:
- 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)
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 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
- Daniel Lemire, [Number Parsing at a Gigabyte per 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 (to appear)
## 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 `rcppfastfloat`.
- [There is a Rust port of the fast_float library](https://github.com/aldanor/fast-float-rust/) called `fast-float-rust`. - [There is a Rust port of the fast_float library](https://github.com/aldanor/fast-float-rust/) called `fast-float-rust`.
- [There is a Java port of the fast_float library](https://github.com/wrandelshofer/FastDoubleParser) called `FastDoubleParser`. - [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 C# port of the fast_float library](https://github.com/CarlVerret/csFastFloat) called `csFastFloat`. - [There is a C# port of the fast_float library](https://github.com/CarlVerret/csFastFloat) called `csFastFloat`.
## Relation With Other Work
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).
## Users ## Users
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). 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).
@ -133,14 +190,14 @@ It can parse random floating-point numbers at a speed of 1 GB/s on some systems.
<img src="http://lemire.me/blog/wp-content/uploads/2020/11/fastfloat_speed.png" width="400"> <img src="http://lemire.me/blog/wp-content/uploads/2020/11/fastfloat_speed.png" width="400">
``` ```
$ ./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
netlib : 271.18 MB/s (+/- 1.2 %) 12.93 Mfloat/s netlib : 271.18 MB/s (+/- 1.2 %) 12.93 Mfloat/s
doubleconversion : 225.35 MB/s (+/- 1.2 %) 10.74 Mfloat/s doubleconversion : 225.35 MB/s (+/- 1.2 %) 10.74 Mfloat/s
strtod : 190.94 MB/s (+/- 1.6 %) 9.10 Mfloat/s strtod : 190.94 MB/s (+/- 1.6 %) 9.10 Mfloat/s
abseil : 430.45 MB/s (+/- 2.2 %) 20.52 Mfloat/s abseil : 430.45 MB/s (+/- 2.2 %) 20.52 Mfloat/s
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 https://github.com/lemire/simple_fastfloat_benchmark for our benchmarking code.
@ -181,23 +238,23 @@ You should change the `GIT_TAG` line so that you recover the version you wish to
## 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 directory of this repository. Just run the script from the root directory of this repository.
You can customize the license type and output file if desired as described in You can customize the license type and output file 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/v1.1.2/fast_float.h https://github.com/fastfloat/fast_float/releases/download/v3.4.0/fast_float.h
## 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 especially from exchanges with
Michael Eisel, who motivated the original research with his key insights, and with Nigel Tao who provided Michael Eisel, who motivated the original 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. 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 was originally published
under the Apache 2.0 license. under the Apache 2.0 license.
## License ## License

8
fuzz/build.sh Normal file
View File

@ -0,0 +1,8 @@
#!/bin/bash
$CXX $CFLAGS $CXXFLAGS \
-I $SRC/fast_float/include \
-c $SRC/fast_float/fuzz/from_chars.cc -o from_chars.o
$CXX $CFLAGS $CXXFLAGS $LIB_FUZZING_ENGINE from_chars.o \
-o $OUT/from_chars

34
fuzz/from_chars.cc Normal file
View File

@ -0,0 +1,34 @@
#include "fast_float/fast_float.h"
#include <fuzzer/FuzzedDataProvider.h>
#include <string>
#include <system_error>
fast_float::chars_format arbitrary_format(FuzzedDataProvider &fdp) {
using fast_float::chars_format;
switch (fdp.ConsumeIntegralInRange<int>(0,3)) {
case 0:
return chars_format::scientific;
break;
case 1:
return chars_format::fixed;
break;
case 2:
return chars_format::fixed;
break;
}
return chars_format::general;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
FuzzedDataProvider fdp(data, size);
fast_float::chars_format format = arbitrary_format(fdp);
double result_d = 0.0;
std::string input_d = fdp.ConsumeRandomLengthString(128);
auto answer =
fast_float::from_chars(input_d.data(), input_d.data() + input_d.size(), result_d, format);
std::string input_f = fdp.ConsumeRandomLengthString(128);
double result_f = 0.0;
answer =
fast_float::from_chars(input_f.data(), input_f.data() + input_f.size(), result_f, format);
return 0;
}

View File

@ -12,9 +12,12 @@ namespace fast_float {
// Next function can be micro-optimized, but compilers are entirely // Next function can be micro-optimized, but compilers are entirely
// able to optimize it well. // able to optimize it well.
fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } template <typename UC>
fastfloat_really_inline constexpr bool is_integer(UC c) noexcept {
return !(c > UC('9') || c < UC('0'));
}
fastfloat_really_inline 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 & 0x0000FF0000000000) >> 24
@ -24,8 +27,17 @@ fastfloat_really_inline uint64_t byteswap(uint64_t val) {
| (val & 0x000000000000FF00) << 40 | (val & 0x000000000000FF00) << 40
| (val & 0x00000000000000FF) << 56; | (val & 0x00000000000000FF) << 56;
} }
template <typename UC>
fastfloat_really_inline uint64_t read_u64(const char *chars) { fastfloat_really_inline FASTFLOAT_CONSTEXPR20
uint64_t read_u64(UC const * chars) {
if (cpp20_and_in_constexpr() || sizeof(UC) > 1) {
uint64_t val{};
for(int i = 0; i < 8; ++i) {
val |= uint64_t(char(*chars)) << (i * 8);
++chars;
}
return val;
}
uint64_t val; uint64_t val;
::memcpy(&val, chars, sizeof(uint64_t)); ::memcpy(&val, chars, sizeof(uint64_t));
#if FASTFLOAT_IS_BIG_ENDIAN == 1 #if FASTFLOAT_IS_BIG_ENDIAN == 1
@ -35,7 +47,16 @@ fastfloat_really_inline uint64_t read_u64(const char *chars) {
return val; return val;
} }
fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { 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 #if FASTFLOAT_IS_BIG_ENDIAN == 1
// Need to read as-if the number was in little-endian order. // Need to read as-if the number was in little-endian order.
val = byteswap(val); val = byteswap(val);
@ -44,7 +65,8 @@ fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) {
} }
// credit @aqrit // credit @aqrit
fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { fastfloat_really_inline FASTFLOAT_CONSTEXPR14
uint32_t parse_eight_digits_unrolled(uint64_t val) {
const uint64_t mask = 0x000000FF000000FF; const uint64_t mask = 0x000000FF000000FF;
const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32)
@ -53,47 +75,55 @@ fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) {
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);
} }
template <typename UC>
fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { fastfloat_really_inline FASTFLOAT_CONSTEXPR20
uint32_t parse_eight_digits_unrolled(UC const * chars) noexcept {
return parse_eight_digits_unrolled(read_u64(chars)); return parse_eight_digits_unrolled(read_u64(chars));
} }
// credit @aqrit // credit @aqrit
fastfloat_really_inline 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));
} }
fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { template <typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
bool is_made_of_eight_digits_fast(UC const * chars) noexcept {
return is_made_of_eight_digits_fast(read_u64(chars)); return is_made_of_eight_digits_fast(read_u64(chars));
} }
typedef span<const char> byte_span; template <typename UC>
struct parsed_number_string_t {
struct parsed_number_string {
int64_t exponent{0}; int64_t exponent{0};
uint64_t mantissa{0}; uint64_t mantissa{0};
const char *lastmatch{nullptr}; UC const * lastmatch{nullptr};
bool negative{false}; bool negative{false};
bool valid{false}; bool valid{false};
bool too_many_digits{false}; bool too_many_digits{false};
// contains the range of the significant digits // contains the range of the significant digits
byte_span integer{}; // non-nullable span<UC> integer{}; // non-nullable
byte_span fraction{}; // nullable span<UC> fraction{}; // nullable
}; };
using byte_span = span<char>;
using parsed_number_string = parsed_number_string_t<char>;
// 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.
fastfloat_really_inline template <typename UC>
parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { fastfloat_really_inline FASTFLOAT_CONSTEXPR20
const chars_format fmt = options.format; parsed_number_string_t<UC> parse_number_string(UC const *p, UC const * pend, parse_options_t<UC> options) noexcept {
const char decimal_point = options.decimal_point; chars_format const fmt = options.format;
UC const decimal_point = options.decimal_point;
parsed_number_string answer; parsed_number_string_t<UC> answer;
answer.valid = false; answer.valid = false;
answer.too_many_digits = false; answer.too_many_digits = false;
answer.negative = (*p == '-'); answer.negative = (*p == UC('-'));
if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here #ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default
if ((*p == UC('-')) || (*p == UC('+'))) {
#else
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 answer;
@ -102,28 +132,24 @@ parsed_number_string parse_number_string(const char *p, const char *pend, parse_
return answer; return answer;
} }
} }
const char *const start_digits = p; UC const * const start_digits = p;
uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad)
while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok
p += 8;
}
while ((p != pend) && is_integer(*p)) { while ((p != pend) && is_integer(*p)) {
// 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 - '0'); // might overflow, we will handle the overflow later uint64_t(*p - UC('0')); // might overflow, we will handle the overflow later
++p; ++p;
} }
const char *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 = byte_span(start_digits, size_t(digit_count)); answer.integer = span<UC>(start_digits, size_t(digit_count));
int64_t exponent = 0; int64_t exponent = 0;
if ((p != pend) && (*p == decimal_point)) { if ((p != pend) && (*p == decimal_point)) {
++p; ++p;
const char* before = p; UC const * before = p;
// can occur at most twice without overflowing, but let it occur more, since // can occur at most twice without overflowing, but let it occur more, since
// for integers with many digits, digit parsing is the primary bottleneck. // for integers with many digits, digit parsing is the primary bottleneck.
while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
@ -131,12 +157,12 @@ parsed_number_string parse_number_string(const char *p, const char *pend, parse_
p += 8; p += 8;
} }
while ((p != pend) && is_integer(*p)) { while ((p != pend) && is_integer(*p)) {
uint8_t digit = uint8_t(*p - '0'); uint8_t digit = uint8_t(*p - UC('0'));
++p; ++p;
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 = byte_span(before, size_t(p - before)); answer.fraction = span<UC>(before, size_t(p - before));
digit_count -= exponent; digit_count -= exponent;
} }
// we must have encountered at least one integer! // we must have encountered at least one integer!
@ -144,14 +170,14 @@ parsed_number_string parse_number_string(const char *p, const char *pend, parse_
return answer; return answer;
} }
int64_t exp_number = 0; // explicit exponential part int64_t exp_number = 0; // explicit exponential part
if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { if ((fmt & chars_format::scientific) && (p != pend) && ((UC('e') == *p) || (UC('E') == *p))) {
const char * location_of_e = p; UC const * location_of_e = p;
++p; ++p;
bool neg_exp = false; bool neg_exp = false;
if ((p != pend) && ('-' == *p)) { if ((p != pend) && (UC('-') == *p)) {
neg_exp = true; neg_exp = true;
++p; ++p;
} else if ((p != pend) && ('+' == *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)) {
@ -163,7 +189,7 @@ parsed_number_string parse_number_string(const char *p, const char *pend, parse_
p = location_of_e; p = location_of_e;
} else { } else {
while ((p != pend) && is_integer(*p)) { while ((p != pend) && is_integer(*p)) {
uint8_t digit = uint8_t(*p - '0'); uint8_t digit = uint8_t(*p - UC('0'));
if (exp_number < 0x10000000) { if (exp_number < 0x10000000) {
exp_number = 10 * exp_number + digit; exp_number = 10 * exp_number + digit;
} }
@ -189,9 +215,9 @@ parsed_number_string parse_number_string(const char *p, const char *pend, parse_
// We have to handle the case where we have 0.0000somenumber. // We have to handle the case where we have 0.0000somenumber.
// We need to be mindful of the case where we only have zeroes... // We need to be mindful of the case where we only have zeroes...
// E.g., 0.000000000...000. // E.g., 0.000000000...000.
const char *start = start_digits; UC const * start = start_digits;
while ((start != pend) && (*start == '0' || *start == decimal_point)) { while ((start != pend) && (*start == UC('0') || *start == decimal_point)) {
if(*start == '0') { digit_count --; } if(*start == UC('0')) { digit_count --; }
start++; start++;
} }
if (digit_count > 19) { if (digit_count > 19) {
@ -201,19 +227,19 @@ parsed_number_string parse_number_string(const char *p, const char *pend, parse_
// pre-tokenized spans from above. // pre-tokenized spans from above.
i = 0; i = 0;
p = answer.integer.ptr; p = answer.integer.ptr;
const char* int_end = p + answer.integer.len(); UC const * int_end = p + answer.integer.len();
const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; const uint64_t 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 - '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 integers
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;
const char* frac_end = p + answer.fraction.len(); UC const * frac_end = p + answer.fraction.len();
while((i < minimal_nineteen_digit_integer) && (p != frac_end)) { while((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
i = i * 10 + uint64_t(*p - '0'); i = i * 10 + uint64_t(*p - UC('0'));
++p; ++p;
} }
exponent = answer.fraction.ptr - p + exp_number; exponent = answer.fraction.ptr - p + exp_number;

View File

@ -17,7 +17,7 @@ namespace fast_float {
// we might have platforms where `CHAR_BIT` is not 8, so let's avoid // we might have platforms where `CHAR_BIT` is not 8, so let's avoid
// doing `8 * sizeof(limb)`. // doing `8 * sizeof(limb)`.
#if defined(FASTFLOAT_64BIT) && !defined(__sparc) #if defined(FASTFLOAT_64BIT) && !defined(__sparc)
#define FASTFLOAT_64BIT_LIMB #define FASTFLOAT_64BIT_LIMB 1
typedef uint64_t limb; typedef uint64_t limb;
constexpr size_t limb_bits = 64; constexpr size_t limb_bits = 64;
#else #else
@ -50,27 +50,27 @@ struct stackvec {
stackvec &operator=(stackvec &&other) = delete; stackvec &operator=(stackvec &&other) = delete;
// create stack vector from existing limb span. // create stack vector from existing limb span.
stackvec(limb_span s) { FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) {
FASTFLOAT_ASSERT(try_extend(s)); FASTFLOAT_ASSERT(try_extend(s));
} }
limb& operator[](size_t index) noexcept { FASTFLOAT_CONSTEXPR14 limb& operator[](size_t index) noexcept {
FASTFLOAT_DEBUG_ASSERT(index < length); FASTFLOAT_DEBUG_ASSERT(index < length);
return data[index]; return data[index];
} }
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
const limb& rindex(size_t index) const noexcept { FASTFLOAT_CONSTEXPR14 const limb& rindex(size_t index) const noexcept {
FASTFLOAT_DEBUG_ASSERT(index < length); FASTFLOAT_DEBUG_ASSERT(index < length);
size_t rindex = length - index - 1; size_t rindex = length - index - 1;
return data[rindex]; return data[rindex];
} }
// set the length, without bounds checking. // set the length, without bounds checking.
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 { constexpr size_t len() const noexcept {
@ -83,12 +83,12 @@ struct stackvec {
return size; return size;
} }
// append item to vector, without bounds checking // append item to vector, without bounds checking
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
bool try_push(limb value) noexcept { FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept {
if (len() < capacity()) { if (len() < capacity()) {
push_unchecked(value); push_unchecked(value);
return true; return true;
@ -97,13 +97,13 @@ struct stackvec {
} }
} }
// add items to the vector, from a span, without bounds checking // add items to the vector, from a span, without bounds checking
void extend_unchecked(limb_span s) noexcept { FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept {
limb* ptr = data + length; limb* ptr = data + length;
::memcpy((void*)ptr, (const void*)s.ptr, sizeof(limb) * s.len()); 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
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()) {
extend_unchecked(s); extend_unchecked(s);
return true; return true;
@ -114,6 +114,7 @@ struct stackvec {
// 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.
FASTFLOAT_CONSTEXPR20
void resize_unchecked(size_t new_len, limb value) noexcept { void resize_unchecked(size_t new_len, limb value) noexcept {
if (new_len > len()) { if (new_len > len()) {
size_t count = new_len - len(); size_t count = new_len - len();
@ -126,7 +127,7 @@ struct stackvec {
} }
} }
// try to resize the vector, returning if the vector was resized. // try to resize the vector, returning if the vector was resized.
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()) {
return false; return false;
} else { } else {
@ -137,7 +138,7 @@ struct stackvec {
// 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.
bool nonzero(size_t index) const noexcept { FASTFLOAT_CONSTEXPR14 bool nonzero(size_t index) const noexcept {
while (index < len()) { while (index < len()) {
if (rindex(index) != 0) { if (rindex(index) != 0) {
return true; return true;
@ -147,27 +148,27 @@ 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.
void normalize() noexcept { FASTFLOAT_CONSTEXPR14 void normalize() noexcept {
while (len() > 0 && rindex(0) == 0) { while (len() > 0 && rindex(0) == 0) {
length--; length--;
} }
} }
}; };
fastfloat_really_inline fastfloat_really_inline FASTFLOAT_CONSTEXPR14
uint64_t empty_hi64(bool& truncated) noexcept { uint64_t empty_hi64(bool& truncated) noexcept {
truncated = false; truncated = false;
return 0; return 0;
} }
fastfloat_really_inline fastfloat_really_inline FASTFLOAT_CONSTEXPR20
uint64_t uint64_hi64(uint64_t r0, bool& truncated) noexcept { uint64_t 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_really_inline FASTFLOAT_CONSTEXPR20
uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept { uint64_t 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) {
@ -180,19 +181,19 @@ uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept {
} }
} }
fastfloat_really_inline fastfloat_really_inline FASTFLOAT_CONSTEXPR20
uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept { uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept {
return uint64_hi64(r0, truncated); return uint64_hi64(r0, truncated);
} }
fastfloat_really_inline fastfloat_really_inline FASTFLOAT_CONSTEXPR20
uint64_t uint32_hi64(uint32_t r0, uint32_t r1, bool& truncated) noexcept { uint64_t 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_really_inline FASTFLOAT_CONSTEXPR20
uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool& truncated) noexcept { uint64_t 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;
@ -204,15 +205,16 @@ 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_really_inline FASTFLOAT_CONSTEXPR20
limb scalar_add(limb x, limb y, bool& overflow) noexcept { limb scalar_add(limb x, limb y, bool& overflow) noexcept {
limb z; limb z;
// gcc and clang // gcc and clang
#if defined(__has_builtin) #if defined(__has_builtin)
#if __has_builtin(__builtin_add_overflow) #if __has_builtin(__builtin_add_overflow)
overflow = __builtin_add_overflow(x, y, &z); if (!cpp20_and_in_constexpr()) {
return z; overflow = __builtin_add_overflow(x, y, &z);
return z;
}
#endif #endif
#endif #endif
@ -223,7 +225,7 @@ 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_really_inline FASTFLOAT_CONSTEXPR20
limb scalar_mul(limb x, limb y, limb& carry) noexcept { limb 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__)
@ -251,7 +253,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 bool small_add_from(stackvec<size>& vec, limb y, size_t start) noexcept { inline FASTFLOAT_CONSTEXPR20
bool small_add_from(stackvec<size>& vec, limb y, size_t start) noexcept {
size_t index = start; size_t index = start;
limb carry = y; limb carry = y;
bool overflow; bool overflow;
@ -268,13 +271,15 @@ inline 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 bool small_add(stackvec<size>& vec, limb y) noexcept { fastfloat_really_inline FASTFLOAT_CONSTEXPR20
bool 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 bool small_mul(stackvec<size>& vec, limb y) noexcept { inline FASTFLOAT_CONSTEXPR20
bool small_mul(stackvec<size>& vec, 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);
@ -288,6 +293,7 @@ inline 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
bool large_add_from(stackvec<size>& x, limb_span y, size_t start) noexcept { bool large_add_from(stackvec<size>& x, limb_span y, 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.
@ -318,12 +324,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 bool large_add_from(stackvec<size>& x, limb_span y) noexcept { fastfloat_really_inline FASTFLOAT_CONSTEXPR20
bool 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
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);
@ -352,6 +360,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
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]));
@ -361,21 +370,52 @@ bool large_mul(stackvec<size>& x, limb_span y) noexcept {
return true; return true;
} }
template <typename = void>
struct pow5_tables {
static constexpr uint32_t large_step = 135;
static constexpr uint64_t small_power_of_5[] = {
1UL, 5UL, 25UL, 125UL, 625UL, 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
constexpr static limb large_power_of_5[] = {
1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL,
10482974169319127550UL, 198276706040285095UL};
#else
constexpr static limb large_power_of_5[] = {
4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U,
1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U};
#endif
};
template <typename T>
constexpr uint32_t pow5_tables<T>::large_step;
template <typename T>
constexpr uint64_t pow5_tables<T>::small_power_of_5[];
template <typename T>
constexpr limb pow5_tables<T>::large_power_of_5[];
// 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
// faster algorithms are slower for a small number of limbs. // faster algorithms are slower for a small number of limbs.
// all operations assume the big-integer is normalized. // all operations assume the big-integer is normalized.
struct bigint { struct bigint : pow5_tables<> {
// storage of the limbs, in little-endian order. // storage of the limbs, in little-endian order.
stackvec<bigint_limbs> vec; stackvec<bigint_limbs> vec;
bigint(): vec() {} FASTFLOAT_CONSTEXPR20 bigint(): vec() {}
bigint(const bigint &) = delete; bigint(const bigint &) = delete;
bigint &operator=(const bigint &) = delete; bigint &operator=(const bigint &) = delete;
bigint(bigint &&) = delete; bigint(bigint &&) = delete;
bigint &operator=(bigint &&other) = delete; bigint &operator=(bigint &&other) = delete;
bigint(uint64_t value): vec() { FASTFLOAT_CONSTEXPR20 bigint(uint64_t value): vec() {
#ifdef FASTFLOAT_64BIT_LIMB #ifdef FASTFLOAT_64BIT_LIMB
vec.push_unchecked(value); vec.push_unchecked(value);
#else #else
@ -387,7 +427,7 @@ struct bigint {
// get the high 64 bits from the vector, and if bits were truncated. // get the high 64 bits from the vector, and if bits were truncated.
// this is to get the significant digits for the float. // this is to get the significant digits for the float.
uint64_t hi64(bool& truncated) const noexcept { FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool& truncated) const noexcept {
#ifdef FASTFLOAT_64BIT_LIMB #ifdef FASTFLOAT_64BIT_LIMB
if (vec.len() == 0) { if (vec.len() == 0) {
return empty_hi64(truncated); return empty_hi64(truncated);
@ -419,7 +459,7 @@ struct bigint {
// 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.
int compare(const bigint& other) const noexcept { FASTFLOAT_CONSTEXPR20 int compare(const bigint& 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()) {
@ -440,7 +480,7 @@ struct bigint {
// shift left each limb n bits, carrying over to the new limb // shift left each limb n bits, carrying over to the new limb
// returns true if we were able to shift all the digits. // returns true if we were able to shift all the digits.
bool shl_bits(size_t n) noexcept { FASTFLOAT_CONSTEXPR20 bool shl_bits(size_t n) noexcept {
// Internally, for each item, we shift left by n, and add the previous // Internally, for each item, we shift left by n, and add the previous
// right shifted limb-bits. // right shifted limb-bits.
// For example, we transform (for u8) shifted left 2, to: // For example, we transform (for u8) shifted left 2, to:
@ -466,7 +506,7 @@ struct bigint {
} }
// move the limbs left by `n` limbs. // move the limbs left by `n` limbs.
bool shl_limbs(size_t n) noexcept { FASTFLOAT_CONSTEXPR20 bool shl_limbs(size_t n) noexcept {
FASTFLOAT_DEBUG_ASSERT(n != 0); FASTFLOAT_DEBUG_ASSERT(n != 0);
if (n + vec.len() > vec.capacity()) { if (n + vec.len() > vec.capacity()) {
return false; return false;
@ -474,7 +514,7 @@ struct bigint {
// move limbs // move limbs
limb* dst = vec.data + n; limb* dst = vec.data + n;
const limb* src = vec.data; const limb* src = vec.data;
::memmove(dst, src, sizeof(limb) * vec.len()); std::copy_backward(src, src + vec.len(), dst + vec.len());
// fill in empty limbs // fill in empty limbs
limb* first = vec.data; limb* first = vec.data;
limb* last = first + n; limb* last = first + n;
@ -487,7 +527,7 @@ struct bigint {
} }
// move the limbs left by `n` bits. // move the limbs left by `n` bits.
bool shl(size_t n) noexcept { FASTFLOAT_CONSTEXPR20 bool shl(size_t n) noexcept {
size_t rem = n % limb_bits; size_t rem = n % limb_bits;
size_t div = n / limb_bits; size_t div = n / limb_bits;
if (rem != 0) { if (rem != 0) {
@ -500,7 +540,7 @@ struct bigint {
} }
// get the number of leading zeros in the bigint. // get the number of leading zeros in the bigint.
int ctlz() const noexcept { FASTFLOAT_CONSTEXPR20 int ctlz() const noexcept {
if (vec.is_empty()) { if (vec.is_empty()) {
return 0; return 0;
} else { } else {
@ -515,45 +555,27 @@ struct bigint {
} }
// get the number of bits in the bigint. // get the number of bits in the bigint.
int bit_length() const noexcept { FASTFLOAT_CONSTEXPR20 int bit_length() const noexcept {
int lz = ctlz(); int lz = ctlz();
return int(limb_bits * vec.len()) - lz; return int(limb_bits * vec.len()) - lz;
} }
bool mul(limb y) noexcept { FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept {
return small_mul(vec, y); return small_mul(vec, y);
} }
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.
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.
bool pow5(uint32_t exp) noexcept { FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept {
// multiply by a power of 5 // multiply by a power of 5
static constexpr uint32_t large_step = 135;
static constexpr uint64_t small_power_of_5[] = {
1UL, 5UL, 25UL, 125UL, 625UL, 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
constexpr static limb large_power_of_5[] = {
1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL,
10482974169319127550UL, 198276706040285095UL};
#else
constexpr static limb large_power_of_5[] = {
4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U,
1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U};
#endif
size_t large_length = sizeof(large_power_of_5) / sizeof(limb); size_t large_length = sizeof(large_power_of_5) / sizeof(limb);
limb_span large = limb_span(large_power_of_5, large_length); limb_span large = limb_span(large_power_of_5, large_length);
while (exp >= large_step) { while (exp >= large_step) {
@ -572,14 +594,19 @@ struct bigint {
exp -= small_step; exp -= small_step;
} }
if (exp != 0) { if (exp != 0) {
FASTFLOAT_TRY(small_mul(vec, limb(small_power_of_5[exp]))); // Work around clang bug https://godbolt.org/z/zedh7rrhc
// This is similar to https://github.com/llvm/llvm-project/issues/47746,
// except the workaround described there don't work here
FASTFLOAT_TRY(
small_mul(vec, limb(((void)small_power_of_5[0], small_power_of_5[exp])))
);
} }
return true; return true;
} }
// multiply as if by 10 raised to a power. // multiply as if by 10 raised to a power.
bool pow10(uint32_t exp) noexcept { FASTFLOAT_CONSTEXPR20 bool pow10(uint32_t exp) noexcept {
FASTFLOAT_TRY(pow5(exp)); FASTFLOAT_TRY(pow5(exp));
return pow2(exp); return pow2(exp);
} }

View File

@ -0,0 +1,40 @@
#ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H
#define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H
#ifdef __has_include
#if __has_include(<version>)
#include <version>
#endif
#endif
// Testing for https://wg21.link/N3652, adopted in C++14
#if __cpp_constexpr >= 201304
#define FASTFLOAT_CONSTEXPR14 constexpr
#else
#define FASTFLOAT_CONSTEXPR14
#endif
#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L
#define FASTFLOAT_HAS_BIT_CAST 1
#else
#define FASTFLOAT_HAS_BIT_CAST 0
#endif
#if defined(__cpp_lib_is_constant_evaluated) && __cpp_lib_is_constant_evaluated >= 201811L
#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1
#else
#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0
#endif
// Testing for relevant C++20 constexpr library features
#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED \
&& FASTFLOAT_HAS_BIT_CAST \
&& __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/
#define FASTFLOAT_CONSTEXPR20 constexpr
#define FASTFLOAT_IS_CONSTEXPR 1
#else
#define FASTFLOAT_CONSTEXPR20
#define FASTFLOAT_IS_CONSTEXPR 0
#endif
#endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H

View File

@ -17,7 +17,7 @@ namespace fast_float {
// low part corresponding to the least significant bits. // low part corresponding to the least significant bits.
// //
template <int bit_precision> template <int bit_precision>
fastfloat_really_inline fastfloat_really_inline FASTFLOAT_CONSTEXPR20
value128 compute_product_approximation(int64_t q, uint64_t w) { value128 compute_product_approximation(int64_t q, uint64_t w) {
const int index = 2 * int(q - powers::smallest_power_of_five); const int 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 because
@ -48,9 +48,9 @@ namespace detail {
* where * where
* p = log(5**q)/log(2) = q * log(5)/log(2) * p = log(5**q)/log(2) = q * log(5)/log(2)
* *
* For negative values of q in (-400,0), we have that * For negative values of q in (-400,0), we have that
* f = (((152170 + 65536) * q ) >> 16); * f = (((152170 + 65536) * q ) >> 16);
* is equal to * is equal to
* -ceil(p) + q * -ceil(p) + q
* where * where
* p = log(5**-q)/log(2) = -q * log(5)/log(2) * p = log(5**-q)/log(2) = -q * log(5)/log(2)
@ -63,7 +63,7 @@ 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_really_inline FASTFLOAT_CONSTEXPR14
adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { adjusted_mantissa 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;
@ -76,7 +76,7 @@ adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept
// 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_really_inline FASTFLOAT_CONSTEXPR20
adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept { adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept {
int lz = leading_zeroes(w); int lz = leading_zeroes(w);
w <<= lz; w <<= lz;
@ -90,7 +90,7 @@ adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept {
// return an adjusted_mantissa with a negative power of 2: the caller should recompute // return an adjusted_mantissa with a negative power of 2: the caller should recompute
// in such cases. // in such cases.
template <typename binary> template <typename binary>
fastfloat_really_inline fastfloat_really_inline FASTFLOAT_CONSTEXPR20
adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { adjusted_mantissa 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())) {
@ -117,16 +117,11 @@ adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept {
// 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);
if(product.low == 0xFFFFFFFFFFFFFFFF) { // could guard it further // The computed 'product' is always sufficient.
// In some very rare cases, this could happen, in which case we might need a more accurate // Mathematical proof:
// computation that what we can provide cheaply. This is very, very unlikely. // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to appear)
// // See script/mushtak_lemire.py
const bool inside_safe_exponent = (q >= -27) && (q <= 55); // always good because 5**q <2**128 when q>=0,
// and otherwise, for q<0, we have 5**-q<2**64 and the 128-bit reciprocal allows for exact computation.
if(!inside_safe_exponent) {
return compute_error_scaled<binary>(q, product.high, lz);
}
}
// The "compute_product_approximation" function can be slightly slower than a branchless approach: // The "compute_product_approximation" function can be slightly slower than a branchless approach:
// value128 product = compute_product(q, w); // value128 product = compute_product(q, w);
// but in practice, we can win big with the compute_product_approximation if its additional branch // but in practice, we can win big with the compute_product_approximation if its additional branch

View File

@ -23,7 +23,9 @@ constexpr static uint64_t powers_of_ten_uint64[] = {
// 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.
fastfloat_really_inline int32_t scientific_exponent(parsed_number_string& num) noexcept { template <typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR14
int32_t scientific_exponent(parsed_number_string_t<UC> & num) noexcept {
uint64_t mantissa = num.mantissa; uint64_t mantissa = num.mantissa;
int32_t exponent = int32_t(num.exponent); int32_t exponent = int32_t(num.exponent);
while (mantissa >= 10000) { while (mantissa >= 10000) {
@ -43,41 +45,30 @@ fastfloat_really_inline int32_t scientific_exponent(parsed_number_string& num) n
// 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 adjusted_mantissa to_extended(T value) noexcept { fastfloat_really_inline FASTFLOAT_CONSTEXPR20
adjusted_mantissa to_extended(T value) noexcept {
using equiv_uint = typename binary_format<T>::equiv_uint;
constexpr equiv_uint exponent_mask = binary_format<T>::exponent_mask();
constexpr equiv_uint mantissa_mask = binary_format<T>::mantissa_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();
if (std::is_same<T, float>::value) { equiv_uint bits;
constexpr uint32_t exponent_mask = 0x7F800000; #if FASTFLOAT_HAS_BIT_CAST
constexpr uint32_t mantissa_mask = 0x007FFFFF; bits = std::bit_cast<equiv_uint>(value);
constexpr uint64_t hidden_bit_mask = 0x00800000; #else
uint32_t bits; ::memcpy(&bits, &value, sizeof(T));
::memcpy(&bits, &value, sizeof(T)); #endif
if ((bits & exponent_mask) == 0) { if ((bits & exponent_mask) == 0) {
// denormal // denormal
am.power2 = 1 - bias; am.power2 = 1 - bias;
am.mantissa = bits & mantissa_mask; am.mantissa = bits & mantissa_mask;
} else {
// normal
am.power2 = int32_t((bits & exponent_mask) >> binary_format<T>::mantissa_explicit_bits());
am.power2 -= bias;
am.mantissa = (bits & mantissa_mask) | hidden_bit_mask;
}
} else { } else {
constexpr uint64_t exponent_mask = 0x7FF0000000000000; // normal
constexpr uint64_t mantissa_mask = 0x000FFFFFFFFFFFFF; am.power2 = int32_t((bits & exponent_mask) >> binary_format<T>::mantissa_explicit_bits());
constexpr uint64_t hidden_bit_mask = 0x0010000000000000; am.power2 -= bias;
uint64_t bits; am.mantissa = (bits & mantissa_mask) | hidden_bit_mask;
::memcpy(&bits, &value, sizeof(T));
if ((bits & exponent_mask) == 0) {
// denormal
am.power2 = 1 - bias;
am.mantissa = bits & mantissa_mask;
} else {
// normal
am.power2 = int32_t((bits & exponent_mask) >> binary_format<T>::mantissa_explicit_bits());
am.power2 -= bias;
am.mantissa = (bits & mantissa_mask) | hidden_bit_mask;
}
} }
return am; return am;
@ -87,7 +78,8 @@ fastfloat_really_inline 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 adjusted_mantissa to_extended_halfway(T value) noexcept { fastfloat_really_inline FASTFLOAT_CONSTEXPR20
adjusted_mantissa 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;
@ -97,12 +89,13 @@ fastfloat_really_inline 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 void round(adjusted_mantissa& am, callback cb) noexcept { fastfloat_really_inline FASTFLOAT_CONSTEXPR14
void round(adjusted_mantissa& am, 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(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;
@ -126,23 +119,19 @@ fastfloat_really_inline void round(adjusted_mantissa& am, callback cb) noexcept
} }
template <typename callback> template <typename callback>
fastfloat_really_inline fastfloat_really_inline FASTFLOAT_CONSTEXPR14
void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept { void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept {
uint64_t mask; const uint64_t mask
uint64_t halfway; = (shift == 64)
if (shift == 64) { ? UINT64_MAX
mask = UINT64_MAX; : (uint64_t(1) << shift) - 1;
} else { const uint64_t halfway
mask = (uint64_t(1) << shift) - 1; = (shift == 0)
} ? 0
if (shift == 0) { : uint64_t(1) << (shift - 1);
halfway = 0;
} else {
halfway = uint64_t(1) << (shift - 1);
}
uint64_t truncated_bits = am.mantissa & mask; uint64_t truncated_bits = am.mantissa & mask;
uint64_t is_above = truncated_bits > halfway; bool is_above = truncated_bits > halfway;
uint64_t is_halfway = truncated_bits == halfway; bool is_halfway = truncated_bits == halfway;
// shift digits into position // shift digits into position
if (shift == 64) { if (shift == 64) {
@ -156,7 +145,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 void round_down(adjusted_mantissa& am, int32_t shift) noexcept { fastfloat_really_inline FASTFLOAT_CONSTEXPR14
void round_down(adjusted_mantissa& am, int32_t shift) noexcept {
if (shift == 64) { if (shift == 64) {
am.mantissa = 0; am.mantissa = 0;
} else { } else {
@ -164,18 +154,19 @@ fastfloat_really_inline void round_down(adjusted_mantissa& am, int32_t shift) no
} }
am.power2 += shift; am.power2 += shift;
} }
template <typename UC>
fastfloat_really_inline void skip_zeros(const char*& first, const char* last) noexcept { fastfloat_really_inline FASTFLOAT_CONSTEXPR20
void skip_zeros(UC const * & first, UC const * last) noexcept {
uint64_t val; uint64_t val;
while (std::distance(first, last) >= 8) { 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 != 0x3030303030303030) { if (val != int_cmp_zeros<UC>()) {
break; break;
} }
first += 8; first += int_cmp_len<UC>();
} }
while (first != last) { while (first != last) {
if (*first != '0') { if (*first != UC('0')) {
break; break;
} }
first++; first++;
@ -184,52 +175,58 @@ fastfloat_really_inline void skip_zeros(const char*& first, const char* last) no
// 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.
fastfloat_really_inline bool is_truncated(const char* first, const char* last) noexcept { template <typename UC>
fastfloat_really_inline FASTFLOAT_CONSTEXPR20
bool 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 (std::distance(first, last) >= 8) { 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 != 0x3030303030303030) { if (val != int_cmp_zeros<UC>()) {
return true; return true;
} }
first += 8; first += int_cmp_len<UC>();
} }
while (first != last) { while (first != last) {
if (*first != '0') { if (*first != UC('0')) {
return true; return true;
} }
first++; ++first;
} }
return false; return false;
} }
template <typename UC>
fastfloat_really_inline bool is_truncated(byte_span s) noexcept { fastfloat_really_inline FASTFLOAT_CONSTEXPR20
bool is_truncated(span<UC> s) noexcept {
return is_truncated(s.ptr, s.ptr + s.len()); return is_truncated(s.ptr, s.ptr + s.len());
} }
fastfloat_really_inline template <typename UC>
void parse_eight_digits(const char*& p, limb& value, size_t& counter, size_t& count) noexcept { fastfloat_really_inline FASTFLOAT_CONSTEXPR20
void 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;
count += 8; count += 8;
} }
fastfloat_really_inline template <typename UC>
void parse_one_digit(const char*& p, limb& value, size_t& counter, size_t& count) noexcept { fastfloat_really_inline FASTFLOAT_CONSTEXPR14
value = value * 10 + limb(*p - '0'); void parse_one_digit(UC const *& p, limb& value, size_t& counter, size_t& count) noexcept {
value = value * 10 + limb(*p - UC('0'));
p++; p++;
counter++; counter++;
count++; count++;
} }
fastfloat_really_inline fastfloat_really_inline FASTFLOAT_CONSTEXPR20
void add_native(bigint& big, limb power, limb value) noexcept { void add_native(bigint& big, limb power, limb value) noexcept {
big.mul(power); big.mul(power);
big.add(value); big.add(value);
} }
fastfloat_really_inline void round_up_bigint(bigint& big, size_t& count) noexcept { fastfloat_really_inline FASTFLOAT_CONSTEXPR20
void 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);
@ -237,7 +234,9 @@ fastfloat_really_inline void round_up_bigint(bigint& big, size_t& count) noexcep
} }
// parse the significant digits into a big integer // parse the significant digits into a big integer
inline void parse_mantissa(bigint& result, parsed_number_string& num, size_t max_digits, size_t& digits) noexcept { template <typename UC>
inline FASTFLOAT_CONSTEXPR20
void parse_mantissa(bigint& result, parsed_number_string_t<UC>& num, size_t max_digits, size_t& digits) noexcept {
// try to minimize the number of big integer and scalar multiplication. // try to minimize the number of big integer and scalar multiplication.
// therefore, try to parse 8 digits at a time, and multiply by the largest // therefore, try to parse 8 digits at a time, and multiply by the largest
// scalar value (9 or 19 digits) for each step. // scalar value (9 or 19 digits) for each step.
@ -251,8 +250,8 @@ inline void parse_mantissa(bigint& result, parsed_number_string& num, size_t max
#endif #endif
// process all integer digits. // process all integer digits.
const char* p = num.integer.ptr; UC const * p = num.integer.ptr;
const char* pend = p + num.integer.len(); UC const * pend = p + num.integer.len();
skip_zeros(p, pend); skip_zeros(p, pend);
// process all digits, in increments of step per loop // process all digits, in increments of step per loop
while (p != pend) { while (p != pend) {
@ -317,7 +316,8 @@ inline void parse_mantissa(bigint& result, parsed_number_string& num, size_t max
} }
template <typename T> template <typename T>
inline adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcept { inline FASTFLOAT_CONSTEXPR20
adjusted_mantissa 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;
@ -340,7 +340,8 @@ inline adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent)
// 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 adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int32_t exponent) noexcept { inline FASTFLOAT_CONSTEXPR20
adjusted_mantissa negative_digit_comp(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;
@ -399,8 +400,9 @@ inline adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa
// `b` as a big-integer type, scaled to the same binary exponent as // `b` as a big-integer type, scaled to the same binary exponent as
// 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> template <typename T, typename UC>
inline adjusted_mantissa digit_comp(parsed_number_string& num, adjusted_mantissa am) noexcept { inline FASTFLOAT_CONSTEXPR20
adjusted_mantissa 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;

View File

@ -3,6 +3,8 @@
#include <system_error> #include <system_error>
#include "constexpr_feature_detect.h"
namespace fast_float { namespace fast_float {
enum chars_format { enum chars_format {
scientific = 1<<0, scientific = 1<<0,
@ -11,22 +13,25 @@ enum chars_format {
general = fixed | scientific general = fixed | scientific
}; };
template <typename UC>
struct from_chars_result { struct from_chars_result_t {
const char *ptr; UC const * ptr;
std::errc ec; std::errc ec;
}; };
using from_chars_result = from_chars_result_t<char>;
struct parse_options { template <typename UC>
constexpr explicit parse_options(chars_format fmt = chars_format::general, struct parse_options_t {
char dot = '.') constexpr explicit parse_options_t(chars_format fmt = chars_format::general,
UC dot = UC('.'))
: format(fmt), decimal_point(dot) {} : format(fmt), decimal_point(dot) {}
/** Which number formats are accepted */ /** Which number formats are accepted */
chars_format format; chars_format format;
/** The character used as decimal point */ /** The character used as decimal point */
char decimal_point; UC decimal_point;
}; };
using parse_options = parse_options_t<char>;
/** /**
* 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 parses floating-point numbers expecting
@ -44,20 +49,22 @@ struct parse_options {
* 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 optional last argument of
* the type `fast_float::chars_format`. It is a bitset value: we check whether * 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 * `fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set
* to determine whether we allowe the fixed point and scientific notation respectively. * 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 default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`.
*/ */
template<typename T> template<typename T, typename UC = char>
from_chars_result from_chars(const char *first, const char *last, FASTFLOAT_CONSTEXPR20
from_chars_result_t<UC> from_chars(UC const * first, UC const * last,
T &value, chars_format fmt = chars_format::general) noexcept; 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.
*/ */
template<typename T> template<typename T, typename UC = char>
from_chars_result from_chars_advanced(const char *first, const char *last, FASTFLOAT_CONSTEXPR20
T &value, parse_options options) noexcept; from_chars_result_t<UC> from_chars_advanced(UC const * first, UC const * last,
T &value, parse_options_t<UC> options) noexcept;
} } // namespace fast_float
#include "parse_number.h" #include "parse_number.h"
#endif // FASTFLOAT_FAST_FLOAT_H #endif // FASTFLOAT_FAST_FLOAT_H

File diff suppressed because it is too large Load Diff

View File

@ -5,18 +5,22 @@
#include <cstdint> #include <cstdint>
#include <cassert> #include <cassert>
#include <cstring> #include <cstring>
#include <type_traits>
#if FASTFLOAT_HAS_BIT_CAST
#include <bit>
#endif
#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \ #if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \
|| defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) \ || defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) \
|| defined(__MINGW64__) \ || defined(__MINGW64__) \
|| defined(__s390x__) \ || defined(__s390x__) \
|| (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || defined(__PPC64LE__)) \ || (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || defined(__PPC64LE__)) )
|| defined(__EMSCRIPTEN__)) #define FASTFLOAT_64BIT 1
#define FASTFLOAT_64BIT
#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \ #elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \
|| defined(__arm__) || defined(_M_ARM) \ || defined(__arm__) || defined(_M_ARM) || defined(__ppc__) \
|| defined(__MINGW32__)) || defined(__MINGW32__) || defined(__EMSCRIPTEN__))
#define FASTFLOAT_32BIT #define FASTFLOAT_32BIT 1
#else #else
// Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow.
// We can never tell the register width, but the SIZE_MAX is a good approximation. // We can never tell the register width, but the SIZE_MAX is a good approximation.
@ -24,9 +28,9 @@
#if SIZE_MAX == 0xffff #if SIZE_MAX == 0xffff
#error Unknown platform (16-bit, unsupported) #error Unknown platform (16-bit, unsupported)
#elif SIZE_MAX == 0xffffffff #elif SIZE_MAX == 0xffffffff
#define FASTFLOAT_32BIT #define FASTFLOAT_32BIT 1
#elif SIZE_MAX == 0xffffffffffffffff #elif SIZE_MAX == 0xffffffffffffffff
#define FASTFLOAT_64BIT #define FASTFLOAT_64BIT 1
#else #else
#error Unknown platform (not 32-bit, not 64-bit?) #error Unknown platform (not 32-bit, not 64-bit?)
#endif #endif
@ -40,7 +44,9 @@
#define FASTFLOAT_VISUAL_STUDIO 1 #define FASTFLOAT_VISUAL_STUDIO 1
#endif #endif
#ifdef _WIN32 #if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__
#define FASTFLOAT_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#elif defined _WIN32
#define FASTFLOAT_IS_BIG_ENDIAN 0 #define FASTFLOAT_IS_BIG_ENDIAN 0
#else #else
#if defined(__APPLE__) || defined(__FreeBSD__) #if defined(__APPLE__) || defined(__FreeBSD__)
@ -48,7 +54,11 @@
#elif defined(sun) || defined(__sun) #elif defined(sun) || defined(__sun)
#include <sys/byteorder.h> #include <sys/byteorder.h>
#else #else
#ifdef __has_include
#if __has_include(<endian.h>)
#include <endian.h> #include <endian.h>
#endif //__has_include(<endian.h>)
#endif //__has_include
#endif #endif
# #
#ifndef __BYTE_ORDER__ #ifndef __BYTE_ORDER__
@ -75,12 +85,11 @@
#endif #endif
#ifndef FASTFLOAT_ASSERT #ifndef FASTFLOAT_ASSERT
#define FASTFLOAT_ASSERT(x) { if (!(x)) abort(); } #define FASTFLOAT_ASSERT(x) { ((void)(x)); }
#endif #endif
#ifndef FASTFLOAT_DEBUG_ASSERT #ifndef FASTFLOAT_DEBUG_ASSERT
#include <cassert> #define FASTFLOAT_DEBUG_ASSERT(x) { ((void)(x)); }
#define FASTFLOAT_DEBUG_ASSERT(x) assert(x)
#endif #endif
// rust style `try!()` macro, or `?` operator // rust style `try!()` macro, or `?` operator
@ -88,12 +97,21 @@
namespace fast_float { namespace fast_float {
fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() {
#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED
return std::is_constant_evaluated();
#else
return false;
#endif
}
// Compares two ASCII strings in a case insensitive manner. // Compares two ASCII strings in a case insensitive manner.
inline bool fastfloat_strncasecmp(const char *input1, const char *input2, template <typename UC>
size_t length) { inline FASTFLOAT_CONSTEXPR14 bool
fastfloat_strncasecmp(UC const * input1, UC const * input2, size_t length) {
char running_diff{0}; char running_diff{0};
for (size_t i = 0; i < length; i++) { for (size_t i = 0; i < length; ++i) {
running_diff |= (input1[i] ^ input2[i]); running_diff |= (char(input1[i]) ^ char(input2[i]));
} }
return (running_diff == 0) || (running_diff == 32); return (running_diff == 0) || (running_diff == 32);
} }
@ -107,14 +125,14 @@ template <typename T>
struct span { struct span {
const T* ptr; const T* ptr;
size_t length; size_t length;
span(const T* _ptr, size_t _length) : ptr(_ptr), length(_length) {} constexpr span(const T* _ptr, size_t _length) : ptr(_ptr), length(_length) {}
span() : ptr(nullptr), length(0) {} constexpr span() : ptr(nullptr), length(0) {}
constexpr size_t len() const noexcept { constexpr size_t len() const noexcept {
return length; return length;
} }
const T& operator[](size_t index) const noexcept { FASTFLOAT_CONSTEXPR14 const T& operator[](size_t index) const noexcept {
FASTFLOAT_DEBUG_ASSERT(index < length); FASTFLOAT_DEBUG_ASSERT(index < length);
return ptr[index]; return ptr[index];
} }
@ -123,13 +141,31 @@ struct span {
struct value128 { struct value128 {
uint64_t low; uint64_t low;
uint64_t high; uint64_t high;
value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} constexpr value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {}
value128() : low(0), high(0) {} constexpr value128() : low(0), high(0) {}
}; };
/* Helper C++11 constexpr generic implementation of leading_zeroes */
fastfloat_really_inline constexpr
int leading_zeroes_generic(uint64_t input_num, int last_bit = 0) {
return (
((input_num & uint64_t(0xffffffff00000000)) && (input_num >>= 32, last_bit |= 32)),
((input_num & uint64_t( 0xffff0000)) && (input_num >>= 16, last_bit |= 16)),
((input_num & uint64_t( 0xff00)) && (input_num >>= 8, last_bit |= 8)),
((input_num & uint64_t( 0xf0)) && (input_num >>= 4, last_bit |= 4)),
((input_num & uint64_t( 0xc)) && (input_num >>= 2, last_bit |= 2)),
((input_num & uint64_t( 0x2)) && (input_num >>= 1, last_bit |= 1)),
63 - last_bit
);
}
/* result might be undefined when input_num is zero */ /* result might be undefined when input_num is zero */
fastfloat_really_inline int leading_zeroes(uint64_t input_num) { fastfloat_really_inline FASTFLOAT_CONSTEXPR20
int leading_zeroes(uint64_t input_num) {
assert(input_num > 0); assert(input_num > 0);
if (cpp20_and_in_constexpr()) {
return leading_zeroes_generic(input_num);
}
#ifdef FASTFLOAT_VISUAL_STUDIO #ifdef FASTFLOAT_VISUAL_STUDIO
#if defined(_M_X64) || defined(_M_ARM64) #if defined(_M_X64) || defined(_M_ARM64)
unsigned long leading_zero = 0; unsigned long leading_zero = 0;
@ -138,31 +174,20 @@ fastfloat_really_inline int leading_zeroes(uint64_t input_num) {
_BitScanReverse64(&leading_zero, input_num); _BitScanReverse64(&leading_zero, input_num);
return (int)(63 - leading_zero); return (int)(63 - leading_zero);
#else #else
int last_bit = 0; return leading_zeroes_generic(input_num);
if(input_num & uint64_t(0xffffffff00000000)) input_num >>= 32, last_bit |= 32;
if(input_num & uint64_t( 0xffff0000)) input_num >>= 16, last_bit |= 16;
if(input_num & uint64_t( 0xff00)) input_num >>= 8, last_bit |= 8;
if(input_num & uint64_t( 0xf0)) input_num >>= 4, last_bit |= 4;
if(input_num & uint64_t( 0xc)) input_num >>= 2, last_bit |= 2;
if(input_num & uint64_t( 0x2)) input_num >>= 1, last_bit |= 1;
return 63 - last_bit;
#endif #endif
#else #else
return __builtin_clzll(input_num); return __builtin_clzll(input_num);
#endif #endif
} }
#ifdef FASTFLOAT_32BIT
// slow emulation routine for 32-bit // slow emulation routine for 32-bit
fastfloat_really_inline uint64_t emulu(uint32_t x, uint32_t y) { fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) {
return x * (uint64_t)y; return x * (uint64_t)y;
} }
// slow emulation routine for 32-bit fastfloat_really_inline FASTFLOAT_CONSTEXPR14
#if !defined(__MINGW64__) uint64_t umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) {
fastfloat_really_inline uint64_t _umul128(uint64_t ab, uint64_t cd,
uint64_t *hi) {
uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd);
uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd);
uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32));
@ -172,17 +197,32 @@ fastfloat_really_inline uint64_t _umul128(uint64_t ab, uint64_t cd,
(adbc_carry << 32) + !!(lo < bd); (adbc_carry << 32) + !!(lo < bd);
return lo; return lo;
} }
#ifdef FASTFLOAT_32BIT
// slow emulation routine for 32-bit
#if !defined(__MINGW64__)
fastfloat_really_inline FASTFLOAT_CONSTEXPR14
uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) {
return umul128_generic(ab, cd, hi);
}
#endif // !__MINGW64__ #endif // !__MINGW64__
#endif // FASTFLOAT_32BIT #endif // FASTFLOAT_32BIT
// compute 64-bit a*b // compute 64-bit a*b
fastfloat_really_inline value128 full_multiplication(uint64_t a, fastfloat_really_inline FASTFLOAT_CONSTEXPR20
uint64_t b) { value128 full_multiplication(uint64_t a, uint64_t b) {
if (cpp20_and_in_constexpr()) {
value128 answer;
answer.low = umul128_generic(a, b, &answer.high);
return answer;
}
value128 answer; value128 answer;
#ifdef _M_ARM64 #if defined(_M_ARM64) && !defined(__MINGW32__)
// ARM64 has native support for 64-bit multiplications, no need to emulate // ARM64 has native support for 64-bit multiplications, no need to emulate
// But MinGW on ARM64 doesn't have native support for 64-bit multiplications
answer.high = __umulh(a, b); answer.high = __umulh(a, b);
answer.low = a * b; answer.low = a * b;
#elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__)) #elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__))
@ -192,7 +232,7 @@ fastfloat_really_inline value128 full_multiplication(uint64_t a,
answer.low = uint64_t(r); answer.low = uint64_t(r);
answer.high = uint64_t(r >> 64); answer.high = uint64_t(r >> 64);
#else #else
#error Not implemented answer.low = umul128_generic(a, b, &answer.high);
#endif #endif
return answer; return answer;
} }
@ -201,10 +241,10 @@ struct adjusted_mantissa {
uint64_t mantissa{0}; uint64_t mantissa{0};
int32_t power2{0}; // a negative value indicates an invalid result int32_t power2{0}; // a negative value indicates an invalid result
adjusted_mantissa() = default; adjusted_mantissa() = default;
bool operator==(const adjusted_mantissa &o) const { constexpr bool operator==(const adjusted_mantissa &o) const {
return mantissa == o.mantissa && power2 == o.power2; return mantissa == o.mantissa && power2 == o.power2;
} }
bool operator!=(const adjusted_mantissa &o) const { constexpr bool operator!=(const adjusted_mantissa &o) const {
return mantissa != o.mantissa || power2 != o.power2; return mantissa != o.mantissa || power2 != o.power2;
} }
}; };
@ -215,25 +255,91 @@ constexpr static int32_t invalid_am_bias = -0x8000;
constexpr static double powers_of_ten_double[] = { constexpr static double powers_of_ten_double[] = {
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
constexpr static float powers_of_ten_float[] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, constexpr static float powers_of_ten_float[] = {1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f,
1e6, 1e7, 1e8, 1e9, 1e10}; 1e6f, 1e7f, 1e8f, 1e9f, 1e10f};
// used for max_mantissa_double and max_mantissa_float
constexpr uint64_t constant_55555 = 5 * 5 * 5 * 5 * 5;
// Largest integer value v so that (5**index * v) <= 1<<53.
// 0x10000000000000 == 1 << 53
constexpr static uint64_t max_mantissa_double[] = {
0x10000000000000,
0x10000000000000 / 5,
0x10000000000000 / (5 * 5),
0x10000000000000 / (5 * 5 * 5),
0x10000000000000 / (5 * 5 * 5 * 5),
0x10000000000000 / (constant_55555),
0x10000000000000 / (constant_55555 * 5),
0x10000000000000 / (constant_55555 * 5 * 5),
0x10000000000000 / (constant_55555 * 5 * 5 * 5),
0x10000000000000 / (constant_55555 * 5 * 5 * 5 * 5),
0x10000000000000 / (constant_55555 * constant_55555),
0x10000000000000 / (constant_55555 * constant_55555 * 5),
0x10000000000000 / (constant_55555 * constant_55555 * 5 * 5),
0x10000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5),
0x10000000000000 / (constant_55555 * constant_55555 * constant_55555),
0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5),
0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5),
0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5),
0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5),
0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555),
0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5),
0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5),
0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5),
0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5)};
// Largest integer value v so that (5**index * v) <= 1<<24.
// 0x1000000 == 1<<24
constexpr static uint64_t max_mantissa_float[] = {
0x1000000,
0x1000000 / 5,
0x1000000 / (5 * 5),
0x1000000 / (5 * 5 * 5),
0x1000000 / (5 * 5 * 5 * 5),
0x1000000 / (constant_55555),
0x1000000 / (constant_55555 * 5),
0x1000000 / (constant_55555 * 5 * 5),
0x1000000 / (constant_55555 * 5 * 5 * 5),
0x1000000 / (constant_55555 * 5 * 5 * 5 * 5),
0x1000000 / (constant_55555 * constant_55555),
0x1000000 / (constant_55555 * constant_55555 * 5)};
template <typename T> struct binary_format { template <typename T> struct binary_format {
using equiv_uint = typename std::conditional<sizeof(T) == 4, uint32_t, uint64_t>::type;
static inline constexpr int mantissa_explicit_bits(); static inline constexpr int mantissa_explicit_bits();
static inline constexpr int minimum_exponent(); static inline constexpr int minimum_exponent();
static inline constexpr int infinite_power(); static inline constexpr int infinite_power();
static inline constexpr int sign_index(); static inline constexpr int sign_index();
static inline constexpr int min_exponent_fast_path(); static inline constexpr int min_exponent_fast_path(); // used when fegetround() == FE_TONEAREST
static inline constexpr int max_exponent_fast_path(); static inline constexpr int max_exponent_fast_path();
static inline constexpr int max_exponent_round_to_even(); static inline constexpr int max_exponent_round_to_even();
static inline constexpr int min_exponent_round_to_even(); static inline constexpr int min_exponent_round_to_even();
static inline constexpr uint64_t max_mantissa_fast_path(); static inline constexpr uint64_t max_mantissa_fast_path(int64_t power);
static inline constexpr uint64_t max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST
static inline constexpr int largest_power_of_ten(); static inline constexpr int largest_power_of_ten();
static inline constexpr int smallest_power_of_ten(); static inline constexpr int smallest_power_of_ten();
static inline constexpr T exact_power_of_ten(int64_t power); static inline constexpr T exact_power_of_ten(int64_t power);
static inline constexpr size_t max_digits(); static inline constexpr size_t max_digits();
static inline constexpr equiv_uint exponent_mask();
static inline constexpr equiv_uint mantissa_mask();
static inline constexpr equiv_uint hidden_bit_mask();
}; };
template <> inline constexpr int binary_format<double>::min_exponent_fast_path() {
#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
return 0;
#else
return -22;
#endif
}
template <> inline constexpr int binary_format<float>::min_exponent_fast_path() {
#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
return 0;
#else
return -10;
#endif
}
template <> inline constexpr int binary_format<double>::mantissa_explicit_bits() { template <> inline constexpr int binary_format<double>::mantissa_explicit_bits() {
return 52; return 52;
} }
@ -274,34 +380,30 @@ template <> inline constexpr int binary_format<float>::infinite_power() {
template <> inline constexpr int binary_format<double>::sign_index() { return 63; } template <> inline constexpr int binary_format<double>::sign_index() { return 63; }
template <> inline constexpr int binary_format<float>::sign_index() { return 31; } template <> inline constexpr int binary_format<float>::sign_index() { return 31; }
template <> inline constexpr int binary_format<double>::min_exponent_fast_path() {
#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
return 0;
#else
return -22;
#endif
}
template <> inline constexpr int binary_format<float>::min_exponent_fast_path() {
#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
return 0;
#else
return -10;
#endif
}
template <> inline constexpr int binary_format<double>::max_exponent_fast_path() { template <> inline constexpr int binary_format<double>::max_exponent_fast_path() {
return 22; return 22;
} }
template <> inline constexpr int binary_format<float>::max_exponent_fast_path() { template <> inline constexpr int binary_format<float>::max_exponent_fast_path() {
return 10; return 10;
} }
template <> inline constexpr uint64_t binary_format<double>::max_mantissa_fast_path() { template <> inline constexpr uint64_t binary_format<double>::max_mantissa_fast_path() {
return uint64_t(2) << mantissa_explicit_bits(); return uint64_t(2) << mantissa_explicit_bits();
} }
template <> inline constexpr uint64_t binary_format<double>::max_mantissa_fast_path(int64_t power) {
// caller is responsible to ensure that
// power >= 0 && power <= 22
//
return max_mantissa_double[power];
}
template <> inline constexpr uint64_t binary_format<float>::max_mantissa_fast_path() { template <> inline constexpr uint64_t binary_format<float>::max_mantissa_fast_path() {
return uint64_t(2) << mantissa_explicit_bits(); return uint64_t(2) << mantissa_explicit_bits();
} }
template <> inline constexpr uint64_t binary_format<float>::max_mantissa_fast_path(int64_t power) {
// caller is responsible to ensure that
// power >= 0 && power <= 10
//
return max_mantissa_float[power];
}
template <> template <>
inline constexpr double binary_format<double>::exact_power_of_ten(int64_t power) { inline constexpr double binary_format<double>::exact_power_of_ten(int64_t power) {
@ -339,24 +441,131 @@ template <> inline constexpr size_t binary_format<float>::max_digits() {
return 114; return 114;
} }
template <> inline constexpr binary_format<float>::equiv_uint
binary_format<float>::exponent_mask() {
return 0x7F800000;
}
template <> inline constexpr binary_format<double>::equiv_uint
binary_format<double>::exponent_mask() {
return 0x7FF0000000000000;
}
template <> inline constexpr binary_format<float>::equiv_uint
binary_format<float>::mantissa_mask() {
return 0x007FFFFF;
}
template <> inline constexpr binary_format<double>::equiv_uint
binary_format<double>::mantissa_mask() {
return 0x000FFFFFFFFFFFFF;
}
template <> inline constexpr binary_format<float>::equiv_uint
binary_format<float>::hidden_bit_mask() {
return 0x00800000;
}
template <> inline constexpr binary_format<double>::equiv_uint
binary_format<double>::hidden_bit_mask() {
return 0x0010000000000000;
}
template<typename T> template<typename T>
fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &value) { fastfloat_really_inline FASTFLOAT_CONSTEXPR20
uint64_t word = am.mantissa; void to_float(bool negative, adjusted_mantissa am, T &value) {
word |= uint64_t(am.power2) << binary_format<T>::mantissa_explicit_bits(); using uint = typename binary_format<T>::equiv_uint;
word = negative uint word = (uint)am.mantissa;
? word | (uint64_t(1) << binary_format<T>::sign_index()) : word; word |= uint(am.power2) << binary_format<T>::mantissa_explicit_bits();
#if FASTFLOAT_IS_BIG_ENDIAN == 1 word |= uint(negative) << binary_format<T>::sign_index();
if (std::is_same<T, float>::value) { #if FASTFLOAT_HAS_BIT_CAST
::memcpy(&value, (char *)&word + 4, sizeof(T)); // extract value at offset 4-7 if float on big-endian value = std::bit_cast<T>(word);
} else {
::memcpy(&value, &word, sizeof(T));
}
#else #else
// For little-endian systems: ::memcpy(&value, &word, sizeof(T));
::memcpy(&value, &word, sizeof(T));
#endif #endif
} }
#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default
template <typename = void>
struct space_lut {
static constexpr bool value[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
};
template <typename T>
constexpr bool space_lut<T>::value[];
inline constexpr bool is_space(uint8_t c) { return space_lut<>::value[c]; }
#endif
template<typename UC>
static constexpr uint64_t int_cmp_zeros()
{
static_assert((sizeof(UC) == 1) || (sizeof(UC) == 2) || (sizeof(UC) == 4), "Unsupported character size");
return (sizeof(UC) == 1) ? 0x3030303030303030 : (sizeof(UC) == 2) ? (uint64_t(UC('0')) << 48 | uint64_t(UC('0')) << 32 | uint64_t(UC('0')) << 16 | UC('0')) : (uint64_t(UC('0')) << 32 | UC('0'));
}
template<typename UC>
static constexpr int int_cmp_len()
{
return sizeof(uint64_t) / sizeof(UC);
}
template<typename UC>
static constexpr UC const * str_const_nan()
{
return nullptr;
}
template<>
constexpr char const * str_const_nan<char>()
{
return "nan";
}
template<>
constexpr wchar_t const * str_const_nan<wchar_t>()
{
return L"nan";
}
template<>
constexpr char16_t const * str_const_nan<char16_t>()
{
return u"nan";
}
template<>
constexpr char32_t const * str_const_nan<char32_t>()
{
return U"nan";
}
template<typename UC>
static constexpr UC const * str_const_inf()
{
return nullptr;
}
template<>
constexpr char const * str_const_inf<char>()
{
return "infinity";
}
template<>
constexpr wchar_t const * str_const_inf<wchar_t>()
{
return L"infinity";
}
template<>
constexpr char16_t const * str_const_inf<char16_t>()
{
return u"infinity";
}
template<>
constexpr char32_t const * str_const_inf<char32_t>()
{
return U"infinity";
}
} // namespace fast_float } // namespace fast_float
#endif #endif

View File

@ -19,35 +19,41 @@ namespace detail {
* The case comparisons could be made much faster given that we know that the * The case comparisons could be made much faster given that we know that the
* strings a null-free and fixed. * strings a null-free and fixed.
**/ **/
template <typename T> template <typename T, typename UC>
from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept { from_chars_result_t<UC> FASTFLOAT_CONSTEXPR14
from_chars_result answer; parse_infnan(UC const * first, UC const * last, T &value) noexcept {
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; bool minusSign = false;
if (*first == '-') { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here if (*first == UC('-')) { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here
minusSign = true; minusSign = true;
++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, "nan", 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() : 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). // 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 == '(') { if(first != last && *first == UC('(')) {
for(const char* ptr = first + 1; ptr != last; ++ptr) { for(UC const * ptr = first + 1; ptr != last; ++ptr) {
if (*ptr == ')') { 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(!(('a' <= *ptr && *ptr <= 'z') || ('A' <= *ptr && *ptr <= 'Z') || ('0' <= *ptr && *ptr <= '9') || *ptr == '_')) else if(!((UC('a') <= *ptr && *ptr <= UC('z')) || (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, "inf", 3)) { if (fastfloat_strncasecmp(first, str_const_inf<UC>(), 3)) {
if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, "inity", 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;
@ -60,40 +66,146 @@ from_chars_result parse_infnan(const char *first, const char *last, T &value) n
return answer; return answer;
} }
} // namespace detail /**
* Returns true if the floating-pointing rounding mode is to 'nearest'.
template<typename T> * It is the default on most system. This function is meant to be inexpensive.
from_chars_result from_chars(const char *first, const char *last, * Credit : @mwalcott3
T &value, chars_format fmt /*= chars_format::general*/) noexcept { */
return from_chars_advanced(first, last, value, parse_options{fmt}); fastfloat_really_inline bool rounds_to_nearest() noexcept {
// https://lemire.me/blog/2020/06/26/gcc-not-nearest/
#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
return false;
#endif
// See
// A fast function to check your floating-point rounding mode
// https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/
//
// This function is meant to be equivalent to :
// prior: #include <cfenv>
// return fegetround() == FE_TONEAREST;
// However, it is expected to be much faster than the fegetround()
// function call.
//
// The volatile keywoard prevents the compiler from computing the function
// at compile-time.
// There might be other ways to prevent compile-time optimizations (e.g., asm).
// The value does not need to be std::numeric_limits<float>::min(), any small
// value so that 1 + x should round to 1 would do (after accounting for excess
// precision, as in 387 instructions).
static volatile float fmin = std::numeric_limits<float>::min();
float fmini = fmin; // we copy it so that it gets loaded at most once.
//
// Explanation:
// Only when fegetround() == FE_TONEAREST do we have that
// fmin + 1.0f == 1.0f - fmin.
//
// FE_UPWARD:
// fmin + 1.0f > 1
// 1.0f - fmin == 1
//
// FE_DOWNWARD or FE_TOWARDZERO:
// fmin + 1.0f == 1
// 1.0f - fmin < 1
//
// Note: This may fail to be accurate if fast-math has been
// enabled, as rounding conventions may not apply.
#ifdef FASTFLOAT_VISUAL_STUDIO
# pragma warning(push)
// 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
#elif defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wfloat-equal"
#elif defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wfloat-equal"
#endif
return (fmini + 1.0f == 1.0f - fmini);
#ifdef FASTFLOAT_VISUAL_STUDIO
# pragma warning(pop)
#elif defined(__clang__)
# pragma clang diagnostic pop
#elif defined(__GNUC__)
# pragma GCC diagnostic pop
#endif
} }
template<typename T> } // namespace detail
from_chars_result from_chars_advanced(const char *first, const char *last,
T &value, parse_options options) noexcept { template<typename T, typename UC>
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_advanced(first, last, value, parse_options_t<UC>{fmt});
}
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 {
static_assert (std::is_same<T, double>::value || std::is_same<T, float>::value, "only float and double are supported"); static_assert (std::is_same<T, double>::value || std::is_same<T, float>::value, "only float and double are supported");
static_assert (std::is_same<UC, char>::value ||
std::is_same<UC, wchar_t>::value ||
std::is_same<UC, char16_t>::value ||
std::is_same<UC, char32_t>::value , "only char, wchar_t, char16_t and char32_t are supported");
from_chars_result_t<UC> answer;
from_chars_result answer; #ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default
while ((first != last) && fast_float::is_space(uint8_t(*first))) {
first++;
}
#endif
if (first == last) { if (first == last) {
answer.ec = std::errc::invalid_argument; answer.ec = std::errc::invalid_argument;
answer.ptr = first; answer.ptr = first;
return answer; return answer;
} }
parsed_number_string pns = parse_number_string(first, last, options); parsed_number_string_t<UC> pns = parse_number_string<UC>(first, last, options);
if (!pns.valid) { if (!pns.valid) {
return detail::parse_infnan(first, last, value); return detail::parse_infnan(first, last, value);
} }
answer.ec = std::errc(); // be optimistic answer.ec = std::errc(); // be optimistic
answer.ptr = pns.lastmatch; answer.ptr = pns.lastmatch;
// Next is Clinger's fast path. // The implementation of the Clinger's fast path is convoluted because
if (binary_format<T>::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format<T>::max_exponent_fast_path() && pns.mantissa <=binary_format<T>::max_mantissa_fast_path() && !pns.too_many_digits) { // we want round-to-nearest in all cases, irrespective of the rounding mode
value = T(pns.mantissa); // selected on the thread.
if (pns.exponent < 0) { value = value / binary_format<T>::exact_power_of_ten(-pns.exponent); } // We proceed optimistically, assuming that detail::rounds_to_nearest() returns
else { value = value * binary_format<T>::exact_power_of_ten(pns.exponent); } // true.
if (pns.negative) { value = -value; } if (binary_format<T>::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format<T>::max_exponent_fast_path() && !pns.too_many_digits) {
return answer; // Unfortunately, the conventional Clinger's fast path is only possible
// when the system rounds to the nearest float.
//
// We expect the next branch to almost always be selected.
// We could check it first (before the previous branch), but
// there might be performance advantages at having the check
// be last.
if(!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) {
// We have that fegetround() == FE_TONEAREST.
// Next is Clinger's fast path.
if (pns.mantissa <=binary_format<T>::max_mantissa_fast_path()) {
value = T(pns.mantissa);
if (pns.exponent < 0) { value = value / binary_format<T>::exact_power_of_ten(-pns.exponent); }
else { value = value * binary_format<T>::exact_power_of_ten(pns.exponent); }
if (pns.negative) { value = -value; }
return answer;
}
} else {
// We do not have that fegetround() == FE_TONEAREST.
// Next is a modified Clinger's fast path, inspired by Jakub Jelínek's proposal
if (pns.exponent >= 0 && pns.mantissa <=binary_format<T>::max_mantissa_fast_path(pns.exponent)) {
#if defined(__clang__)
// Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD
if(pns.mantissa == 0) {
value = pns.negative ? -0. : 0.;
return answer;
}
#endif
value = T(pns.mantissa) * binary_format<T>::exact_power_of_ten(pns.exponent);
if (pns.negative) { value = -value; }
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) {
@ -105,6 +217,10 @@ from_chars_result from_chars_advanced(const char *first, const char *last,
// then we need to go the long way around again. This is very uncommon. // then we need to go the long way around again. This is very uncommon.
if(am.power2 < 0) { am = digit_comp<T>(pns, am); } 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.
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;
}
return answer; return answer;
} }

View File

@ -137,7 +137,7 @@ inline uint64_t round(decimal &h) {
} }
bool round_up = false; bool round_up = false;
if (dp < h.num_digits) { if (dp < h.num_digits) {
round_up = h.digits[dp] >= 5; // normally, we round up round_up = h.digits[dp] >= 5; // normally, we round up
// but we may need to round to even! // but we may need to round to even!
if ((h.digits[dp] == 5) && (dp + 1 == h.num_digits)) { if ((h.digits[dp] == 5) && (dp + 1 == h.num_digits)) {
round_up = h.truncated || ((dp > 0) && (1 & h.digits[dp - 1])); round_up = h.truncated || ((dp > 0) && (1 & h.digits[dp - 1]));
@ -266,7 +266,7 @@ adjusted_mantissa compute_float(decimal &d) {
return answer; return answer;
} else if(d.decimal_point >= 310) { } else if(d.decimal_point >= 310) {
// We have something at least as large as 0.1e310 which is // We have something at least as large as 0.1e310 which is
// always infinite. // always infinite.
answer.power2 = binary::infinite_power(); answer.power2 = binary::infinite_power();
answer.mantissa = 0; answer.mantissa = 0;
return answer; return answer;

View File

@ -3,54 +3,87 @@ processed_files = { }
# authors # authors
for filename in ['AUTHORS', 'CONTRIBUTORS']: for filename in ['AUTHORS', 'CONTRIBUTORS']:
with open(filename) 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 processed_files[filename] = text + '//\n'
# licenses # licenses
for filename in ['LICENSE-MIT', 'LICENSE-APACHE']: for filename in ['LICENSE-MIT', 'LICENSE-APACHE']:
with open(filename) as f: lines = []
text = '' with open(filename, encoding='utf8') as f:
for line in f: lines = f.readlines()
text += '// ' + line
processed_files[filename] = text # Retrieve subset required for inclusion in source
if filename == 'LICENSE-APACHE':
lines = [
' Copyright 2021 The fast_float authors\n',
*lines[179:-1]
]
text = ''
for line in lines:
text += '// ' + line.strip() + '\n'
processed_files[filename] = text
# code # code
for filename in [ 'fast_float.h', 'float_common.h', 'ascii_number.h', for filename in [ 'constexpr_feature_detect.h', 'fast_float.h', 'float_common.h', 'ascii_number.h',
'fast_table.h', 'decimal_to_binary.h', 'bigint.h', 'fast_table.h', 'decimal_to_binary.h', 'bigint.h',
'ascii_number.h', 'digit_comparison.h', 'parse_number.h']: 'ascii_number.h', 'digit_comparison.h', 'parse_number.h']:
with open('include/fast_float/' + filename) as f: with open('include/fast_float/' + filename, encoding='utf8') as f:
text = '' 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] = 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='MIT', help='choose license') parser.add_argument('--license', default='DUAL', choices=['DUAL', 'MIT', 'APACHE'], help='choose license')
parser.add_argument('--output', default='', help='output file (stdout if none') parser.add_argument('--output', default='', help='output file (stdout if none')
args = parser.parse_args() args = parser.parse_args()
text = '\n\n'.join([ def license_content(license_arg):
processed_files['AUTHORS'], processed_files['CONTRIBUTORS'], result = []
processed_files['LICENSE-' + args.license],
processed_files['fast_float.h'], processed_files['float_common.h'], if license_arg == 'DUAL':
result += [
'// Licensed under the Apache License, Version 2.0, or the\n',
'// MIT License at your option. This file may not be copied,\n',
'// modified, or distributed except according to those terms.\n',
'//\n'
]
if license_arg in ('DUAL', 'MIT'):
result.append('// MIT License Notice\n//\n')
result.append(processed_files['LICENSE-MIT'])
result.append('//\n')
if license_arg in ('DUAL', 'APACHE'):
result.append('// Apache License (Version 2.0) Notice\n//\n')
result.append(processed_files['LICENSE-APACHE'])
result.append('//\n')
return result
text = ''.join([
processed_files['AUTHORS'], processed_files['CONTRIBUTORS'],
*license_content(args.license),
processed_files['constexpr_feature_detect.h'],
processed_files['fast_float.h'], processed_files['float_common.h'],
processed_files['ascii_number.h'], processed_files['fast_table.h'], processed_files['ascii_number.h'], processed_files['fast_table.h'],
processed_files['decimal_to_binary.h'], processed_files['bigint.h'], processed_files['decimal_to_binary.h'], processed_files['bigint.h'],
processed_files['ascii_number.h'], processed_files['digit_comparison.h'], processed_files['ascii_number.h'], processed_files['digit_comparison.h'],
processed_files['parse_number.h']]) processed_files['parse_number.h']])
if args.output: if args.output:
with open(args.output, 'wt') as f: with open(args.output, 'wt', encoding='utf8') as f:
f.write(text) f.write(text)
else: else:
print(text) print(text)

75
script/mushtak_lemire.py Normal file
View File

@ -0,0 +1,75 @@
#
# Reference :
# Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to appear)
#
all_tqs = []
# Generates all possible values of T[q]
# Appendix B of Number parsing at a gigabyte per second.
# Software: Practice and Experience 2021;51(8):17001727.
for q in range(-342, -27):
power5 = 5**-q
z = 0
while (1 << z) < power5:
z += 1
b = 2 * z + 2 * 64
c = 2**b // power5 + 1
while c >= (1 << 128):
c //= 2
all_tqs.append(c)
for q in range(-27, 0):
power5 = 5**-q
z = 0
while (1 << z) < power5:
z += 1
b = z + 127
c = 2**b // power5 + 1
all_tqs.append(c)
for q in range(0, 308 + 1):
power5 = 5**q
while power5 < (1 << 127):
power5 *= 2
while power5 >= (1 << 128):
power5 //= 2
all_tqs.append(power5)
# Returns the continued fraction of numer/denom as a list [a0; a1, a2, ..., an]
def continued_fraction(numer, denom):
# (look at page numbers in top-left, not PDF page numbers)
cf = []
while denom != 0:
quot, rem = divmod(numer, denom)
cf.append(quot)
numer, denom = denom, rem
return cf
# Given a continued fraction [a0; a1, a2, ..., an], returns
# all the convergents of that continued fraction
# as pairs of the form (numer, denom), where numer/denom is
# a convergent of the continued fraction in simple form.
def convergents(cf):
p_n_minus_2 = 0
q_n_minus_2 = 1
p_n_minus_1 = 1
q_n_minus_1 = 0
convergents = []
for a_n in cf:
p_n = a_n * p_n_minus_1 + p_n_minus_2
q_n = a_n * q_n_minus_1 + q_n_minus_2
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
return convergents
# Enumerate through all the convergents of T[q] / 2^137 with denominators < 2^64
found_solution = False
for j, tq in enumerate(all_tqs):
for _, w in convergents(continued_fraction(tq, 2**137)):
if w >= 2**64:
break
if (tq*w) % 2**137 > 2**137 - 2**64:
print(f"SOLUTION: q={j-342} T[q]={tq} w={w}")
found_solution = True
if not found_solution:
print("No solutions!")

View File

@ -38,7 +38,11 @@ add_library(supplemental-data INTERFACE)
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")
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)
add_test(${TEST_NAME} ${TEST_NAME}) if(CMAKE_CROSSCOMPILING)
set(ccemulator ${CMAKE_CROSSCOMPILING_EMULATOR})
endif()
add_test(NAME ${TEST_NAME}
COMMAND ${ccemulator} $<TARGET_FILE:${TEST_NAME}>)
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
target_compile_options(${TEST_NAME} PUBLIC /EHsc) target_compile_options(${TEST_NAME} PUBLIC /EHsc)
endif() endif()
@ -52,10 +56,18 @@ function(fast_float_add_cpp_test TEST_NAME)
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(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(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)
if (FASTFLOAT_CONSTEXPR_TESTS)
target_compile_features(basictest PRIVATE cxx_std_20)
target_compile_definitions(basictest PRIVATE FASTFLOAT_CONSTEXPR_TESTS)
else()
target_compile_features(basictest PRIVATE cxx_std_17)
endif()
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)
@ -77,3 +89,4 @@ if (FASTFLOAT_EXHAUSTIVE)
endif(FASTFLOAT_EXHAUSTIVE) endif(FASTFLOAT_EXHAUSTIVE)
add_subdirectory(build_tests) add_subdirectory(build_tests)
add_subdirectory(bloat_analysis)

View File

@ -10,6 +10,18 @@
#include <limits> #include <limits>
#include <string> #include <string>
#include <system_error> #include <system_error>
#include <type_traits>
#include <cfenv>
#if FASTFLOAT_IS_CONSTEXPR
#ifndef FASTFLOAT_CONSTEXPR_TESTS
#define FASTFLOAT_CONSTEXPR_TESTS 1
#endif // #ifndef FASTFLOAT_CONSTEXPR_TESTS
#endif // FASTFLOAT_IS_CONSTEXPR
#if FASTFLOAT_HAS_BIT_CAST
#include <bit>
#endif
#ifndef SUPPLEMENTAL_TEST_DATA_DIR #ifndef SUPPLEMENTAL_TEST_DATA_DIR
#define SUPPLEMENTAL_TEST_DATA_DIR "data/" #define SUPPLEMENTAL_TEST_DATA_DIR "data/"
@ -42,6 +54,198 @@
#define FASTFLOAT_ODDPLATFORM 1 #define FASTFLOAT_ODDPLATFORM 1
#endif #endif
#define iHexAndDec(v) std::hex << "0x" << (v) << " (" << std::dec << (v) << ")"
#define fHexAndDec(v) std::hexfloat << (v) << " (" << std::defaultfloat << (v) << ")"
const char * round_name(int d) {
switch(d) {
case FE_UPWARD:
return "FE_UPWARD";
case FE_DOWNWARD:
return "FE_DOWNWARD";
case FE_TOWARDZERO:
return "FE_TOWARDZERO";
case FE_TONEAREST:
return "FE_TONEAREST";
default:
return "UNKNOWN";
}
}
#define FASTFLOAT_STR(x) #x
#define SHOW_DEFINE(x) printf("%s='%s'\n", #x, FASTFLOAT_STR(x))
TEST_CASE("system_info") {
std::cout << "system info:" << std::endl;
#ifdef FASTFLOAT_CONSTEXPR_TESTS
SHOW_DEFINE(FASTFLOAT_CONSTEXPR_TESTS);
#endif
#ifdef _MSC_VER
SHOW_DEFINE(_MSC_VER);
#endif
#ifdef FASTFLOAT_64BIT_LIMB
SHOW_DEFINE(FASTFLOAT_64BIT_LIMB);
#endif
#ifdef __clang__
SHOW_DEFINE(__clang__);
#endif
#ifdef FASTFLOAT_VISUAL_STUDIO
SHOW_DEFINE(FASTFLOAT_VISUAL_STUDIO);
#endif
#ifdef FASTFLOAT_IS_BIG_ENDIAN
#if FASTFLOAT_IS_BIG_ENDIAN
printf("big endian\n");
#else
printf("little endian\n");
#endif
#endif
#ifdef FASTFLOAT_32BIT
SHOW_DEFINE(FASTFLOAT_32BIT);
#endif
#ifdef FASTFLOAT_64BIT
SHOW_DEFINE(FASTFLOAT_64BIT);
#endif
#ifdef FLT_EVAL_METHOD
SHOW_DEFINE(FLT_EVAL_METHOD);
#endif
#ifdef _WIN32
SHOW_DEFINE(_WIN32);
#endif
#ifdef _WIN64
SHOW_DEFINE(_WIN64);
#endif
std::cout << "fegetround() = " << round_name(fegetround()) << std::endl;
std::cout << std::endl;
}
TEST_CASE("rounds_to_nearest") {
//
// If this function fails, we may be left in a non-standard rounding state.
//
static volatile float fmin = std::numeric_limits<float>::min();
fesetround(FE_UPWARD);
std::cout << "FE_UPWARD: fmin + 1.0f = " << iHexAndDec(fmin + 1.0f) << " 1.0f - fmin = " << iHexAndDec(1.0f - fmin) << std::endl;
CHECK(fegetround() == FE_UPWARD);
CHECK(fast_float::detail::rounds_to_nearest() == false);
fesetround(FE_DOWNWARD);
std::cout << "FE_DOWNWARD: fmin + 1.0f = " << iHexAndDec(fmin + 1.0f) << " 1.0f - fmin = " << iHexAndDec(1.0f - fmin) << std::endl;
CHECK(fegetround() == FE_DOWNWARD);
CHECK(fast_float::detail::rounds_to_nearest() == false);
fesetround(FE_TOWARDZERO);
std::cout << "FE_TOWARDZERO: fmin + 1.0f = " << iHexAndDec(fmin + 1.0f) << " 1.0f - fmin = " << iHexAndDec(1.0f - fmin) << std::endl;
CHECK(fegetround() == FE_TOWARDZERO);
CHECK(fast_float::detail::rounds_to_nearest() == false);
fesetround(FE_TONEAREST);
std::cout << "FE_TONEAREST: fmin + 1.0f = " << iHexAndDec(fmin + 1.0f) << " 1.0f - fmin = " << iHexAndDec(1.0f - fmin) << std::endl;
CHECK(fegetround() == FE_TONEAREST);
#if (FLT_EVAL_METHOD == 1) || (FLT_EVAL_METHOD == 0)
CHECK(fast_float::detail::rounds_to_nearest() == true);
#endif
}
TEST_CASE("parse_zero") {
//
// If this function fails, we may be left in a non-standard rounding state.
//
const char * zero = "0";
uint64_t float64_parsed;
double f = 0;
::memcpy(&float64_parsed, &f, sizeof(f));
CHECK(float64_parsed == 0);
fesetround(FE_UPWARD);
auto r1 = fast_float::from_chars(zero, zero + 1, f);
CHECK(r1.ec == std::errc());
std::cout << "FE_UPWARD parsed zero as " << iHexAndDec(f) << std::endl;
CHECK(f == 0);
::memcpy(&float64_parsed, &f, sizeof(f));
std::cout << "double as uint64_t is " << float64_parsed << std::endl;
CHECK(float64_parsed == 0);
fesetround(FE_TOWARDZERO);
auto r2 = fast_float::from_chars(zero, zero + 1, f);
CHECK(r2.ec == std::errc());
std::cout << "FE_TOWARDZERO parsed zero as " << iHexAndDec(f) << std::endl;
CHECK(f == 0);
::memcpy(&float64_parsed, &f, sizeof(f));
std::cout << "double as uint64_t is " << float64_parsed << std::endl;
CHECK(float64_parsed == 0);
fesetround(FE_DOWNWARD);
auto r3 = fast_float::from_chars(zero, zero + 1, f);
CHECK(r3.ec == std::errc());
std::cout << "FE_DOWNWARD parsed zero as " << iHexAndDec(f) << std::endl;
CHECK(f == 0);
::memcpy(&float64_parsed, &f, sizeof(f));
std::cout << "double as uint64_t is " << float64_parsed << std::endl;
CHECK(float64_parsed == 0);
fesetround(FE_TONEAREST);
auto r4 = fast_float::from_chars(zero, zero + 1, f);
CHECK(r4.ec == std::errc());
std::cout << "FE_TONEAREST parsed zero as " << iHexAndDec(f) << std::endl;
CHECK(f == 0);
::memcpy(&float64_parsed, &f, sizeof(f));
std::cout << "double as uint64_t is " << float64_parsed << std::endl;
CHECK(float64_parsed == 0);
}
TEST_CASE("parse_negative_zero") {
//
// If this function fails, we may be left in a non-standard rounding state.
//
const char * negative_zero = "-0";
uint64_t float64_parsed;
double f = -0.;
::memcpy(&float64_parsed, &f, sizeof(f));
CHECK(float64_parsed == 0x8000'0000'0000'0000);
fesetround(FE_UPWARD);
auto r1 = fast_float::from_chars(negative_zero, negative_zero + 2, f);
CHECK(r1.ec == std::errc());
std::cout << "FE_UPWARD parsed negative zero as " << iHexAndDec(f) << std::endl;
CHECK(f == 0);
::memcpy(&float64_parsed, &f, sizeof(f));
std::cout << "double as uint64_t is " << float64_parsed << std::endl;
CHECK(float64_parsed == 0x8000'0000'0000'0000);
fesetround(FE_TOWARDZERO);
auto r2 = fast_float::from_chars(negative_zero, negative_zero + 2, f);
CHECK(r2.ec == std::errc());
std::cout << "FE_TOWARDZERO parsed negative zero as " << iHexAndDec(f) << std::endl;
CHECK(f == 0);
::memcpy(&float64_parsed, &f, sizeof(f));
std::cout << "double as uint64_t is " << float64_parsed << std::endl;
CHECK(float64_parsed == 0x8000'0000'0000'0000);
fesetround(FE_DOWNWARD);
auto r3 = fast_float::from_chars(negative_zero, negative_zero + 2, f);
CHECK(r3.ec == std::errc());
std::cout << "FE_DOWNWARD parsed negative zero as " << iHexAndDec(f) << std::endl;
CHECK(f == 0);
::memcpy(&float64_parsed, &f, sizeof(f));
std::cout << "double as uint64_t is " << float64_parsed << std::endl;
CHECK(float64_parsed == 0x8000'0000'0000'0000);
fesetround(FE_TONEAREST);
auto r4 = fast_float::from_chars(negative_zero, negative_zero + 2, f);
CHECK(r4.ec == std::errc());
std::cout << "FE_TONEAREST parsed negative zero as " << iHexAndDec(f) << std::endl;
CHECK(f == 0);
::memcpy(&float64_parsed, &f, sizeof(f));
std::cout << "double as uint64_t is " << float64_parsed << std::endl;
CHECK(float64_parsed == 0x8000'0000'0000'0000);
}
// C++ 17 because it is otherwise annoying to browse all files in a directory. // C++ 17 because it is otherwise annoying to browse all files in a directory.
// We also only run these tests on little endian systems. // We also only run these tests on little endian systems.
#if (FASTFLOAT_CPLUSPLUS >= 201703L) && (FASTFLOAT_IS_BIG_ENDIAN == 0) && !defined(FASTFLOAT_ODDPLATFORM) #if (FASTFLOAT_CPLUSPLUS >= 201703L) && (FASTFLOAT_IS_BIG_ENDIAN == 0) && !defined(FASTFLOAT_ODDPLATFORM)
@ -50,59 +254,77 @@
#include <filesystem> #include <filesystem>
#include <charconv> #include <charconv>
// return true on succcess
// return true on success
bool check_file(std::string file_name) { bool check_file(std::string file_name) {
std::cout << "Checking " << file_name << std::endl; std::cout << "Checking " << file_name << std::endl;
size_t number{0}; // We check all rounding directions, for each file.
std::fstream newfile(file_name, std::ios::in); std::vector<int> directions = {FE_UPWARD, FE_DOWNWARD, FE_TOWARDZERO, FE_TONEAREST};
if (newfile.is_open()) { for (int d : directions) {
std::string str; std::cout << "fesetround to " << round_name(d) << std::endl;
while (std::getline(newfile, str)) { fesetround(d);
if (str.size() > 0) { size_t number{0};
// Read 32-bit hex std::fstream newfile(file_name, std::ios::in);
uint32_t float32; if (newfile.is_open()) {
auto r32 = std::from_chars(str.data() + 5, str.data() + str.size(), std::string str;
while (std::getline(newfile, str)) {
if (str.size() > 0) {
// Read 32-bit hex
uint32_t float32;
auto r32 = std::from_chars(str.data() + 5, str.data() + str.size(),
float32, 16); float32, 16);
if(r32.ec != std::errc()) { std::cerr << "32-bit parsing failure\n"; return false; } if(r32.ec != std::errc()) { std::cerr << "32-bit parsing failure\n"; return false; }
// Read 64-bit hex // Read 64-bit hex
uint64_t float64; uint64_t float64;
auto r64 = std::from_chars(str.data() + 14, str.data() + str.size(), auto r64 = std::from_chars(str.data() + 14, str.data() + str.size(),
float64, 16); float64, 16);
if(r64.ec != std::errc()) { std::cerr << "64-bit parsing failure\n"; return false; } if(r64.ec != std::errc()) { std::cerr << "64-bit parsing failure\n"; return false; }
// The string to parse: // The string to parse:
const char *number_string = str.data() + 31; const char *number_string = str.data() + 31;
const char *end_of_string = str.data() + str.size(); const char *end_of_string = str.data() + str.size();
// Parse as 32-bit float // Parse as 32-bit float
float parsed_32; float parsed_32;
auto fast_float_r32 = fast_float::from_chars(number_string, end_of_string, parsed_32); auto fast_float_r32 = fast_float::from_chars(number_string, end_of_string, parsed_32);
if(fast_float_r32.ec != std::errc()) { std::cerr << "parsing failure\n"; return false; } if(fast_float_r32.ec != std::errc() && fast_float_r32.ec != std::errc::result_out_of_range) {std::cerr << "32-bit fast_float parsing failure for: " + str + "\n"; return false; }
// Parse as 64-bit float // Parse as 64-bit float
double parsed_64; double parsed_64;
auto fast_float_r64 = fast_float::from_chars(number_string, end_of_string, parsed_64); auto fast_float_r64 = fast_float::from_chars(number_string, end_of_string, parsed_64);
if(fast_float_r64.ec != std::errc()) { std::cerr << "parsing failure\n"; return false; } if(fast_float_r64.ec != std::errc() && fast_float_r32.ec != std::errc::result_out_of_range) { std::cerr << "64-bit fast_float parsing failure: " + str + "\n"; return false; }
// Convert the floats to unsigned ints. // Convert the floats to unsigned ints.
uint32_t float32_parsed; uint32_t float32_parsed;
uint64_t float64_parsed; uint64_t float64_parsed;
::memcpy(&float32_parsed, &parsed_32, sizeof(parsed_32)); ::memcpy(&float32_parsed, &parsed_32, sizeof(parsed_32));
::memcpy(&float64_parsed, &parsed_64, sizeof(parsed_64)); ::memcpy(&float64_parsed, &parsed_64, sizeof(parsed_64));
// Compare with expected results // Compare with expected results
if (float32_parsed != float32) { if (float32_parsed != float32) {
std::cout << "bad 32 " << str << std::endl; std::cout << "bad 32 " << str << std::endl;
return false; std::cout << "parsed as " << iHexAndDec(parsed_32) << std::endl;
std::cout << "as raw uint32_t, parsed = " << float32_parsed << ", expected = " << float32 << std::endl;
std::cout << "fesetround: " << round_name(d) << std::endl;
fesetround(FE_TONEAREST);
return false;
}
if (float64_parsed != float64) {
std::cout << "bad 64 " << str << std::endl;
std::cout << "parsed as " << iHexAndDec(parsed_64) << std::endl;
std::cout << "as raw uint64_t, parsed = " << float64_parsed << ", expected = " << float64 << std::endl;
std::cout << "fesetround: " << round_name(d) << std::endl;
fesetround(FE_TONEAREST);
return false;
}
number++;
} }
if (float64_parsed != float64) {
std::cout << "bad 64 " << str << std::endl;
return false;
}
number++;
} }
std::cout << "checked " << std::defaultfloat << number << " values" << std::endl;
newfile.close(); // close the file object
} else {
std::cout << "Could not read " << file_name << std::endl;
fesetround(FE_TONEAREST);
return false;
} }
std::cout << "checked " << std::defaultfloat << number << " values" << std::endl;
newfile.close(); // close the file object
} else {
std::cout << "Could not read " << file_name << std::endl;
return false;
} }
fesetround(FE_TONEAREST);
return true; return true;
} }
@ -125,9 +347,6 @@ TEST_CASE("leading_zeroes") {
CHECK(fast_float::leading_zeroes(bit << 63) == 0); CHECK(fast_float::leading_zeroes(bit << 63) == 0);
} }
#define iHexAndDec(v) std::hex << "0x" << (v) << " (" << std::dec << (v) << ")"
#define fHexAndDec(v) std::hexfloat << (v) << " (" << std::defaultfloat << (v) << ")"
void test_full_multiplication(uint64_t lhs, uint64_t rhs, uint64_t expected_lo, uint64_t expected_hi) { void test_full_multiplication(uint64_t lhs, uint64_t rhs, uint64_t expected_lo, uint64_t expected_hi) {
fast_float::value128 v; fast_float::value128 v;
v = fast_float::full_multiplication(lhs, rhs); v = fast_float::full_multiplication(lhs, rhs);
@ -224,6 +443,33 @@ TEST_CASE("decimal_point_parsing") {
} }
} }
TEST_CASE("issue19") {
const std::string input = "234532.3426362,7869234.9823,324562.645";
double result;
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
CHECK_MESSAGE(answer.ec == std::errc(), "We want to parse up to 234532.3426362\n");
CHECK_MESSAGE(answer.ptr == input.data() + 14,
"Parsed the number " << result
<< " and stopped at the wrong character: after " << (answer.ptr - input.data()) << " characters");
CHECK_MESSAGE(result == 234532.3426362, "We want to parse234532.3426362\n");
CHECK_MESSAGE(answer.ptr[0] == ',', "We want to parse up to the comma\n");
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
CHECK_MESSAGE(answer.ec == std::errc(), "We want to parse 7869234.9823\n");
CHECK_MESSAGE(answer.ptr == input.data() + 27,
"Parsed the number " << result
<< " and stopped at the wrong character " << (answer.ptr - input.data()));
CHECK_MESSAGE(answer.ptr[0] == ',', "We want to parse up to the comma\n");
CHECK_MESSAGE(result == 7869234.9823, "We want to parse up 7869234.9823\n");
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
CHECK_MESSAGE(answer.ec == std::errc(), "We want to parse 324562.645\n");
CHECK_MESSAGE(answer.ptr == input.data() + 38,
"Parsed the number " << result
<< " and stopped at the wrong character " << (answer.ptr - input.data()));
CHECK_MESSAGE(result == 324562.645, "We want to parse up 7869234.9823\n");
}
TEST_CASE("issue19") { TEST_CASE("issue19") {
const std::string input = "3.14e"; const std::string input = "3.14e";
double result; double result;
@ -263,6 +509,9 @@ TEST_CASE("test_fixed_only") {
static const double testing_power_of_ten[] = { static const double testing_power_of_ten[] = {
1e-323, 1e-322, 1e-321, 1e-320, 1e-319, 1e-318, 1e-317, 1e-316, 1e-315,
1e-314, 1e-313, 1e-312, 1e-311, 1e-310, 1e-309, 1e-308,
1e-307, 1e-306, 1e-305, 1e-304, 1e-303, 1e-302, 1e-301, 1e-300, 1e-299, 1e-307, 1e-306, 1e-305, 1e-304, 1e-303, 1e-302, 1e-301, 1e-300, 1e-299,
1e-298, 1e-297, 1e-296, 1e-295, 1e-294, 1e-293, 1e-292, 1e-291, 1e-290, 1e-298, 1e-297, 1e-296, 1e-295, 1e-294, 1e-293, 1e-292, 1e-291, 1e-290,
1e-289, 1e-288, 1e-287, 1e-286, 1e-285, 1e-284, 1e-283, 1e-282, 1e-281, 1e-289, 1e-288, 1e-287, 1e-286, 1e-285, 1e-284, 1e-283, 1e-282, 1e-281,
@ -346,8 +595,9 @@ TEST_CASE("powers_of_ten") {
REQUIRE(n < sizeof(buf)); // if false, fails the test and exits REQUIRE(n < sizeof(buf)); // if false, fails the test and exits
double actual; double actual;
auto result = fast_float::from_chars(buf, buf + 1000, actual); auto result = fast_float::from_chars(buf, buf + 1000, actual);
CHECK_MESSAGE(result.ec == std::errc(), " I could not parse " << buf); double expected = ((i >= -323) ? testing_power_of_ten[i + 323] : std::pow(10, i));
double expected = ((i >= -307) ? testing_power_of_ten[i + 307] : std::pow(10, i)); auto expected_ec = (i < -323 || i > 308) ? std::errc::result_out_of_range : std::errc();
CHECK_MESSAGE(result.ec == expected_ec, " I could not parse " << buf);
CHECK_MESSAGE(actual == expected, "String '" << buf << "'parsed to " << actual); CHECK_MESSAGE(actual == expected, "String '" << buf << "'parsed to " << actual);
} }
} }
@ -388,52 +638,172 @@ std::string append_zeros(std::string str, size_t number_of_zeros) {
return answer; return answer;
} }
template<class T> namespace {
void check_basic_test_result(const std::string& str, fast_float::from_chars_result result,
T actual, T expected) { enum class Diag { runtime, comptime };
INFO("str=" << str << "\n"
<< " expected=" << fHexAndDec(expected) << "\n" } // anonymous namespace
<< " ..actual=" << fHexAndDec(actual) << "\n"
<< " expected mantissa=" << iHexAndDec(get_mantissa(expected)) << "\n" template <Diag diag, class T, typename result_type, typename stringtype>
<< " ..actual mantissa=" << iHexAndDec(get_mantissa(actual))); constexpr void check_basic_test_result(stringtype str,
CHECK_EQ(result.ec, std::errc()); result_type result,
CHECK_EQ(result.ptr, str.data() + str.size()); T actual, T expected, std::errc expected_ec) {
CHECK_EQ(copysign(1, actual), copysign(1, expected)); if constexpr (diag == Diag::runtime) {
CHECK_EQ(std::isnan(actual), std::isnan(expected)); INFO(
CHECK_EQ(actual, expected); "str=" << str << "\n"
<< " expected=" << fHexAndDec(expected) << "\n"
<< " ..actual=" << fHexAndDec(actual) << "\n"
<< " expected mantissa=" << iHexAndDec(get_mantissa(expected))
<< "\n"
<< " ..actual mantissa=" << iHexAndDec(get_mantissa(actual)));
}
struct ComptimeDiag {
// Purposely not constexpr
static void error_not_equal() {}
};
#define FASTFLOAT_CHECK_EQ(...) \
if constexpr (diag == Diag::runtime) { \
CHECK_EQ(__VA_ARGS__); \
} else { \
if ([](const auto &lhs, const auto &rhs) { \
return lhs != rhs; \
}(__VA_ARGS__)) { \
ComptimeDiag::error_not_equal(); \
} \
}
auto copysign = [](double x, double y) -> double {
#if FASTFLOAT_HAS_BIT_CAST
if (fast_float::cpp20_and_in_constexpr()) {
using equiv_int = std::make_signed_t<
typename fast_float::binary_format<double>::equiv_uint>;
const auto i = std::bit_cast<equiv_int>(y);
if (i < 0) {
return -x;
}
return x;
}
#endif
return std::copysign(x, y);
};
auto isnan = [](double x) -> bool {
return x != x;
};
FASTFLOAT_CHECK_EQ(result.ec, expected_ec);
FASTFLOAT_CHECK_EQ(result.ptr, str.data() + str.size());
FASTFLOAT_CHECK_EQ(copysign(1, actual), copysign(1, expected));
FASTFLOAT_CHECK_EQ(isnan(actual), isnan(expected));
FASTFLOAT_CHECK_EQ(actual, expected);
#undef FASTFLOAT_CHECK_EQ
} }
template<class T> // We give plenty of memory: 2048 characters.
void basic_test(std::string str, T expected) { const size_t global_string_capacity = 2048;
std::u16string u16(global_string_capacity, '\0');
std::u32string u32(global_string_capacity, '\0');
template<Diag diag, class T>
constexpr void basic_test(std::string_view str, T expected, std::errc expected_ec = std::errc()) {
T actual; T actual;
auto result = fast_float::from_chars(str.data(), str.data() + str.size(), actual); auto result = fast_float::from_chars(str.data(), str.data() + str.size(), actual);
check_basic_test_result(str, result, actual, expected); check_basic_test_result<diag>(str, result, actual, expected, expected_ec);
if(str.size() > global_string_capacity) {
return;
}
for (size_t i = 0; i < str.size(); i++) {
u16[i] = char16_t(str[i]);
}
auto result16 = fast_float::from_chars(u16.data(), u16.data() + str.size(), actual);
check_basic_test_result<diag>(std::u16string_view(u16.data(), str.size()), result16, actual, expected, expected_ec);
for (size_t i = 0; i < str.size(); i++) {
u32[i] = char32_t(str[i]);
}
auto result32 = fast_float::from_chars(u32.data(), u32.data() + str.size(), actual);
check_basic_test_result<diag>(std::u32string_view(u32.data(), str.size()), result32, actual, expected, expected_ec);
} }
template<class T> template<Diag diag, class T>
void basic_test(std::string str, T expected, fast_float::parse_options options) { constexpr void basic_test(std::string_view str, T expected, fast_float::parse_options options) {
T actual; T actual;
auto result = fast_float::from_chars_advanced(str.data(), str.data() + str.size(), actual, options); auto result = fast_float::from_chars_advanced(str.data(), str.data() + str.size(), actual, options);
check_basic_test_result(str, result, actual, expected); check_basic_test_result<diag>(str, result, actual, expected, std::errc());
}
template<Diag diag, class T>
constexpr void basic_test(std::string_view str, T expected, std::errc expected_ec, fast_float::parse_options options) {
T actual;
auto result = fast_float::from_chars_advanced(str.data(), str.data() + str.size(), actual, options);
check_basic_test_result<diag>(str, result, actual, expected, expected_ec);
} }
void basic_test(float val) { void basic_test(float val) {
{ {
std::string long_vals = to_long_string(val); std::string long_vals = to_long_string(val);
INFO("long vals: " << long_vals); INFO("long vals: " << long_vals);
basic_test<float>(long_vals, val); basic_test<Diag::runtime, float>(long_vals, val);
} }
{ {
std::string vals = to_string(val); std::string vals = to_string(val);
INFO("vals: " << vals); INFO("vals: " << vals);
basic_test<float>(vals, val); basic_test<Diag::runtime, float>(vals, val);
} }
} }
#define verify(lhs, rhs) { INFO(lhs); basic_test(lhs, rhs); } #define verify_runtime(...) \
#define verify32(val) { INFO(#val); basic_test(val); } do { \
basic_test<Diag::runtime>(__VA_ARGS__); \
} while (false)
#define verify_options(lhs, rhs) { INFO(lhs); basic_test(lhs, rhs, options); } #define verify_comptime(...) \
do { \
constexpr int verify_comptime_var = \
(basic_test<Diag::comptime>(__VA_ARGS__), 0); \
(void)verify_comptime_var; \
} while (false)
#define verify_options_runtime(...) \
do { \
basic_test<Diag::runtime>(__VA_ARGS__, options); \
} while (false)
#define verify_options_comptime(...) \
do { \
constexpr int verify_options_comptime_var = \
(basic_test<Diag::comptime>(__VA_ARGS__, options), 0); \
(void)verify_options_comptime_var; \
} while (false)
#if defined(FASTFLOAT_CONSTEXPR_TESTS)
#if !FASTFLOAT_IS_CONSTEXPR
#error "from_chars must be constexpr for constexpr tests"
#endif
#define verify(...) \
do { \
verify_runtime(__VA_ARGS__); \
verify_comptime(__VA_ARGS__); \
} while (false)
#define verify_options(...) \
do { \
verify_options_runtime(__VA_ARGS__); \
verify_options_comptime(__VA_ARGS__); \
} while (false)
#else
#define verify verify_runtime
#define verify_options verify_options_runtime
#endif
#define verify32(val) { INFO(#val); basic_test(val); }
TEST_CASE("64bit.inf") { TEST_CASE("64bit.inf") {
verify("INF", std::numeric_limits<double>::infinity()); verify("INF", std::numeric_limits<double>::infinity());
@ -444,20 +814,23 @@ TEST_CASE("64bit.inf") {
verify("-infinity", -std::numeric_limits<double>::infinity()); verify("-infinity", -std::numeric_limits<double>::infinity());
verify("inf", std::numeric_limits<double>::infinity()); verify("inf", std::numeric_limits<double>::infinity());
verify("-inf", -std::numeric_limits<double>::infinity()); verify("-inf", -std::numeric_limits<double>::infinity());
verify("1234456789012345678901234567890e9999999999999999999999999999", std::numeric_limits<double>::infinity()); verify("1234456789012345678901234567890e9999999999999999999999999999", std::numeric_limits<double>::infinity(), std::errc::result_out_of_range);
verify("-2139879401095466344511101915470454744.9813888656856943E+272", -std::numeric_limits<double>::infinity()); verify("-2139879401095466344511101915470454744.9813888656856943E+272", -std::numeric_limits<double>::infinity(), std::errc::result_out_of_range);
verify("1.8e308", std::numeric_limits<double>::infinity()); verify("1.8e308", std::numeric_limits<double>::infinity(), std::errc::result_out_of_range);
verify("1.832312213213213232132132143451234453123412321321312e308", std::numeric_limits<double>::infinity()); verify("1.832312213213213232132132143451234453123412321321312e308", std::numeric_limits<double>::infinity(), std::errc::result_out_of_range);
verify("2e30000000000000000", std::numeric_limits<double>::infinity()); verify("2e30000000000000000", std::numeric_limits<double>::infinity(), std::errc::result_out_of_range);
verify("2e3000", std::numeric_limits<double>::infinity()); verify("2e3000", std::numeric_limits<double>::infinity(), std::errc::result_out_of_range);
verify("1.9e308", std::numeric_limits<double>::infinity()); verify("1.9e308", std::numeric_limits<double>::infinity(), std::errc::result_out_of_range);
} }
TEST_CASE("64bit.general") { TEST_CASE("64bit.general") {
verify("22250738585072012e-324",0x1p-1022); /* limit between normal and subnormal*/
verify("-22250738585072012e-324",-0x1p-1022); /* limit between normal and subnormal*/
verify("-1e-999", -0.0, std::errc::result_out_of_range);
verify("-2.2222222222223e-322",-0x1.68p-1069); verify("-2.2222222222223e-322",-0x1.68p-1069);
verify("9007199254740993.0", 0x1p+53); verify("9007199254740993.0", 0x1p+53);
verify("860228122.6654514319E+90", 0x1.92bb20990715fp+328); verify("860228122.6654514319E+90", 0x1.92bb20990715fp+328);
verify(append_zeros("9007199254740993.0",1000), 0x1p+53); verify_runtime(append_zeros("9007199254740993.0",1000), 0x1p+53);
verify("10000000000000000000", 0x1.158e460913dp+63); verify("10000000000000000000", 0x1.158e460913dp+63);
verify("10000000000000000000000000000001000000000000", 0x1.cb2d6f618c879p+142); verify("10000000000000000000000000000001000000000000", 0x1.cb2d6f618c879p+142);
verify("10000000000000000000000000000000000000000001", 0x1.cb2d6f618c879p+142); verify("10000000000000000000000000000000000000000001", 0x1.cb2d6f618c879p+142);
@ -509,26 +882,29 @@ TEST_CASE("64bit.general") {
verify("45035996.273704985", 45035996.273704985); verify("45035996.273704985", 45035996.273704985);
verify("0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044501477170144022721148195934182639518696390927032912960468522194496444440421538910330590478162701758282983178260792422137401728773891892910553144148156412434867599762821265346585071045737627442980259622449029037796981144446145705102663115100318287949527959668236039986479250965780342141637013812613333119898765515451440315261253813266652951306000184917766328660755595837392240989947807556594098101021612198814605258742579179000071675999344145086087205681577915435923018910334964869420614052182892431445797605163650903606514140377217442262561590244668525767372446430075513332450079650686719491377688478005309963967709758965844137894433796621993967316936280457084866613206797017728916080020698679408551343728867675409720757232455434770912461317493580281734466552734375", 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044501477170144022721148195934182639518696390927032912960468522194496444440421538910330590478162701758282983178260792422137401728773891892910553144148156412434867599762821265346585071045737627442980259622449029037796981144446145705102663115100318287949527959668236039986479250965780342141637013812613333119898765515451440315261253813266652951306000184917766328660755595837392240989947807556594098101021612198814605258742579179000071675999344145086087205681577915435923018910334964869420614052182892431445797605163650903606514140377217442262561590244668525767372446430075513332450079650686719491377688478005309963967709758965844137894433796621993967316936280457084866613206797017728916080020698679408551343728867675409720757232455434770912461317493580281734466552734375); verify("0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044501477170144022721148195934182639518696390927032912960468522194496444440421538910330590478162701758282983178260792422137401728773891892910553144148156412434867599762821265346585071045737627442980259622449029037796981144446145705102663115100318287949527959668236039986479250965780342141637013812613333119898765515451440315261253813266652951306000184917766328660755595837392240989947807556594098101021612198814605258742579179000071675999344145086087205681577915435923018910334964869420614052182892431445797605163650903606514140377217442262561590244668525767372446430075513332450079650686719491377688478005309963967709758965844137894433796621993967316936280457084866613206797017728916080020698679408551343728867675409720757232455434770912461317493580281734466552734375", 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044501477170144022721148195934182639518696390927032912960468522194496444440421538910330590478162701758282983178260792422137401728773891892910553144148156412434867599762821265346585071045737627442980259622449029037796981144446145705102663115100318287949527959668236039986479250965780342141637013812613333119898765515451440315261253813266652951306000184917766328660755595837392240989947807556594098101021612198814605258742579179000071675999344145086087205681577915435923018910334964869420614052182892431445797605163650903606514140377217442262561590244668525767372446430075513332450079650686719491377688478005309963967709758965844137894433796621993967316936280457084866613206797017728916080020698679408551343728867675409720757232455434770912461317493580281734466552734375);
verify("0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072008890245868760858598876504231122409594654935248025624400092282356951787758888037591552642309780950434312085877387158357291821993020294379224223559819827501242041788969571311791082261043971979604000454897391938079198936081525613113376149842043271751033627391549782731594143828136275113838604094249464942286316695429105080201815926642134996606517803095075913058719846423906068637102005108723282784678843631944515866135041223479014792369585208321597621066375401613736583044193603714778355306682834535634005074073040135602968046375918583163124224521599262546494300836851861719422417646455137135420132217031370496583210154654068035397417906022589503023501937519773030945763173210852507299305089761582519159720757232455434770912461317493580281734466552734375", 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072008890245868760858598876504231122409594654935248025624400092282356951787758888037591552642309780950434312085877387158357291821993020294379224223559819827501242041788969571311791082261043971979604000454897391938079198936081525613113376149842043271751033627391549782731594143828136275113838604094249464942286316695429105080201815926642134996606517803095075913058719846423906068637102005108723282784678843631944515866135041223479014792369585208321597621066375401613736583044193603714778355306682834535634005074073040135602968046375918583163124224521599262546494300836851861719422417646455137135420132217031370496583210154654068035397417906022589503023501937519773030945763173210852507299305089761582519159720757232455434770912461317493580281734466552734375); verify("0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072008890245868760858598876504231122409594654935248025624400092282356951787758888037591552642309780950434312085877387158357291821993020294379224223559819827501242041788969571311791082261043971979604000454897391938079198936081525613113376149842043271751033627391549782731594143828136275113838604094249464942286316695429105080201815926642134996606517803095075913058719846423906068637102005108723282784678843631944515866135041223479014792369585208321597621066375401613736583044193603714778355306682834535634005074073040135602968046375918583163124224521599262546494300836851861719422417646455137135420132217031370496583210154654068035397417906022589503023501937519773030945763173210852507299305089761582519159720757232455434770912461317493580281734466552734375", 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072008890245868760858598876504231122409594654935248025624400092282356951787758888037591552642309780950434312085877387158357291821993020294379224223559819827501242041788969571311791082261043971979604000454897391938079198936081525613113376149842043271751033627391549782731594143828136275113838604094249464942286316695429105080201815926642134996606517803095075913058719846423906068637102005108723282784678843631944515866135041223479014792369585208321597621066375401613736583044193603714778355306682834535634005074073040135602968046375918583163124224521599262546494300836851861719422417646455137135420132217031370496583210154654068035397417906022589503023501937519773030945763173210852507299305089761582519159720757232455434770912461317493580281734466552734375);
verify("1438456663141390273526118207642235581183227845246331231162636653790368152091394196930365828634687637948157940776599182791387527135353034738357134110310609455693900824193549772792016543182680519740580354365467985440183598701312257624545562331397018329928613196125590274187720073914818062530830316533158098624984118889298281371812288789537310599037529113415438738954894752124724983067241108764488346454376699018673078404751121414804937224240805993123816932326223683090770561597570457793932985826162604255884529134126396282202126526253389383421806727954588525596114379801269094096329805054803089299736996870951258573010877404407451953846698609198213926882692078557033228265259305481198526059813164469187586693257335779522020407645498684263339921905227556616698129967412891282231685504660671277927198290009824680186319750978665734576683784255802269708917361719466043175201158849097881370477111850171579869056016061666173029059588433776015644439705050377554277696143928278093453792803846252715966016733222646442382892123940052441346822429721593884378212558701004356924243030059517489346646577724622498919752597382095222500311124181823512251071356181769376577651390028297796156208815375089159128394945710515861334486267101797497111125909272505194792870889617179758703442608016143343262159998149700606597792535574457560429226974273443630323818747730771316763398572110874959981923732463076884528677392654150010269822239401993427482376513231389212353583573566376915572650916866553612366187378959554983566712767093372906030188976220169058025354973622211666504549316958271880975697143546564469806791358707318873075708383345004090151974068325838177531266954177406661392229801349994695941509935655355652985723782153570084089560139142231.738475042362596875449154552392299548947138162081694168675340677843807613129780449323363759027012972466987370921816813162658754726545121090545507240267000456594786540949605260722461937870630634874991729398208026467698131898691830012167897399682179601734569071423681e-733", std::numeric_limits<double>::infinity()); verify("1438456663141390273526118207642235581183227845246331231162636653790368152091394196930365828634687637948157940776599182791387527135353034738357134110310609455693900824193549772792016543182680519740580354365467985440183598701312257624545562331397018329928613196125590274187720073914818062530830316533158098624984118889298281371812288789537310599037529113415438738954894752124724983067241108764488346454376699018673078404751121414804937224240805993123816932326223683090770561597570457793932985826162604255884529134126396282202126526253389383421806727954588525596114379801269094096329805054803089299736996870951258573010877404407451953846698609198213926882692078557033228265259305481198526059813164469187586693257335779522020407645498684263339921905227556616698129967412891282231685504660671277927198290009824680186319750978665734576683784255802269708917361719466043175201158849097881370477111850171579869056016061666173029059588433776015644439705050377554277696143928278093453792803846252715966016733222646442382892123940052441346822429721593884378212558701004356924243030059517489346646577724622498919752597382095222500311124181823512251071356181769376577651390028297796156208815375089159128394945710515861334486267101797497111125909272505194792870889617179758703442608016143343262159998149700606597792535574457560429226974273443630323818747730771316763398572110874959981923732463076884528677392654150010269822239401993427482376513231389212353583573566376915572650916866553612366187378959554983566712767093372906030188976220169058025354973622211666504549316958271880975697143546564469806791358707318873075708383345004090151974068325838177531266954177406661392229801349994695941509935655355652985723782153570084089560139142231.738475042362596875449154552392299548947138162081694168675340677843807613129780449323363759027012972466987370921816813162658754726545121090545507240267000456594786540949605260722461937870630634874991729398208026467698131898691830012167897399682179601734569071423681e-733", std::numeric_limits<double>::infinity(), std::errc::result_out_of_range);
verify("-2240084132271013504.131248280843119943687942846658579428", -0x1.f1660a65b00bfp+60); verify("-2240084132271013504.131248280843119943687942846658579428", -0x1.f1660a65b00bfp+60);
} }
TEST_CASE("64bit.decimal_point") { TEST_CASE("64bit.decimal_point") {
fast_float::parse_options options{}; constexpr auto options = []{
options.decimal_point = ','; fast_float::parse_options ret{};
ret.decimal_point = ',';
return ret;
}();
// infinities // infinities
verify_options("1,8e308", std::numeric_limits<double>::infinity()); verify_options("1,8e308", std::numeric_limits<double>::infinity(), std::errc::result_out_of_range);
verify_options("1,832312213213213232132132143451234453123412321321312e308", std::numeric_limits<double>::infinity()); verify_options("1,832312213213213232132132143451234453123412321321312e308", std::numeric_limits<double>::infinity(), std::errc::result_out_of_range);
verify_options("2e30000000000000000", std::numeric_limits<double>::infinity()); verify_options("2e30000000000000000", std::numeric_limits<double>::infinity(), std::errc::result_out_of_range);
verify_options("2e3000", std::numeric_limits<double>::infinity()); verify_options("2e3000", std::numeric_limits<double>::infinity(), std::errc::result_out_of_range);
verify_options("1,9e308", std::numeric_limits<double>::infinity()); verify_options("1,9e308", std::numeric_limits<double>::infinity(), std::errc::result_out_of_range);
// finites // finites
verify_options("-2,2222222222223e-322",-0x1.68p-1069); verify_options("-2,2222222222223e-322",-0x1.68p-1069);
verify_options("9007199254740993,0", 0x1p+53); verify_options("9007199254740993,0", 0x1p+53);
verify_options("860228122,6654514319E+90", 0x1.92bb20990715fp+328); verify_options("860228122,6654514319E+90", 0x1.92bb20990715fp+328);
verify_options(append_zeros("9007199254740993,0",1000), 0x1p+53); verify_options_runtime(append_zeros("9007199254740993,0",1000), 0x1p+53);
verify_options("1,1920928955078125e-07", 1.1920928955078125e-07); verify_options("1,1920928955078125e-07", 1.1920928955078125e-07);
verify_options("9355950000000000000,00000000000000000000000000000000001844674407370955161600000184467440737095516161844674407370955161407370955161618446744073709551616000184467440737095516166000001844674407370955161618446744073709551614073709551616184467440737095516160001844674407370955161601844674407370955674451616184467440737095516140737095516161844674407370955161600018446744073709551616018446744073709551611616000184467440737095001844674407370955161600184467440737095516160018446744073709551168164467440737095516160001844073709551616018446744073709551616184467440737095516160001844674407536910751601611616000184467440737095001844674407370955161600184467440737095516160018446744073709551616184467440737095516160001844955161618446744073709551616000184467440753691075160018446744073709",0x1.03ae05e8fca1cp+63); verify_options("9355950000000000000,00000000000000000000000000000000001844674407370955161600000184467440737095516161844674407370955161407370955161618446744073709551616000184467440737095516166000001844674407370955161618446744073709551614073709551616184467440737095516160001844674407370955161601844674407370955674451616184467440737095516140737095516161844674407370955161600018446744073709551616018446744073709551611616000184467440737095001844674407370955161600184467440737095516160018446744073709551168164467440737095516160001844073709551616018446744073709551616184467440737095516160001844674407536910751601611616000184467440737095001844674407370955161600184467440737095516160018446744073709551616184467440737095516160001844955161618446744073709551616000184467440753691075160018446744073709",0x1.03ae05e8fca1cp+63);
verify_options("2,22507385850720212418870147920222032907240528279439037814303133837435107319244194686754406432563881851382188218502438069999947733013005649884107791928741341929297200970481951993067993290969042784064731682041565926728632933630474670123316852983422152744517260835859654566319282835244787787799894310779783833699159288594555213714181128458251145584319223079897504395086859412457230891738946169368372321191373658977977723286698840356390251044443035457396733706583981055420456693824658413747607155981176573877626747665912387199931904006317334709003012790188175203447190250028061277777916798391090578584006464715943810511489154282775041174682194133952466682503431306181587829379004205392375072083366693241580002758391118854188641513168478436313080237596295773983001708984375e-308", 0x1.0000000000002p-1022); verify_options("2,22507385850720212418870147920222032907240528279439037814303133837435107319244194686754406432563881851382188218502438069999947733013005649884107791928741341929297200970481951993067993290969042784064731682041565926728632933630474670123316852983422152744517260835859654566319282835244787787799894310779783833699159288594555213714181128458251145584319223079897504395086859412457230891738946169368372321191373658977977723286698840356390251044443035457396733706583981055420456693824658413747607155981176573877626747665912387199931904006317334709003012790188175203447190250028061277777916798391090578584006464715943810511489154282775041174682194133952466682503431306181587829379004205392375072083366693241580002758391118854188641513168478436313080237596295773983001708984375e-308", 0x1.0000000000002p-1022);
@ -582,24 +958,25 @@ TEST_CASE("32bit.inf") {
verify("-infinity", -std::numeric_limits<float>::infinity()); verify("-infinity", -std::numeric_limits<float>::infinity());
verify("inf", std::numeric_limits<float>::infinity()); verify("inf", std::numeric_limits<float>::infinity());
verify("-inf", -std::numeric_limits<float>::infinity()); verify("-inf", -std::numeric_limits<float>::infinity());
verify("1234456789012345678901234567890e9999999999999999999999999999", std::numeric_limits<float>::infinity()); verify("1234456789012345678901234567890e9999999999999999999999999999", std::numeric_limits<float>::infinity(), std::errc::result_out_of_range);
verify("2e3000", std::numeric_limits<float>::infinity()); verify("2e3000", std::numeric_limits<float>::infinity(), std::errc::result_out_of_range);
verify("3.5028234666e38", std::numeric_limits<float>::infinity()); verify("3.5028234666e38", std::numeric_limits<float>::infinity(), std::errc::result_out_of_range);
} }
TEST_CASE("32bit.general") { TEST_CASE("32bit.general") {
verify("-1e-999", -0.0f, std::errc::result_out_of_range);
verify("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125", 0x1.2ced3p+0f); verify("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125", 0x1.2ced3p+0f);
verify("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125e-38", 0x1.fffff8p-127f); verify("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125e-38", 0x1.fffff8p-127f);
verify(append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655), 0x1.2ced3p+0f); verify_runtime(append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655), 0x1.2ced3p+0f);
verify(append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656), 0x1.2ced3p+0f); verify_runtime(append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656), 0x1.2ced3p+0f);
verify(append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000), 0x1.2ced3p+0f); verify_runtime(append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000), 0x1.2ced3p+0f);
std::string test_string; std::string test_string;
test_string = append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655) + std::string("e-38"); test_string = append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655) + std::string("e-38");
verify(test_string, 0x1.fffff8p-127f); verify_runtime(test_string, 0x1.fffff8p-127f);
test_string = append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656) + std::string("e-38"); test_string = append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656) + std::string("e-38");
verify(test_string, 0x1.fffff8p-127f); verify_runtime(test_string, 0x1.fffff8p-127f);
test_string = append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000) + std::string("e-38"); test_string = append_zeros("1.1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000) + std::string("e-38");
verify(test_string, 0x1.fffff8p-127f); verify_runtime(test_string, 0x1.fffff8p-127f);
verify32(1.00000006e+09f); verify32(1.00000006e+09f);
verify32(1.4012984643e-45f); verify32(1.4012984643e-45f);
verify32(1.1754942107e-38f); verify32(1.1754942107e-38f);
@ -653,7 +1030,7 @@ TEST_CASE("32bit.general") {
verify("3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679", 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679f); verify("3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679", 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679f);
verify("2.3509887016445750159374730744444913556373311135441750430175034126e-38", 2.3509887016445750159374730744444913556373311135441750430175034126e-38f); verify("2.3509887016445750159374730744444913556373311135441750430175034126e-38", 2.3509887016445750159374730744444913556373311135441750430175034126e-38f);
verify("1", 1.f); verify("1", 1.f);
verify("7.0060e-46", 0.f); verify("7.0060e-46", 0.f, std::errc::result_out_of_range);
verify("3.4028234664e38", 0x1.fffffep+127f); verify("3.4028234664e38", 0x1.fffffep+127f);
verify("3.4028234665e38", 0x1.fffffep+127f); verify("3.4028234665e38", 0x1.fffffep+127f);
verify("3.4028234666e38", 0x1.fffffep+127f); verify("3.4028234666e38", 0x1.fffffep+127f);
@ -664,25 +1041,28 @@ TEST_CASE("32bit.general") {
} }
TEST_CASE("32bit.decimal_point") { TEST_CASE("32bit.decimal_point") {
fast_float::parse_options options{}; constexpr auto options = [] {
options.decimal_point = ','; fast_float::parse_options ret{};
ret.decimal_point = ',';
return ret;
}();
// infinity // infinity
verify_options("3,5028234666e38", std::numeric_limits<float>::infinity()); verify_options("3,5028234666e38", std::numeric_limits<float>::infinity(), std::errc::result_out_of_range);
// finites // finites
verify_options("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125", 0x1.2ced3p+0f); verify_options("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125", 0x1.2ced3p+0f);
verify_options("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125e-38", 0x1.fffff8p-127f); verify_options("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125e-38", 0x1.fffff8p-127f);
verify_options(append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655), 0x1.2ced3p+0f); verify_options_runtime(append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655), 0x1.2ced3p+0f);
verify_options(append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656), 0x1.2ced3p+0f); verify_options_runtime(append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656), 0x1.2ced3p+0f);
verify_options(append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000), 0x1.2ced3p+0f); verify_options_runtime(append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000), 0x1.2ced3p+0f);
std::string test_string; std::string test_string;
test_string = append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655) + std::string("e-38"); test_string = append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",655) + std::string("e-38");
verify_options(test_string, 0x1.fffff8p-127f); verify_options_runtime(test_string, 0x1.fffff8p-127f);
test_string = append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656) + std::string("e-38"); test_string = append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",656) + std::string("e-38");
verify_options(test_string, 0x1.fffff8p-127f); verify_options_runtime(test_string, 0x1.fffff8p-127f);
test_string = append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000) + std::string("e-38"); test_string = append_zeros("1,1754941406275178592461758986628081843312458647327962400313859427181746759860647699724722770042717456817626953125",1000) + std::string("e-38");
verify_options(test_string, 0x1.fffff8p-127f); verify_options_runtime(test_string, 0x1.fffff8p-127f);
verify_options("1,1754943508e-38", 1.1754943508e-38f); verify_options("1,1754943508e-38", 1.1754943508e-38f);
verify_options("30219,0830078125", 30219.0830078125f); verify_options("30219,0830078125", 30219.0830078125f);
verify_options("1,1754947011469036e-38", 0x1.000006p-126f); verify_options("1,1754947011469036e-38", 0x1.000006p-126f);
@ -693,7 +1073,7 @@ TEST_CASE("32bit.decimal_point") {
verify_options("3,1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679", 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679f); verify_options("3,1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679", 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679f);
verify_options("2,3509887016445750159374730744444913556373311135441750430175034126e-38", 2.3509887016445750159374730744444913556373311135441750430175034126e-38f); verify_options("2,3509887016445750159374730744444913556373311135441750430175034126e-38", 2.3509887016445750159374730744444913556373311135441750430175034126e-38f);
verify_options("1", 1.f); verify_options("1", 1.f);
verify_options("7,0060e-46", 0.f); verify_options("7,0060e-46", 0.f, std::errc::result_out_of_range);
verify_options("3,4028234664e38", 0x1.fffffep+127f); verify_options("3,4028234664e38", 0x1.fffffep+127f);
verify_options("3,4028234665e38", 0x1.fffffep+127f); verify_options("3,4028234665e38", 0x1.fffffep+127f);
verify_options("3,4028234666e38", 0x1.fffffep+127f); verify_options("3,4028234666e38", 0x1.fffffep+127f);

View File

@ -0,0 +1,5 @@
add_executable(bloaty main.cpp a1.cpp a2.cpp a3.cpp a4.cpp a4.cpp a5.cpp a6.cpp a7.cpp a8.cpp a9.cpp a10.cpp)
target_link_libraries(bloaty PUBLIC fast_float)
add_executable(bloatyref main_ref.cpp)
target_link_libraries(bloatyref PUBLIC fast_float)

View File

@ -0,0 +1,9 @@
#include "fast_float/fast_float.h"
double get1(const char* input) {
double result_value;
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) { return 1; }
return result_value;
}

View File

@ -0,0 +1,9 @@
#include "fast_float/fast_float.h"
double get10(const char* input) {
double result_value;
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) { return 10; }
return result_value;
}

View File

@ -0,0 +1,9 @@
#include "fast_float/fast_float.h"
double get2(const char* input) {
double result_value;
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) { return 2; }
return result_value;
}

View File

@ -0,0 +1,9 @@
#include "fast_float/fast_float.h"
double get3(const char* input) {
double result_value;
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) { return 3; }
return result_value;
}

View File

@ -0,0 +1,9 @@
#include "fast_float/fast_float.h"
double get4(const char* input) {
double result_value;
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) { return 4; }
return result_value;
}

View File

@ -0,0 +1,9 @@
#include "fast_float/fast_float.h"
double get5(const char* input) {
double result_value;
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) { return 5; }
return result_value;
}

View File

@ -0,0 +1,9 @@
#include "fast_float/fast_float.h"
double get6(const char* input) {
double result_value;
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) { return 6; }
return result_value;
}

View File

@ -0,0 +1,9 @@
#include "fast_float/fast_float.h"
double get7(const char* input) {
double result_value;
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) { return 7; }
return result_value;
}

View File

@ -0,0 +1,9 @@
#include "fast_float/fast_float.h"
double get8(const char* input) {
double result_value;
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) { return 8; }
return result_value;
}

View File

@ -0,0 +1,9 @@
#include "fast_float/fast_float.h"
double get9(const char* input) {
double result_value;
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) { return 9; }
return result_value;
}

View File

@ -0,0 +1,19 @@
double get1(const char* input);
double get2(const char* input);
double get3(const char* input);
double get4(const char* input);
double get5(const char* input);
double get6(const char* input);
double get7(const char* input);
double get8(const char* input);
double get9(const char* input);
double get10(const char* input);
int main(int arg, char** argv) {
double x = get1(argv[0]) + get2(argv[0]) + get3(argv[0]) + get4(argv[0]) + get5(argv[0])
+ get6(argv[0]) + get7(argv[0]) + get8(argv[0]) + get9(argv[0]) + get10(argv[0]);
return int(x);
}

View File

@ -0,0 +1,13 @@
#include "fast_float/fast_float.h"
double get(const char * input) {
double result_value;
auto result = fast_float::from_chars(input, input + strlen(input), result_value);
if (result.ec != std::errc()) { return 10; }
return result_value;
}
int main(int arg, char** argv) {
double x = get(argv[0]);
return int(x);
}

View File

@ -3,7 +3,7 @@
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <system_error> #include <system_error>
int main() { int main() {
const std::string input = "3,1416 xyz "; const std::string input = "3,1416 xyz ";
double result; double result;

View File

@ -3,12 +3,78 @@
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <system_error> #include <system_error>
bool many() {
const std::string input = "234532.3426362,7869234.9823,324562.645";
double result;
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
if(answer.ec != std::errc()) { return false; }
if(result != 234532.3426362) { 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 != 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;
}
void many_loop() {
const std::string input = "234532.3426362,7869234.9823,324562.645";
double result;
const char* pointer = input.data();
const char* end_pointer = input.data() + input.size();
while(pointer < end_pointer) {
auto answer = fast_float::from_chars(pointer, end_pointer, result);
if(answer.ec != std::errc()) {
std::cerr << "error while parsing" << std::endl;
break;
}
std::cout << "parsed: " << result << std::endl;
pointer = answer.ptr;
if ((answer.ptr < end_pointer) && (*pointer == ',')) {
pointer++;
}
}
}
#if FASTFLOAT_IS_CONSTEXPR
// consteval forces compile-time evaluation of the function in C++20.
consteval double parse(std::string_view input) {
double result;
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
if(answer.ec != std::errc()) { return -1.0; }
return result;
}
// This function should compile to a function which
// merely returns 3.1415.
constexpr double constexptest() {
return parse("3.1415 input");
}
#endif
int main() { int main() {
const std::string input = "3.1416 xyz "; const 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()) || ((result != 3.1416))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; } if((answer.ec != std::errc()) || ((result != 3.1416))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
std::cout << "parsed the number " << result << std::endl; std::cout << "parsed the number " << result << std::endl;
if(!many()) {
printf("Bug\n");
return EXIT_FAILURE;
}
many_loop();
#if FASTFLOAT_IS_CONSTEXPR
if constexpr(constexptest() != 3.1415) {
return EXIT_FAILURE;
}
#endif
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -30,7 +30,10 @@ void allvalues() {
const char *string_end = to_string(v, buffer); const char *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);
if (result.ec != std::errc()) { // Starting with version 4.0 for fast_float, we return result_out_of_range if the
// value is either too small (too close to zero) or too large (effectively infinity).
// So std::errc::result_out_of_range is normal for 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();
} }
@ -46,7 +49,7 @@ void allvalues() {
} else if (result_value != v) { } else if (result_value != v) {
std::cerr << "no match ? " << buffer << std::endl; std::cerr << "no match ? " << buffer << 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;
abort(); abort();
} }

View File

@ -21,7 +21,7 @@ bool basic_test_64bit(std::string vals, double val) {
double result_value; double result_value;
auto result = fast_float::from_chars(vals.data(), vals.data() + vals.size(), auto result = fast_float::from_chars(vals.data(), vals.data() + vals.size(),
result_value); result_value);
if (result.ec != std::errc()) { if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
std::cerr << " I could not parse " << vals << std::endl; std::cerr << " I could not parse " << vals << std::endl;
return false; return false;
} }
@ -30,11 +30,11 @@ bool basic_test_64bit(std::string vals, double val) {
std::cerr << vals << std::endl; std::cerr << vals << std::endl;
std::cerr << "not nan" << result_value << std::endl; std::cerr << "not nan" << result_value << std::endl;
return false; return false;
} }
} else if(copysign(1,result_value) != copysign(1,val)) { } else if(copysign(1,result_value) != copysign(1,val)) {
std::cerr << "I got " << std::hexfloat << result_value << " but I was expecting " << val std::cerr << "I got " << std::hexfloat << result_value << " but I was expecting " << val
<< std::endl; << 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 << " but I was expecting " << val

View File

@ -8,7 +8,7 @@
#include <limits> #include <limits>
#include <stdexcept> #include <stdexcept>
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__)
// 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.
@ -73,7 +73,7 @@ bool allvalues() {
} }
uint32_t word = uint32_t(w); uint32_t word = uint32_t(w);
memcpy(&v, &word, sizeof(v)); memcpy(&v, &word, sizeof(v));
if(std::isfinite(v)) { if(std::isfinite(v)) {
float nextf = std::nextafterf(v, INFINITY); float nextf = std::nextafterf(v, INFINITY);
if(copysign(1,v) != copysign(1,nextf)) { continue; } if(copysign(1,v) != copysign(1,nextf)) { continue; }
if(!std::isfinite(nextf)) { continue; } if(!std::isfinite(nextf)) { continue; }
@ -90,7 +90,10 @@ bool allvalues() {
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);
if (result.ec != std::errc()) { // Starting with version 4.0 for fast_float, we return result_out_of_range if the
// value is either too small (too close to zero) or too large (effectively infinity).
// So std::errc::result_out_of_range is normal for 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;
} }
@ -120,7 +123,7 @@ bool allvalues() {
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;
} }
@ -132,8 +135,8 @@ 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__) || defined(sun) || defined(__sun)
if (!Assertion) { std::cerr << "Omitting hard falure on msys/cygwin/sun systems."; } 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
} }

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.15) cmake_minimum_required(VERSION 3.15)
project(test_simdjson_install VERSION 0.1.0 LANGUAGES CXX) project(test_install VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -14,7 +14,7 @@ find_package(FastFloat REQUIRED)
file(WRITE main.cpp " file(WRITE main.cpp "
#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 \"; const std::string input = \"3.1416 xyz \";
double result; double result;
@ -27,4 +27,4 @@ int main() {
add_executable(repro main.cpp) add_executable(repro main.cpp)
target_link_libraries(repro PUBLIC FastFloat::fast_float) target_link_libraries(repro PUBLIC FastFloat::fast_float)

View File

@ -29,7 +29,10 @@ void allvalues() {
const char *string_end = to_string(v, buffer); const char *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);
if (result.ec != std::errc()) { // Starting with version 4.0 for fast_float, we return result_out_of_range if the
// value is either too small (too close to zero) or too large (effectively infinity).
// So std::errc::result_out_of_range is normal for 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();
} }
@ -46,7 +49,7 @@ void allvalues() {
} 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;
abort(); abort();
} }

View File

@ -28,7 +28,10 @@ void all_32bit_values() {
const char *string_end = to_string(v, buffer); const char *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);
if (result.ec != std::errc()) { // Starting with version 4.0 for fast_float, we return result_out_of_range if the
// value is either too small (too close to zero) or too large (effectively infinity).
// So std::errc::result_out_of_range is normal for 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();
} }
@ -49,7 +52,7 @@ void all_32bit_values() {
} else if (result_value != v) { } else if (result_value != v) {
std::cerr << "no match ? " << buffer << std::endl; std::cerr << "no match ? " << buffer << 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;
abort(); abort();
} }

View File

@ -27,9 +27,9 @@ static fast_float::value128 g_lehmer64_state;
* Society 68.225 (1999): 249-260. * Society 68.225 (1999): 249-260.
*/ */
static inline void lehmer64_seed(uint64_t seed) { static inline void lehmer64_seed(uint64_t seed) {
g_lehmer64_state.high = 0; g_lehmer64_state.high = 0;
g_lehmer64_state.low = seed; g_lehmer64_state.low = seed;
} }
static inline uint64_t lehmer64() { static inline uint64_t lehmer64() {
@ -56,12 +56,16 @@ void random_values(size_t N) {
const char *string_end = to_string(v, buffer); const char *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);
if (result.ec != std::errc()) { // Starting with version 4.0 for fast_float, we return result_out_of_range if the
// value is either too small (too close to zero) or too large (effectively infinity).
// So std::errc::result_out_of_range is normal for 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) {
abort(); abort();
} }
continue;
} }
if (std::isnan(v)) { if (std::isnan(v)) {
if (!std::isnan(result_value)) { if (!std::isnan(result_value)) {
@ -79,7 +83,7 @@ void random_values(size_t N) {
} else if (result_value != v) { } else if (result_value != v) {
std::cerr << "no match ? '" << buffer << "'" << std::endl; std::cerr << "no match ? '" << buffer << "'" << 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;
errors++; errors++;
if (errors > 10) { if (errors > 10) {

View File

@ -22,7 +22,7 @@ bool test() {
while((begin < end) && (std::isspace(*begin))) { begin++; } while((begin < end) && (std::isspace(*begin))) { begin++; }
auto result = fast_float::from_chars(begin, end, auto result = fast_float::from_chars(begin, end,
result_value); result_value);
if (result.ec != std::errc()) { 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;
@ -40,7 +40,7 @@ bool test() {
} }
if(begin != end) { if(begin != end) {
std::cerr << " bad ending " << std::endl; std::cerr << " bad ending " << std::endl;
return false; return false;
} }
return true; return true;
} }

View File

@ -105,7 +105,7 @@ bool tester() {
double result_value; double result_value;
auto result = auto result =
fast_float::from_chars(to_be_parsed.data(), to_be_parsed.data() + to_be_parsed.size(), result_value); fast_float::from_chars(to_be_parsed.data(), to_be_parsed.data() + to_be_parsed.size(), result_value);
if (result.ec != std::errc()) { 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

@ -29,9 +29,9 @@ static fast_float::value128 g_lehmer64_state;
* Society 68.225 (1999): 249-260. * Society 68.225 (1999): 249-260.
*/ */
static inline void lehmer64_seed(uint64_t seed) { static inline void lehmer64_seed(uint64_t seed) {
g_lehmer64_state.high = 0; g_lehmer64_state.high = 0;
g_lehmer64_state.low = seed; g_lehmer64_state.low = seed;
} }
static inline uint64_t lehmer64() { static inline uint64_t lehmer64() {
@ -59,12 +59,16 @@ void random_values(size_t N) {
const char *string_end = to_string(v, buffer); const char *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);
if (result.ec != std::errc()) { // Starting with version 4.0 for fast_float, we return result_out_of_range if the
// value is either too small (too close to zero) or too large (effectively infinity).
// So std::errc::result_out_of_range is normal for 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) {
abort(); abort();
} }
continue;
} }
if (std::isnan(v)) { if (std::isnan(v)) {
if (!std::isnan(result_value)) { if (!std::isnan(result_value)) {
@ -82,7 +86,7 @@ void random_values(size_t N) {
} else if (result_value != v) { } else if (result_value != v) {
std::cerr << "no match ? " << buffer << std::endl; std::cerr << "no match ? " << buffer << 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;
errors++; errors++;
if (errors > 10) { if (errors > 10) {

View File

@ -101,7 +101,12 @@ size_t build_random_string(RandomEngine &rand, char *buffer) {
if (i == size_t(location_of_decimal_separator)) { if (i == size_t(location_of_decimal_separator)) {
buffer[pos++] = '.'; buffer[pos++] = '.';
} }
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.
while(i == 0 && 1 != size_t(location_of_decimal_separator) && buffer[pos] == '0') {
buffer[pos] = char(rand.next_digit() + '0');
}
pos++;
} }
if (rand.next_bool()) { if (rand.next_bool()) {
if (rand.next_bool()) { if (rand.next_bool()) {
@ -178,7 +183,7 @@ bool tester(uint64_t seed, size_t volume) {
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()) { 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;
@ -201,7 +206,7 @@ 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()) { 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;

View File

@ -0,0 +1,123 @@
/**
* 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 <iostream>
#include <string>
#include <vector>
bool eddelbuettel() {
std::vector<std::string> inputs = {"infinity",
" \r\n\t\f\v3.16227766016838 \r\n\t\f\v",
" \r\n\t\f\v3 \r\n\t\f\v",
" 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"};
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}};
for (size_t i = 0; i < inputs.size(); i++) {
std::string &input = inputs[i];
std::pair<bool, double> expected = expected_results[i];
double result;
// answer contains a error code and a pointer to the end of the
// parsed region (on success).
auto answer = fast_float::from_chars(input.data(),
input.data() + input.size(), result);
if (answer.ec != std::errc()) {
std::cout << "could not parse" << std::endl;
if (expected.first) {
return false;
}
continue;
}
bool non_space_trailing_content = false;
if (answer.ptr != input.data() + input.size()) {
// check that there is no content left
for (const char *leftover = answer.ptr;
leftover != input.data() + input.size(); leftover++) {
if (!fast_float::is_space(uint8_t(*leftover))) {
non_space_trailing_content = true;
break;
}
}
}
if (non_space_trailing_content) {
std::cout << "found trailing content " << std::endl;
}
if (non_space_trailing_content) {
if (!expected.first) {
continue;
} else {
return false;
}
}
std::cout << "parsed " << result << std::endl;
if (!expected.first) {
return false;
}
if (result != expected.second) {
if (std::isnan(result) && std::isnan(expected.second)) {
continue;
}
std::cout << "results do not match. Expected "<< expected.second << std::endl;
return false;
}
}
return true;
}
int main() {
if (!eddelbuettel()) {
printf("Bug.\n");
return EXIT_FAILURE;
}
printf("All ok.\n");
return EXIT_SUCCESS;
}

View File

@ -97,7 +97,12 @@ size_t build_random_string(RandomEngine &rand, char *buffer) {
if (i == size_t(location_of_decimal_separator)) { if (i == size_t(location_of_decimal_separator)) {
buffer[pos++] = '.'; buffer[pos++] = '.';
} }
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.
while(i == 0 && 1 != size_t(location_of_decimal_separator) && buffer[pos] == '0') {
buffer[pos] = char(rand.next_digit() + '0');
}
pos++;
} }
if (rand.next_bool()) { if (rand.next_bool()) {
if (rand.next_bool()) { if (rand.next_bool()) {
@ -174,7 +179,7 @@ bool tester(uint64_t seed, size_t volume) {
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()) { 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;
@ -197,7 +202,7 @@ 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()) { 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;

View File

@ -44,8 +44,8 @@ float cygwin_strtof_l(const char* start, char** end) {
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__) || defined(sun) || defined(__sun)
if (!Assertion) { std::cerr << "Omitting hard falure on msys/cygwin/sun systems."; } 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
} }
@ -62,6 +62,7 @@ template <typename T>
bool test() { bool test() {
std::string input = "0.1 1e1000 100000 3.14159265359 -1e-500 001 1e01 1e0000001 -inf"; std::string input = "0.1 1e1000 100000 3.14159265359 -1e-500 001 1e01 1e0000001 -inf";
std::vector<T> answers = {T(0.1), std::numeric_limits<T>::infinity(), 100000, T(3.14159265359), -0.0, 1, 10, 10, -std::numeric_limits<T>::infinity()}; std::vector<T> answers = {T(0.1), std::numeric_limits<T>::infinity(), 100000, T(3.14159265359), -0.0, 1, 10, 10, -std::numeric_limits<T>::infinity()};
std::vector<std::errc> expected_ec = {std::errc(), std::errc::result_out_of_range, std::errc(), std::errc(), std::errc::result_out_of_range, std::errc(), std::errc(), std::errc(), std::errc()};
const char * begin = input.data(); const char * begin = input.data();
const char * end = input.data() + input.size(); const char * end = input.data() + input.size();
for(size_t i = 0; i < answers.size(); i++) { for(size_t i = 0; i < answers.size(); i++) {
@ -69,7 +70,7 @@ bool test() {
while((begin < end) && (std::isspace(*begin))) { begin++; } while((begin < end) && (std::isspace(*begin))) { begin++; }
auto result = fast_float::from_chars(begin, end, auto result = fast_float::from_chars(begin, end,
result_value); result_value);
if (result.ec != std::errc()) { if (result.ec != expected_ec[i]) {
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;
@ -84,7 +85,7 @@ bool test() {
} }
if(begin != end) { if(begin != end) {
std::cerr << " bad ending " << std::endl; std::cerr << " bad ending " << std::endl;
return false; return false;
} }
return true; return true;
} }
@ -238,7 +239,7 @@ bool partow_test() {
T result_value; T result_value;
auto result = fast_float::from_chars(st.data(), st.data() + st.size(), auto result = fast_float::from_chars(st.data(), st.data() + st.size(),
result_value); result_value);
if (result.ec != std::errc()) { if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
printf("parsing %.*s\n", int(st.size()), st.data()); printf("parsing %.*s\n", int(st.size()), st.data());
std::cerr << " I could not parse " << std::endl; std::cerr << " I could not parse " << std::endl;
return false; return false;
@ -269,7 +270,7 @@ int main() {
std::cout << "32 bits checks" << std::endl; std::cout << "32 bits checks" << std::endl;
Assert(partow_test<float>()); Assert(partow_test<float>());
Assert(test<float>()); Assert(test<float>());
std::cout << "64 bits checks" << std::endl; std::cout << "64 bits checks" << std::endl;
Assert(partow_test<double>()); Assert(partow_test<double>());
Assert(test<double>()); Assert(test<double>());