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.
Files changed (77) hide show
  1. floatium-0.11.0/.github/workflows/ci.yml +46 -0
  2. floatium-0.11.0/.github/workflows/cpython-suite.yml +44 -0
  3. floatium-0.11.0/.github/workflows/wheels.yml +75 -0
  4. floatium-0.11.0/.gitignore +15 -0
  5. floatium-0.11.0/CMakeLists.txt +131 -0
  6. floatium-0.11.0/DIFFERENCES.md +88 -0
  7. floatium-0.11.0/INTERNALS.md +83 -0
  8. floatium-0.11.0/LICENSE +32 -0
  9. floatium-0.11.0/PKG-INFO +255 -0
  10. floatium-0.11.0/README.md +220 -0
  11. floatium-0.11.0/bench/__init__.py +0 -0
  12. floatium-0.11.0/bench/bench_format.py +50 -0
  13. floatium-0.11.0/bench/bench_json.py +43 -0
  14. floatium-0.11.0/bench/bench_ns_per_op.py +148 -0
  15. floatium-0.11.0/bench/bench_parse.py +50 -0
  16. floatium-0.11.0/bench/bench_repr.py +42 -0
  17. floatium-0.11.0/bench/corpora.py +78 -0
  18. floatium-0.11.0/bench/results/.gitkeep +0 -0
  19. floatium-0.11.0/bench/run_all.sh +40 -0
  20. floatium-0.11.0/floatium/__init__.py +94 -0
  21. floatium-0.11.0/floatium/_autopatch.py +57 -0
  22. floatium-0.11.0/floatium/_ext.pyi +20 -0
  23. floatium-0.11.0/floatium/py.typed +0 -0
  24. floatium-0.11.0/floatium.pth +1 -0
  25. floatium-0.11.0/pyproject.toml +80 -0
  26. floatium-0.11.0/src/backends/fast_float_parse.cc +24 -0
  27. floatium-0.11.0/src/backends/fmt_format.cc +30 -0
  28. floatium-0.11.0/src/backends/fmt_opt_format.cc +34 -0
  29. floatium-0.11.0/src/backends/registry.cc +87 -0
  30. floatium-0.11.0/src/backends/stock_format.cc +56 -0
  31. floatium-0.11.0/src/backends/stock_parse.cc +30 -0
  32. floatium-0.11.0/src/common/backend.h +76 -0
  33. floatium-0.11.0/src/common/format_short.h +50 -0
  34. floatium-0.11.0/src/cpython_adapter/fast_float_strtod.cc +67 -0
  35. floatium-0.11.0/src/cpython_adapter/fmt_dtoa.cc +173 -0
  36. floatium-0.11.0/src/cpython_adapter/fmt_opt_dtoa.cc +249 -0
  37. floatium-0.11.0/src/ext.cc +127 -0
  38. floatium-0.11.0/src/format_short.cc +231 -0
  39. floatium-0.11.0/src/slots.cc +420 -0
  40. floatium-0.11.0/tests/__init__.py +0 -0
  41. floatium-0.11.0/tests/conftest.py +51 -0
  42. floatium-0.11.0/tests/cpython_suite/README.md +39 -0
  43. floatium-0.11.0/tests/cpython_suite/__init__.py +0 -0
  44. floatium-0.11.0/tests/cpython_suite/conftest.py +30 -0
  45. floatium-0.11.0/tests/test_edge.py +103 -0
  46. floatium-0.11.0/tests/test_parity.py +99 -0
  47. floatium-0.11.0/tests/test_roundtrip.py +88 -0
  48. floatium-0.11.0/third_party/SYNC_STATE +4 -0
  49. floatium-0.11.0/third_party/fast_float/LICENSE-APACHE +190 -0
  50. floatium-0.11.0/third_party/fast_float/LICENSE-BOOST +23 -0
  51. floatium-0.11.0/third_party/fast_float/LICENSE-MIT +27 -0
  52. floatium-0.11.0/third_party/fast_float/README.vendor +50 -0
  53. floatium-0.11.0/third_party/fast_float/ascii_number.h +588 -0
  54. floatium-0.11.0/third_party/fast_float/bigint.h +638 -0
  55. floatium-0.11.0/third_party/fast_float/constexpr_feature_detect.h +46 -0
  56. floatium-0.11.0/third_party/fast_float/decimal_to_binary.h +212 -0
  57. floatium-0.11.0/third_party/fast_float/digit_comparison.h +457 -0
  58. floatium-0.11.0/third_party/fast_float/fast_float.h +59 -0
  59. floatium-0.11.0/third_party/fast_float/fast_table.h +708 -0
  60. floatium-0.11.0/third_party/fast_float/float_common.h +1240 -0
  61. floatium-0.11.0/third_party/fast_float/parse_number.h +399 -0
  62. floatium-0.11.0/third_party/fmt/LICENSE +27 -0
  63. floatium-0.11.0/third_party/fmt/README.vendor +43 -0
  64. floatium-0.11.0/third_party/fmt/base.h +3010 -0
  65. floatium-0.11.0/third_party/fmt/format-inl.h +1948 -0
  66. floatium-0.11.0/third_party/fmt/format.h +4395 -0
  67. floatium-0.11.0/third_party/ryu/LICENSE-Apache +201 -0
  68. floatium-0.11.0/third_party/ryu/LICENSE-Boost +23 -0
  69. floatium-0.11.0/third_party/ryu/README.vendor +20 -0
  70. floatium-0.11.0/third_party/ryu/common.h +114 -0
  71. floatium-0.11.0/third_party/ryu/d2fixed.c +821 -0
  72. floatium-0.11.0/third_party/ryu/d2fixed_full_table.h +4420 -0
  73. floatium-0.11.0/third_party/ryu/d2s_intrinsics.h +357 -0
  74. floatium-0.11.0/third_party/ryu/digit_table.h +35 -0
  75. floatium-0.11.0/third_party/ryu/ryu.h +63 -0
  76. floatium-0.11.0/tools/run_stdlib_tests.py +103 -0
  77. 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,15 @@
1
+ build/
2
+ dist/
3
+ wheelhouse/
4
+ *.egg-info/
5
+ __pycache__/
6
+ *.pyc
7
+ *.so
8
+ *.pyd
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ bench/results/*.json
13
+ !bench/results/.gitkeep
14
+ .venv/
15
+ venv/
@@ -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()`.
@@ -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.