floatium 0.11.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- floatium-0.11.0/.github/workflows/ci.yml +46 -0
- floatium-0.11.0/.github/workflows/cpython-suite.yml +44 -0
- floatium-0.11.0/.github/workflows/wheels.yml +75 -0
- floatium-0.11.0/.gitignore +15 -0
- floatium-0.11.0/CMakeLists.txt +131 -0
- floatium-0.11.0/DIFFERENCES.md +88 -0
- floatium-0.11.0/INTERNALS.md +83 -0
- floatium-0.11.0/LICENSE +32 -0
- floatium-0.11.0/PKG-INFO +255 -0
- floatium-0.11.0/README.md +220 -0
- floatium-0.11.0/bench/__init__.py +0 -0
- floatium-0.11.0/bench/bench_format.py +50 -0
- floatium-0.11.0/bench/bench_json.py +43 -0
- floatium-0.11.0/bench/bench_ns_per_op.py +148 -0
- floatium-0.11.0/bench/bench_parse.py +50 -0
- floatium-0.11.0/bench/bench_repr.py +42 -0
- floatium-0.11.0/bench/corpora.py +78 -0
- floatium-0.11.0/bench/results/.gitkeep +0 -0
- floatium-0.11.0/bench/run_all.sh +40 -0
- floatium-0.11.0/floatium/__init__.py +94 -0
- floatium-0.11.0/floatium/_autopatch.py +57 -0
- floatium-0.11.0/floatium/_ext.pyi +20 -0
- floatium-0.11.0/floatium/py.typed +0 -0
- floatium-0.11.0/floatium.pth +1 -0
- floatium-0.11.0/pyproject.toml +80 -0
- floatium-0.11.0/src/backends/fast_float_parse.cc +24 -0
- floatium-0.11.0/src/backends/fmt_format.cc +30 -0
- floatium-0.11.0/src/backends/fmt_opt_format.cc +34 -0
- floatium-0.11.0/src/backends/registry.cc +87 -0
- floatium-0.11.0/src/backends/stock_format.cc +56 -0
- floatium-0.11.0/src/backends/stock_parse.cc +30 -0
- floatium-0.11.0/src/common/backend.h +76 -0
- floatium-0.11.0/src/common/format_short.h +50 -0
- floatium-0.11.0/src/cpython_adapter/fast_float_strtod.cc +67 -0
- floatium-0.11.0/src/cpython_adapter/fmt_dtoa.cc +173 -0
- floatium-0.11.0/src/cpython_adapter/fmt_opt_dtoa.cc +249 -0
- floatium-0.11.0/src/ext.cc +127 -0
- floatium-0.11.0/src/format_short.cc +231 -0
- floatium-0.11.0/src/slots.cc +420 -0
- floatium-0.11.0/tests/__init__.py +0 -0
- floatium-0.11.0/tests/conftest.py +51 -0
- floatium-0.11.0/tests/cpython_suite/README.md +39 -0
- floatium-0.11.0/tests/cpython_suite/__init__.py +0 -0
- floatium-0.11.0/tests/cpython_suite/conftest.py +30 -0
- floatium-0.11.0/tests/test_edge.py +103 -0
- floatium-0.11.0/tests/test_parity.py +99 -0
- floatium-0.11.0/tests/test_roundtrip.py +88 -0
- floatium-0.11.0/third_party/SYNC_STATE +4 -0
- floatium-0.11.0/third_party/fast_float/LICENSE-APACHE +190 -0
- floatium-0.11.0/third_party/fast_float/LICENSE-BOOST +23 -0
- floatium-0.11.0/third_party/fast_float/LICENSE-MIT +27 -0
- floatium-0.11.0/third_party/fast_float/README.vendor +50 -0
- floatium-0.11.0/third_party/fast_float/ascii_number.h +588 -0
- floatium-0.11.0/third_party/fast_float/bigint.h +638 -0
- floatium-0.11.0/third_party/fast_float/constexpr_feature_detect.h +46 -0
- floatium-0.11.0/third_party/fast_float/decimal_to_binary.h +212 -0
- floatium-0.11.0/third_party/fast_float/digit_comparison.h +457 -0
- floatium-0.11.0/third_party/fast_float/fast_float.h +59 -0
- floatium-0.11.0/third_party/fast_float/fast_table.h +708 -0
- floatium-0.11.0/third_party/fast_float/float_common.h +1240 -0
- floatium-0.11.0/third_party/fast_float/parse_number.h +399 -0
- floatium-0.11.0/third_party/fmt/LICENSE +27 -0
- floatium-0.11.0/third_party/fmt/README.vendor +43 -0
- floatium-0.11.0/third_party/fmt/base.h +3010 -0
- floatium-0.11.0/third_party/fmt/format-inl.h +1948 -0
- floatium-0.11.0/third_party/fmt/format.h +4395 -0
- floatium-0.11.0/third_party/ryu/LICENSE-Apache +201 -0
- floatium-0.11.0/third_party/ryu/LICENSE-Boost +23 -0
- floatium-0.11.0/third_party/ryu/README.vendor +20 -0
- floatium-0.11.0/third_party/ryu/common.h +114 -0
- floatium-0.11.0/third_party/ryu/d2fixed.c +821 -0
- floatium-0.11.0/third_party/ryu/d2fixed_full_table.h +4420 -0
- floatium-0.11.0/third_party/ryu/d2s_intrinsics.h +357 -0
- floatium-0.11.0/third_party/ryu/digit_table.h +35 -0
- floatium-0.11.0/third_party/ryu/ryu.h +63 -0
- floatium-0.11.0/tools/run_stdlib_tests.py +103 -0
- floatium-0.11.0/tools/sync_from_cpython.sh +73 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
name: test / py${{ matrix.python }} / ${{ matrix.os }}
|
|
16
|
+
runs-on: ${{ matrix.os }}
|
|
17
|
+
strategy:
|
|
18
|
+
fail-fast: false
|
|
19
|
+
matrix:
|
|
20
|
+
os: [ubuntu-latest, macos-14, windows-latest]
|
|
21
|
+
python: ["3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"]
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v5
|
|
24
|
+
|
|
25
|
+
- uses: actions/setup-python@v6
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python }}
|
|
28
|
+
allow-prereleases: true
|
|
29
|
+
|
|
30
|
+
- name: Install build deps and package
|
|
31
|
+
run: |
|
|
32
|
+
python -m pip install --upgrade pip
|
|
33
|
+
python -m pip install ".[test]"
|
|
34
|
+
|
|
35
|
+
- name: Show info
|
|
36
|
+
run: |
|
|
37
|
+
python -c "import floatium; print(floatium.info())"
|
|
38
|
+
|
|
39
|
+
- name: Run unit + parity + edge tests
|
|
40
|
+
run: |
|
|
41
|
+
python -m pytest tests -x --tb=short --ignore=tests/cpython_suite
|
|
42
|
+
|
|
43
|
+
- name: Run vendored CPython tests (if any)
|
|
44
|
+
run: |
|
|
45
|
+
python -m pytest tests/cpython_suite --tb=short || true
|
|
46
|
+
shell: bash
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: cpython-suite
|
|
2
|
+
|
|
3
|
+
# Runs CPython's own stdlib regression tests against a floatium-patched
|
|
4
|
+
# interpreter (Track A of the test strategy — see tests/cpython_suite/README.md).
|
|
5
|
+
# If this workflow reports a failure, that's a real behavioral divergence
|
|
6
|
+
# from stock CPython — the output should go into DIFFERENCES.md.
|
|
7
|
+
#
|
|
8
|
+
# Kept separate from ci.yml because each test run takes ~1 minute per
|
|
9
|
+
# Python version and we don't want to block ordinary PRs on it.
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
push:
|
|
13
|
+
branches: [main]
|
|
14
|
+
workflow_dispatch:
|
|
15
|
+
schedule:
|
|
16
|
+
- cron: "0 7 * * 1" # weekly Monday at 07:00 UTC
|
|
17
|
+
|
|
18
|
+
concurrency:
|
|
19
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
20
|
+
cancel-in-progress: true
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
stdlib-tests:
|
|
24
|
+
name: stdlib / py${{ matrix.python }} / ${{ matrix.os }}
|
|
25
|
+
runs-on: ${{ matrix.os }}
|
|
26
|
+
strategy:
|
|
27
|
+
fail-fast: false
|
|
28
|
+
matrix:
|
|
29
|
+
os: [ubuntu-latest, macos-14, windows-latest]
|
|
30
|
+
python: ["3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"]
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v5
|
|
33
|
+
|
|
34
|
+
- uses: actions/setup-python@v6
|
|
35
|
+
with:
|
|
36
|
+
python-version: ${{ matrix.python }}
|
|
37
|
+
allow-prereleases: true
|
|
38
|
+
|
|
39
|
+
- run: python -m pip install --upgrade pip && python -m pip install .
|
|
40
|
+
|
|
41
|
+
- name: Run CPython stdlib tests with FLOATIUM_AUTOPATCH=1
|
|
42
|
+
shell: bash
|
|
43
|
+
run: |
|
|
44
|
+
python tools/run_stdlib_tests.py
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: wheels
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: "0 6 * * 1" # nightly sanity build
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build-sdist:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v5
|
|
19
|
+
- uses: actions/setup-python@v6
|
|
20
|
+
with: { python-version: "3.12" }
|
|
21
|
+
- run: |
|
|
22
|
+
python -m pip install --upgrade pip build
|
|
23
|
+
python -m build --sdist --outdir dist
|
|
24
|
+
- uses: actions/upload-artifact@v5
|
|
25
|
+
with:
|
|
26
|
+
name: sdist
|
|
27
|
+
path: dist/*.tar.gz
|
|
28
|
+
|
|
29
|
+
build-wheels:
|
|
30
|
+
name: wheels / ${{ matrix.os }}
|
|
31
|
+
runs-on: ${{ matrix.os }}
|
|
32
|
+
strategy:
|
|
33
|
+
fail-fast: false
|
|
34
|
+
matrix:
|
|
35
|
+
include:
|
|
36
|
+
- os: ubuntu-latest
|
|
37
|
+
- os: ubuntu-24.04-arm
|
|
38
|
+
- os: macos-14 # arm64
|
|
39
|
+
- os: windows-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v5
|
|
42
|
+
|
|
43
|
+
- uses: pypa/cibuildwheel@v2.21
|
|
44
|
+
env:
|
|
45
|
+
# Keep most config in pyproject.toml; these flags are the ones
|
|
46
|
+
# cibuildwheel 2.21 requires at the env level to opt into
|
|
47
|
+
# free-threaded and prerelease CPython builds.
|
|
48
|
+
CIBW_BUILD_VERBOSITY: 1
|
|
49
|
+
CIBW_FREE_THREADED_SUPPORT: "True"
|
|
50
|
+
CIBW_PRERELEASE_PYTHONS: "True"
|
|
51
|
+
|
|
52
|
+
- uses: actions/upload-artifact@v5
|
|
53
|
+
with:
|
|
54
|
+
name: wheels-${{ matrix.os }}
|
|
55
|
+
path: wheelhouse/*.whl
|
|
56
|
+
|
|
57
|
+
publish:
|
|
58
|
+
name: publish to PyPI
|
|
59
|
+
needs: [build-wheels, build-sdist]
|
|
60
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
environment:
|
|
63
|
+
name: pypi
|
|
64
|
+
url: https://pypi.org/project/floatium
|
|
65
|
+
permissions:
|
|
66
|
+
id-token: write # trusted-publisher flow
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/download-artifact@v5
|
|
69
|
+
with:
|
|
70
|
+
path: dist
|
|
71
|
+
pattern: "{wheels-*,sdist}"
|
|
72
|
+
merge-multiple: true
|
|
73
|
+
- name: List artifacts
|
|
74
|
+
run: ls -la dist/
|
|
75
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.20)
|
|
2
|
+
project(floatium LANGUAGES C CXX)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
7
|
+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
8
|
+
|
|
9
|
+
# --- Backend selection ------------------------------------------------------
|
|
10
|
+
# Format backend: fmt_opt (default — fmt for repr/exp, Ryu d2fixed for fixed),
|
|
11
|
+
# fmt (plain fmt for everything, for A/B measurement),
|
|
12
|
+
# stock (CPython dtoa baseline), all.
|
|
13
|
+
# Parse backend: fast_float (default), stock, all.
|
|
14
|
+
#
|
|
15
|
+
# With "all", every backend is compiled in and the active one is chosen at
|
|
16
|
+
# runtime via env vars FLOATIUM_FORMAT_BACKEND / FLOATIUM_PARSE_BACKEND.
|
|
17
|
+
set(FLOATIUM_FORMAT_BACKEND "fmt_opt" CACHE STRING "Format backend: fmt_opt, fmt, stock, all")
|
|
18
|
+
set(FLOATIUM_PARSE_BACKEND "fast_float" CACHE STRING "Parse backend: fast_float, stock, all")
|
|
19
|
+
set_property(CACHE FLOATIUM_FORMAT_BACKEND PROPERTY STRINGS fmt_opt fmt stock all)
|
|
20
|
+
set_property(CACHE FLOATIUM_PARSE_BACKEND PROPERTY STRINGS fast_float stock all)
|
|
21
|
+
|
|
22
|
+
# --- Python -----------------------------------------------------------------
|
|
23
|
+
# scikit-build-core passes Python3_EXECUTABLE via the FindPython hint file.
|
|
24
|
+
find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module)
|
|
25
|
+
|
|
26
|
+
# --- Sources ----------------------------------------------------------------
|
|
27
|
+
set(FLOATIUM_SRCS
|
|
28
|
+
src/ext.cc
|
|
29
|
+
src/slots.cc
|
|
30
|
+
src/format_short.cc
|
|
31
|
+
src/backends/registry.cc
|
|
32
|
+
src/cpython_adapter/fast_float_strtod.cc
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Compile-in backends based on selection. Default selection is made in
|
|
36
|
+
# backends/registry.cc at runtime via FLOATIUM_DEFAULT_FORMAT_BACKEND etc.
|
|
37
|
+
if(FLOATIUM_FORMAT_BACKEND STREQUAL "fmt" OR FLOATIUM_FORMAT_BACKEND STREQUAL "all")
|
|
38
|
+
list(APPEND FLOATIUM_SRCS
|
|
39
|
+
src/backends/fmt_format.cc
|
|
40
|
+
src/cpython_adapter/fmt_dtoa.cc
|
|
41
|
+
)
|
|
42
|
+
set(FLOATIUM_HAS_FMT_FORMAT 1)
|
|
43
|
+
endif()
|
|
44
|
+
if(FLOATIUM_FORMAT_BACKEND STREQUAL "fmt_opt" OR FLOATIUM_FORMAT_BACKEND STREQUAL "all")
|
|
45
|
+
list(APPEND FLOATIUM_SRCS
|
|
46
|
+
src/backends/fmt_opt_format.cc
|
|
47
|
+
src/cpython_adapter/fmt_opt_dtoa.cc
|
|
48
|
+
third_party/ryu/d2fixed.c
|
|
49
|
+
)
|
|
50
|
+
set(FLOATIUM_HAS_FMT_OPT_FORMAT 1)
|
|
51
|
+
endif()
|
|
52
|
+
if(FLOATIUM_FORMAT_BACKEND STREQUAL "stock" OR FLOATIUM_FORMAT_BACKEND STREQUAL "all")
|
|
53
|
+
list(APPEND FLOATIUM_SRCS src/backends/stock_format.cc)
|
|
54
|
+
set(FLOATIUM_HAS_STOCK_FORMAT 1)
|
|
55
|
+
endif()
|
|
56
|
+
if(FLOATIUM_PARSE_BACKEND STREQUAL "fast_float" OR FLOATIUM_PARSE_BACKEND STREQUAL "all")
|
|
57
|
+
list(APPEND FLOATIUM_SRCS src/backends/fast_float_parse.cc)
|
|
58
|
+
set(FLOATIUM_HAS_FAST_FLOAT_PARSE 1)
|
|
59
|
+
endif()
|
|
60
|
+
if(FLOATIUM_PARSE_BACKEND STREQUAL "stock" OR FLOATIUM_PARSE_BACKEND STREQUAL "all")
|
|
61
|
+
list(APPEND FLOATIUM_SRCS src/backends/stock_parse.cc)
|
|
62
|
+
set(FLOATIUM_HAS_STOCK_PARSE 1)
|
|
63
|
+
endif()
|
|
64
|
+
|
|
65
|
+
# Always compile fmt's format TU when fmt_opt is selected — fmt_opt_dtoa.cc
|
|
66
|
+
# still uses fmt for modes 0/2 and for the negative-ndigits fallback.
|
|
67
|
+
# fmt_format.cc's sources are already included above in the "fmt" branch;
|
|
68
|
+
# when only fmt_opt is selected we still need fmt_dtoa.cc's headers to be
|
|
69
|
+
# available to fmt_opt_dtoa.cc via FMT_HEADER_ONLY.
|
|
70
|
+
|
|
71
|
+
python3_add_library(_ext MODULE ${FLOATIUM_SRCS} WITH_SOABI)
|
|
72
|
+
|
|
73
|
+
target_include_directories(_ext PRIVATE
|
|
74
|
+
src
|
|
75
|
+
src/common
|
|
76
|
+
third_party/fmt
|
|
77
|
+
third_party/fast_float
|
|
78
|
+
third_party/ryu
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Match the build flags the CPython fmt-fastfloat branch uses for the
|
|
82
|
+
# vendored C++ code. -fno-exceptions / -fno-rtti keep the libstdc++ footprint
|
|
83
|
+
# minimal and avoid any dependency on C++ runtime type info.
|
|
84
|
+
if(NOT MSVC)
|
|
85
|
+
target_compile_options(_ext PRIVATE
|
|
86
|
+
$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
|
|
87
|
+
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>
|
|
88
|
+
-fvisibility=hidden
|
|
89
|
+
-Wall
|
|
90
|
+
-Wextra
|
|
91
|
+
-Wno-unused-parameter
|
|
92
|
+
)
|
|
93
|
+
else()
|
|
94
|
+
target_compile_options(_ext PRIVATE
|
|
95
|
+
$<$<COMPILE_LANGUAGE:CXX>:/EHs-c-> # disable C++ exceptions
|
|
96
|
+
$<$<COMPILE_LANGUAGE:CXX>:/GR-> # disable RTTI
|
|
97
|
+
$<$<COMPILE_LANGUAGE:CXX>:/utf-8> # fmt's base.h static_asserts against CP-1252 sources
|
|
98
|
+
/W3
|
|
99
|
+
/wd4100 # unreferenced formal parameter
|
|
100
|
+
)
|
|
101
|
+
endif()
|
|
102
|
+
|
|
103
|
+
# Configure-time feature flags surfaced to C++
|
|
104
|
+
# The FLOATIUM_DEFAULT_*_BACKEND macro selects the backend picked when
|
|
105
|
+
# install() is called with no kwargs. "all" builds every backend into the
|
|
106
|
+
# wheel but still need a single default; prefer the optimized fmt-based one.
|
|
107
|
+
set(FLOATIUM_DEFAULT_FORMAT "${FLOATIUM_FORMAT_BACKEND}")
|
|
108
|
+
if(FLOATIUM_DEFAULT_FORMAT STREQUAL "all")
|
|
109
|
+
set(FLOATIUM_DEFAULT_FORMAT "fmt_opt")
|
|
110
|
+
endif()
|
|
111
|
+
set(FLOATIUM_DEFAULT_PARSE "${FLOATIUM_PARSE_BACKEND}")
|
|
112
|
+
if(FLOATIUM_DEFAULT_PARSE STREQUAL "all")
|
|
113
|
+
set(FLOATIUM_DEFAULT_PARSE "fast_float")
|
|
114
|
+
endif()
|
|
115
|
+
|
|
116
|
+
target_compile_definitions(_ext PRIVATE
|
|
117
|
+
$<$<BOOL:${FLOATIUM_HAS_FMT_FORMAT}>:FLOATIUM_HAS_FMT_FORMAT=1>
|
|
118
|
+
$<$<BOOL:${FLOATIUM_HAS_FMT_OPT_FORMAT}>:FLOATIUM_HAS_FMT_OPT_FORMAT=1>
|
|
119
|
+
$<$<BOOL:${FLOATIUM_HAS_STOCK_FORMAT}>:FLOATIUM_HAS_STOCK_FORMAT=1>
|
|
120
|
+
$<$<BOOL:${FLOATIUM_HAS_FAST_FLOAT_PARSE}>:FLOATIUM_HAS_FAST_FLOAT_PARSE=1>
|
|
121
|
+
$<$<BOOL:${FLOATIUM_HAS_STOCK_PARSE}>:FLOATIUM_HAS_STOCK_PARSE=1>
|
|
122
|
+
FLOATIUM_DEFAULT_FORMAT_BACKEND="${FLOATIUM_DEFAULT_FORMAT}"
|
|
123
|
+
FLOATIUM_DEFAULT_PARSE_BACKEND="${FLOATIUM_DEFAULT_PARSE}"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
install(TARGETS _ext DESTINATION floatium)
|
|
127
|
+
|
|
128
|
+
# .pth files must sit directly in site-packages (the purelib/platlib root)
|
|
129
|
+
# to be processed by site.py. scikit-build-core maps `DESTINATION .` to
|
|
130
|
+
# the wheel's platlib root, which becomes site-packages/ after install.
|
|
131
|
+
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/floatium.pth DESTINATION .)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Behavioral differences vs. stock CPython
|
|
2
|
+
|
|
3
|
+
The primary success metric for floatium is that CPython's own regression
|
|
4
|
+
tests pass unchanged when run against a patched interpreter (Track A in
|
|
5
|
+
the test strategy). This file records every observed divergence so the
|
|
6
|
+
PEP discussion has them on the table up front.
|
|
7
|
+
|
|
8
|
+
## Last verified
|
|
9
|
+
|
|
10
|
+
- Interpreter: Python 3.15.0a3 (free-threaded debug build)
|
|
11
|
+
- floatium commit: initial (v0.1.0.dev0)
|
|
12
|
+
- Upstream CPython commit (fmt-fastfloat branch): `46e18aa3d5b`
|
|
13
|
+
|
|
14
|
+
## Summary: zero divergences
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
$ python tools/run_stdlib_tests.py
|
|
18
|
+
== Tests result: SUCCESS ==
|
|
19
|
+
All 12 tests OK.
|
|
20
|
+
Total tests: run=1,616 skipped=210
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Default test set:
|
|
24
|
+
`test_float`, `test_strtod`, `test_fstring`, `test_format`, `test_complex`,
|
|
25
|
+
`test_json`, `test_struct`, `test_string`, `test_decimal`, `test_math`,
|
|
26
|
+
`test_cmath`, `test_statistics`.
|
|
27
|
+
|
|
28
|
+
In addition, the 499-entry byte-identical parity matrix in
|
|
29
|
+
`tests/test_parity.py` (repr, str, format across ~50 format specs × 23
|
|
30
|
+
curated values) passes 100%.
|
|
31
|
+
|
|
32
|
+
## Known limitations (not divergences)
|
|
33
|
+
|
|
34
|
+
These are cases where floatium is **intentionally unable** to intercept
|
|
35
|
+
behavior, not cases where it produces different output. Documented here
|
|
36
|
+
so they're visible when the PEP case is made.
|
|
37
|
+
|
|
38
|
+
### 1. `"%g" % x` is not patched
|
|
39
|
+
|
|
40
|
+
The `%` operator for strings (`printf`-style formatting) calls
|
|
41
|
+
`PyOS_double_to_string` directly from `Python/unicodeobject.c:formatfloat`.
|
|
42
|
+
That symbol lives inside `libpython` and is not reachable from a
|
|
43
|
+
pip-installed extension without `LD_PRELOAD` / symbol interposition.
|
|
44
|
+
|
|
45
|
+
Impact: `"%g" % 0.1` uses the stock dtoa in a floatium-patched process.
|
|
46
|
+
This is a consequence of floatium being a **demo**; the actual PEP would
|
|
47
|
+
modify `pystrtod.c` / `PyOS_double_to_string` directly and cover this
|
|
48
|
+
path. See the companion CPython `fmt-fastfloat` branch for the in-tree
|
|
49
|
+
implementation that covers it.
|
|
50
|
+
|
|
51
|
+
### 2. `float("1_000.5")` falls through to the original `tp_new`
|
|
52
|
+
|
|
53
|
+
`fast_float::from_chars` does not handle PEP 515 underscore separators,
|
|
54
|
+
so `floatium_float_new` checks for `_` in the input and delegates to the
|
|
55
|
+
saved `float_new` when present. Output is bit-identical to stock (we
|
|
56
|
+
literally invoke the original), but this path does not benefit from
|
|
57
|
+
fast_float's speed. The in-tree CPython implementation strips
|
|
58
|
+
underscores before calling fast_float and therefore does benefit.
|
|
59
|
+
|
|
60
|
+
### 3. `float("inf")`, `float("nan")` fall through
|
|
61
|
+
|
|
62
|
+
The `fast_float_strtod` wrapper sets `no_infnan` so these literals are
|
|
63
|
+
rejected by the parse backend and the original `tp_new` handles them.
|
|
64
|
+
This preserves CPython's exact casing rules (`Infinity`, `INF`, `nan`,
|
|
65
|
+
etc.) at the cost of a small fast-path miss on these strings. Parity is
|
|
66
|
+
identical.
|
|
67
|
+
|
|
68
|
+
### 4. `float.__format__` with complex specs falls through
|
|
69
|
+
|
|
70
|
+
`floatium_float_format` handles the common path inline
|
|
71
|
+
(`[.precision]{g,G,e,E,f,F}`) and delegates to the original descriptor
|
|
72
|
+
for anything with fill/align/width/`#`/`,`/`_`/etc. Output is
|
|
73
|
+
bit-identical because the fallback invokes the saved bound method.
|
|
74
|
+
Complex specs therefore run at stock speed.
|
|
75
|
+
|
|
76
|
+
## When you find a new divergence
|
|
77
|
+
|
|
78
|
+
1. Add a minimal reproducer to `tests/test_parity.py` marked `xfail` with
|
|
79
|
+
the reason.
|
|
80
|
+
2. Add an entry to this file's "Known divergences" section (below —
|
|
81
|
+
currently empty).
|
|
82
|
+
3. Open an issue against CPython's `fmt-fastfloat` branch if the
|
|
83
|
+
divergence also exists there, or against floatium if it's specific to
|
|
84
|
+
this package's wrappers.
|
|
85
|
+
|
|
86
|
+
## Known divergences
|
|
87
|
+
|
|
88
|
+
None as of the last-verified date above.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Internals
|
|
2
|
+
|
|
3
|
+
Notes on how floatium is wired internally. Not needed for ordinary use;
|
|
4
|
+
see the [README](README.md) first.
|
|
5
|
+
|
|
6
|
+
## Introspection
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
>>> import floatium
|
|
10
|
+
>>> floatium.info()
|
|
11
|
+
{'patched': True,
|
|
12
|
+
'format_backend': 'fmt_opt',
|
|
13
|
+
'parse_backend': 'fast_float',
|
|
14
|
+
'available_format_backends': 'fmt_opt',
|
|
15
|
+
'available_parse_backends': 'fast_float',
|
|
16
|
+
'default_format_backend': 'fmt_opt',
|
|
17
|
+
'default_parse_backend': 'fast_float'}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Fields:
|
|
21
|
+
|
|
22
|
+
- `patched` — whether `install()` is currently active.
|
|
23
|
+
- `format_backend` / `parse_backend` — which backend is active now.
|
|
24
|
+
- `available_*` — what was compiled into this wheel.
|
|
25
|
+
- `default_*` — what `install()` picks if no backend is requested.
|
|
26
|
+
|
|
27
|
+
## Format backends
|
|
28
|
+
|
|
29
|
+
Registered format backends (all produce output bit-identical to stock
|
|
30
|
+
CPython):
|
|
31
|
+
|
|
32
|
+
| name | Uses | When |
|
|
33
|
+
|------------|-----------------------------------------------|------|
|
|
34
|
+
| `fmt_opt` | fmt Dragonbox + fmt fast subsegment + Ryu d2fixed | default |
|
|
35
|
+
| `fmt` | fmt Dragonbox + fmt `format_float` only | A/B measurement |
|
|
36
|
+
| `stock` | calls back into `PyOS_double_to_string` | regression baseline |
|
|
37
|
+
|
|
38
|
+
`fmt_opt` is the floatium default. For each `_Py_dg_dtoa` call it picks
|
|
39
|
+
per mode:
|
|
40
|
+
|
|
41
|
+
- **Mode 0 (shortest, drives `repr`)** — fmt's Dragonbox via
|
|
42
|
+
`fmt::detail::dragonbox::to_decimal`.
|
|
43
|
+
- **Mode 2 (significant digits, drives `%e`/`%g`)** — fmt's
|
|
44
|
+
`format_float` with `presentation_type::exp`.
|
|
45
|
+
- **Mode 3 (digits past decimal, drives `%f`)** — hybrid:
|
|
46
|
+
- If `3 × precision + max(exp2, 0) ≤ 60`, stay on fmt. This keeps
|
|
47
|
+
easy cases (small magnitudes / small precision) on fmt's fast
|
|
48
|
+
subsegment path, which is ~10 ns faster per call than d2fixed for
|
|
49
|
+
those inputs.
|
|
50
|
+
- Otherwise route through Ryu `d2fixed_buffered_n`, which is
|
|
51
|
+
block-based (9 digits per `mulShift_mod1e9` step) and has no cliff.
|
|
52
|
+
|
|
53
|
+
The `exp2 ≤ 60` boundary mirrors fmt's internal cutoff: fmt falls into
|
|
54
|
+
Dragon4 + bigint once the value's decade plus requested precision
|
|
55
|
+
exceeds the 19-digit Dragonbox first segment, at which point it is
|
|
56
|
+
2-6× slower than stock dtoa. log10(2) ≈ 0.30103 gives
|
|
57
|
+
exp2 ≈ 3.32 × decade, hence the integer inequality above.
|
|
58
|
+
|
|
59
|
+
## Parse backend
|
|
60
|
+
|
|
61
|
+
Only `fast_float` is registered on the parse side. `float(str)` is
|
|
62
|
+
intercepted by swapping `PyFloat_Type.tp_new`; the wrapper UTF-8
|
|
63
|
+
extracts, strips whitespace, rejects underscores / non-ASCII (those
|
|
64
|
+
fall through to the original `tp_new`), null-terminates, and calls the
|
|
65
|
+
backend's `strtod`.
|
|
66
|
+
|
|
67
|
+
## Backend abstraction
|
|
68
|
+
|
|
69
|
+
The vendored libraries sit behind a narrow C interface
|
|
70
|
+
([`src/common/backend.h`](src/common/backend.h)):
|
|
71
|
+
|
|
72
|
+
```c
|
|
73
|
+
struct FloatiumFormatBackend { const char *name; floatium_dtoa_fn dtoa; ... };
|
|
74
|
+
struct FloatiumParseBackend { const char *name; floatium_strtod_fn strtod; };
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Swapping fmt for Ryu shortest or Schubfach is a new file in
|
|
78
|
+
`src/backends/`. Swapping `fast_float` for double-conversion is another.
|
|
79
|
+
Backends are CMake-selectable at build time
|
|
80
|
+
(`-DFLOATIUM_FORMAT_BACKEND=...`, values `fmt_opt`, `fmt`, `stock`,
|
|
81
|
+
`all`); when built with `all` every backend is compiled in and the
|
|
82
|
+
active one is chosen via the `FLOATIUM_FORMAT_BACKEND` env var at
|
|
83
|
+
interpreter startup or the `format_backend=` kwarg to `install()`.
|
floatium-0.11.0/LICENSE
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pieter Eendebak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
This software vendors third-party libraries under their own licenses:
|
|
26
|
+
|
|
27
|
+
- third_party/fmt/ — MIT (see third_party/fmt/LICENSE)
|
|
28
|
+
- third_party/fast_float/ — Apache-2.0 OR MIT OR Boost-1.0
|
|
29
|
+
(see third_party/fast_float/LICENSE-*)
|
|
30
|
+
|
|
31
|
+
- src/format_short.cc is a port of code from CPython Python/pystrtod.c
|
|
32
|
+
(PSF License), redistributed in accordance with the PSF license terms.
|