floatium 0.11.0__cp313-cp313-win_amd64.whl

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/__init__.py ADDED
@@ -0,0 +1,94 @@
1
+ """floatium — drop-in replacement for CPython float formatting/parsing.
2
+
3
+ Backs float repr/str/__format__/__new__ with the {fmt} and fast_float C++
4
+ libraries to demonstrate the performance and correctness case for a
5
+ forthcoming CPython PEP.
6
+
7
+ Basic usage:
8
+
9
+ import floatium
10
+ floatium.install() # patch PyFloat_Type slots
11
+ repr(0.1) # -> '0.1', same as stock
12
+ floatium.uninstall() # restore
13
+
14
+ with floatium.patched():
15
+ ... # patched inside the block
16
+
17
+ Autopatch on interpreter startup: set FLOATIUM_AUTOPATCH=1. See README.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from contextlib import contextmanager
23
+ from typing import Iterator
24
+
25
+ from floatium import _ext
26
+
27
+ __all__ = [
28
+ "install",
29
+ "uninstall",
30
+ "is_patched",
31
+ "info",
32
+ "patched",
33
+ "__version__",
34
+ ]
35
+
36
+ __version__ = "0.1.0.dev0"
37
+
38
+
39
+ def install(
40
+ format_backend: str | None = None,
41
+ parse_backend: str | None = None,
42
+ ) -> None:
43
+ """Install floatium's replacement slots on PyFloat_Type.
44
+
45
+ ``format_backend`` and ``parse_backend`` select which compiled-in
46
+ implementation to use. Pass None to use the build-time defaults
47
+ (typically ``"fmt"`` and ``"fast_float"``). See ``info()`` for
48
+ which backends are available in this wheel.
49
+
50
+ Idempotent: calling install() twice is a no-op on the second call.
51
+ """
52
+ kwargs = {}
53
+ if format_backend is not None:
54
+ kwargs["format_backend"] = format_backend
55
+ if parse_backend is not None:
56
+ kwargs["parse_backend"] = parse_backend
57
+ _ext.install(**kwargs)
58
+
59
+
60
+ def uninstall() -> None:
61
+ """Restore the original PyFloat_Type slots."""
62
+ _ext.uninstall()
63
+
64
+
65
+ def is_patched() -> bool:
66
+ """Return True if floatium is currently installed."""
67
+ return bool(_ext.is_patched())
68
+
69
+
70
+ def info() -> dict:
71
+ """Return a dict describing the current state and available backends."""
72
+ return _ext.info()
73
+
74
+
75
+ @contextmanager
76
+ def patched(
77
+ format_backend: str | None = None,
78
+ parse_backend: str | None = None,
79
+ ) -> Iterator[None]:
80
+ """Context manager that installs on entry and restores on exit.
81
+
82
+ Useful for A/B benchmarking or scoped testing::
83
+
84
+ with floatium.patched():
85
+ assert repr(0.1) == '0.1'
86
+ """
87
+ was_patched = is_patched()
88
+ if not was_patched:
89
+ install(format_backend=format_backend, parse_backend=parse_backend)
90
+ try:
91
+ yield
92
+ finally:
93
+ if not was_patched:
94
+ uninstall()
floatium/_autopatch.py ADDED
@@ -0,0 +1,57 @@
1
+ """Autopatch hook.
2
+
3
+ This module is imported by the site-packages/floatium.pth file at
4
+ interpreter startup. It inspects FLOATIUM_AUTOPATCH and installs the
5
+ replacement slots if set.
6
+
7
+ Rationale for gating on an env var rather than patching unconditionally:
8
+ the .pth file mechanism affects *every* Python invocation that shares
9
+ this site-packages directory, including tools that should not be
10
+ affected (the build system, linters, etc.). Opt-in is safer for
11
+ a process-global mutation.
12
+
13
+ Values that trigger install:
14
+ FLOATIUM_AUTOPATCH=1
15
+ FLOATIUM_AUTOPATCH=true
16
+ FLOATIUM_AUTOPATCH=yes
17
+
18
+ You can also specify backends:
19
+ FLOATIUM_FORMAT_BACKEND=fmt
20
+ FLOATIUM_PARSE_BACKEND=fast_float
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import os
26
+
27
+
28
+ def _truthy(v: str | None) -> bool:
29
+ if v is None:
30
+ return False
31
+ return v.strip().lower() in {"1", "true", "yes", "on"}
32
+
33
+
34
+ def _run() -> None:
35
+ if not _truthy(os.environ.get("FLOATIUM_AUTOPATCH")):
36
+ return
37
+
38
+ # Import lazily so that merely having the .pth on disk doesn't drag the
39
+ # C extension into every Python process — only into ones that opt in.
40
+ try:
41
+ from floatium import install
42
+ except ImportError:
43
+ return
44
+
45
+ fmt_backend = os.environ.get("FLOATIUM_FORMAT_BACKEND") or None
46
+ parse_backend = os.environ.get("FLOATIUM_PARSE_BACKEND") or None
47
+
48
+ try:
49
+ install(format_backend=fmt_backend, parse_backend=parse_backend)
50
+ except Exception: # noqa: BLE001 — never break interpreter startup
51
+ if _truthy(os.environ.get("FLOATIUM_AUTOPATCH_DEBUG")):
52
+ import traceback
53
+
54
+ traceback.print_exc()
55
+
56
+
57
+ _run()
Binary file
floatium/_ext.pyi ADDED
@@ -0,0 +1,20 @@
1
+ """Type stubs for the compiled extension module floatium._ext."""
2
+
3
+ from typing import TypedDict
4
+
5
+ class _Info(TypedDict):
6
+ patched: bool
7
+ format_backend: str | None
8
+ parse_backend: str | None
9
+ available_format_backends: str
10
+ available_parse_backends: str
11
+ default_format_backend: str
12
+ default_parse_backend: str
13
+
14
+ def install(
15
+ format_backend: str | None = ...,
16
+ parse_backend: str | None = ...,
17
+ ) -> None: ...
18
+ def uninstall() -> None: ...
19
+ def is_patched() -> bool: ...
20
+ def info() -> _Info: ...
floatium/py.typed ADDED
File without changes
@@ -0,0 +1,255 @@
1
+ Metadata-Version: 2.1
2
+ Name: floatium
3
+ Version: 0.11.0
4
+ Summary: Drop-in replacement for CPython float formatting/parsing, backed by {fmt} and fast_float. Demonstrator for a forthcoming CPython PEP.
5
+ Keywords: cpython,float,dtoa,fmt,fast_float,repr,format
6
+ Author-Email: Pieter Eendebak <pieter.eendebak@gmail.com>
7
+ License: MIT
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Programming Language :: Python :: 3.15
17
+ Classifier: Programming Language :: Python :: Implementation :: CPython
18
+ Classifier: Programming Language :: C++
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Project-URL: Homepage, https://github.com/eendebakpt/floatium
21
+ Project-URL: Repository, https://github.com/eendebakpt/floatium
22
+ Project-URL: Issues, https://github.com/eendebakpt/floatium/issues
23
+ Requires-Python: >=3.11
24
+ Provides-Extra: test
25
+ Requires-Dist: pytest>=8; extra == "test"
26
+ Requires-Dist: hypothesis>=6; extra == "test"
27
+ Provides-Extra: bench
28
+ Requires-Dist: pyperf>=2.7; extra == "bench"
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8; extra == "dev"
31
+ Requires-Dist: hypothesis>=6; extra == "dev"
32
+ Requires-Dist: pyperf>=2.7; extra == "dev"
33
+ Requires-Dist: ruff>=0.5; extra == "dev"
34
+ Description-Content-Type: text/markdown
35
+
36
+ # floatium
37
+
38
+ Experimental drop-in replacement for CPython's float formatting and parsing,
39
+ backed by the [{fmt}](https://github.com/fmtlib/fmt) and
40
+ [fast_float](https://github.com/fastfloat/fast_float) C++ libraries.
41
+
42
+ `pip install floatium`, set `FLOATIUM_AUTOPATCH=1`, and every subsequent
43
+ Python process uses `{fmt}`'s Dragonbox for `repr(float)` / `str(float)` /
44
+ `float.__format__` and `fast_float` for `float("...")`. Existing code,
45
+ existing tests, existing output — just faster. Works with an unmodified
46
+ stock CPython; no interpreter rebuild required.
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ pip install floatium
52
+ ```
53
+
54
+ ### From source
55
+
56
+ Source builds require a C++17 compiler (GCC 9+, Clang 12+, or MSVC
57
+ 2019+) and CMake ≥ 3.20.
58
+
59
+ ```bash
60
+ git clone https://github.com/eendebakpt/floatium
61
+ cd floatium
62
+ pip install -e '.[test]'
63
+ ```
64
+
65
+ Tests and benchmarks target **CPython 3.14**. No system libraries are
66
+ required; `{fmt}` and `fast_float` are vendored.
67
+
68
+ ## Why this package exists
69
+
70
+ CPython's float formatting has gone through
71
+ [`Python/dtoa.c`](https://github.com/python/cpython/blob/main/Python/dtoa.c)
72
+ — David Gay's 1991 reference implementation — for three decades. The
73
+ code is ~2,800 lines of hand-tuned C and slower than modern alternatives.
74
+ Its parsing counterpart (`_Py_dg_strtod` in the same file) has similar
75
+ constraints.
76
+
77
+ Floatium demonstrates what replacing both sides looks like, as a pip
78
+ package against stock CPython:
79
+
80
+ - **Format** (double → string) via `{fmt}`'s Dragonbox algorithm — the
81
+ same algorithm that backs C++20's `std::format`. Fixed-precision
82
+ formatting for huge-magnitude values is routed through Ryu's
83
+ `d2fixed` to sidestep fmt's Dragon4 + bigint slow path.
84
+ - **Parse** (string → double) via `fast_float`'s Eisel–Lemire + bignum
85
+ path — the same algorithm Rust's `std`, Apache Arrow, and DuckDB use.
86
+
87
+ Both produce output **bit-identical** to stock CPython on every input
88
+ we've tested (see [DIFFERENCES.md](DIFFERENCES.md) — currently zero
89
+ divergences against CPython 3.15's stdlib test suite).
90
+
91
+ Library choices: `{fmt}` is MIT, header-only, and ships Dragonbox
92
+ + grammar + fallbacks from a single upstream (~9.4k LOC vendored).
93
+ `fast_float` is Apache-2.0 / MIT / Boost-1.0 triple-licensed, header-only,
94
+ C++11, and used in Chromium, Apache Arrow, ClickHouse, folly, and DuckDB.
95
+ Ryu is Apache-2.0 / Boost-1.0 dual-licensed and only its `d2fixed` entry
96
+ point is vendored (~100 KB of pow10 tables). Slots are backend-swappable
97
+ — see [INTERNALS.md](INTERNALS.md).
98
+
99
+ ## Usage
100
+
101
+ ### Autopatch (recommended)
102
+
103
+ Once `floatium` is installed, setting `FLOATIUM_AUTOPATCH=1` before any
104
+ Python process starts installs the replacement for the life of that
105
+ process:
106
+
107
+ ```bash
108
+ export FLOATIUM_AUTOPATCH=1
109
+ python -c "import json; print(json.dumps([0.1, 0.2, 1e100]))"
110
+ # [0.1, 0.2, 1e+100]
111
+ ```
112
+
113
+ The mechanism is a `.pth` file placed in `site-packages/` at install
114
+ time — `site.py` executes it during interpreter startup, before user
115
+ code, and the hook calls `install()` if the env var is set.
116
+
117
+ ### Explicit
118
+
119
+ ```python
120
+ import floatium
121
+
122
+ floatium.install() # patch PyFloat_Type slots
123
+ assert repr(0.1) == "0.1"
124
+ floatium.uninstall() # restore
125
+ ```
126
+
127
+ ### Scoped
128
+
129
+ ```python
130
+ import floatium
131
+
132
+ with floatium.patched():
133
+ do_something_float_heavy()
134
+ ```
135
+
136
+ ## What it patches
137
+
138
+ | Surface | How |
139
+ |-------------------------------------|-----------------------------------------------|
140
+ | `repr(x)`, `str(x)`, `f"{x}"` | `PyFloat_Type.tp_repr` pointer swap |
141
+ | `f"{x:.2f}"`, `format(x, spec)` | `__format__` entry in `PyFloat_Type.tp_dict` |
142
+ | `"{}".format(x)` | via `__format__` |
143
+ | `float("1.5")` | `PyFloat_Type.tp_new` pointer swap |
144
+ | `json.dumps([x])` | via `__repr__` |
145
+
146
+ `"%g" % x` is **not** patched — see [DIFFERENCES.md](DIFFERENCES.md).
147
+
148
+ ## Benchmarks
149
+
150
+ Run locally with:
151
+
152
+ ```bash
153
+ python -m bench.bench_ns_per_op --markdown # quick ns/op table
154
+ bench/run_all.sh # full pyperf sweep
155
+ ```
156
+
157
+ Numbers below are from a release CPython 3.14.3 build
158
+ (`python -m bench.bench_ns_per_op`, median of fastest third of samples,
159
+ lower is better). The default backend is `fmt_opt`; see
160
+ [INTERNALS.md](INTERNALS.md) for the routing. The `fmt` column is the
161
+ plain-fmt backend, included for A/B transparency:
162
+
163
+ | Corpus | Operation | Stock (ns) | fmt (ns) | fmt_opt (ns) | fmt vs stock | fmt_opt vs stock |
164
+ |-----------------|-----------------|-----------:|---------:|-------------:|-------------:|-----------------:|
165
+ | random_uniform | `repr(x)` | 289 | 97 | 97 | 2.99× | 2.98× |
166
+ | random_uniform | `f"{x:.4f}"` | 119 | 104 | 104 | 1.14× | 1.15× |
167
+ | random_uniform | `float(s)` | 121 | 121 | 121 | 1.00× | 1.00× |
168
+ | random_bits | `repr(x)` | 820 | 136 | 137 | 6.03× | 5.99× |
169
+ | random_bits | `f"{x:.4f}"` | 1,940 | 5,530 | 193 | 0.35× | 10.04× |
170
+ | random_bits | `float(s)` | 277 | 279 | 278 | 0.99× | 1.00× |
171
+ | financial | `repr(x)` | 173 | 80 | 80 | 2.16× | 2.15× |
172
+ | financial | `f"{x:.4f}"` | 146 | 99 | 101 | 1.46× | 1.44× |
173
+ | financial | `float(s)` | 37 | 37 | 37 | 1.00× | 1.00× |
174
+ | scientific | `repr(x)` | 637 | 133 | 133 | 4.79× | 4.78× |
175
+ | scientific | `f"{x:.4f}"` | 1,081 | 2,961 | 158 | 0.36× | 6.84× |
176
+ | scientific | `float(s)` | 213 | 213 | 212 | 1.00× | 1.00× |
177
+ | integer_valued | `repr(x)` | 143 | 88 | 88 | 1.63× | 1.64× |
178
+ | integer_valued | `f"{x:.4f}"` | 167 | 105 | 106 | 1.59× | 1.58× |
179
+ | integer_valued | `float(s)` | 43 | 43 | 43 | 1.00× | 1.00× |
180
+
181
+ **fmt_opt eliminates the fixed-mode regression.** Plain `fmt` regresses
182
+ by 2-3× on `random_bits` and `scientific` `f"{x:.4f}"` because
183
+ `fmt::detail::format_float` falls into a Dragon4 + bigint classical loop
184
+ when the value's decade + requested precision exceeds the 19-digit
185
+ Dragonbox first segment. `fmt_opt` detects that cliff from the binary
186
+ exponent and routes only those cases through Ryu's `d2fixed` — which is
187
+ block-based and always fast — while keeping fmt's fast subsegment path
188
+ for ordinary magnitudes. Output is bit-identical on both paths.
189
+
190
+ **Parse is flat at this layer.** `fast_float` beats `_Py_dg_strtod` on
191
+ raw `from_chars`, but the wrapper's per-call overhead (UTF-8 extraction,
192
+ whitespace strip, null-termination copy, `PyFloat_FromDouble`) cancels
193
+ the gain on these corpora. The speedup is recoverable either by lifting
194
+ the hook closer to `PyFloat_FromString` (requires an in-tree CPython
195
+ change, out of scope for a pip package) or by batching string-heavy
196
+ workloads where the per-call overhead amortizes — JSON and CSV parsers
197
+ are the obvious candidates.
198
+
199
+ ## Running CPython's test suite against floatium
200
+
201
+ `tools/run_stdlib_tests.py` runs a curated set of CPython's own stdlib
202
+ regression tests with floatium autopatched. Zero divergences on the
203
+ default set as of the last update; see [DIFFERENCES.md](DIFFERENCES.md)
204
+ for the current status.
205
+
206
+ ```bash
207
+ python tools/run_stdlib_tests.py
208
+ # ==> running: python -m test test_float test_strtod test_fstring ...
209
+ # == Tests result: SUCCESS ==
210
+ # All 12 tests OK.
211
+ # Total tests: run=1,616 skipped=210
212
+ ```
213
+
214
+ ## Limitations
215
+
216
+ See [DIFFERENCES.md](DIFFERENCES.md) for the full list. Summary:
217
+
218
+ - **`"%g" % x` is not patched.** The `%` operator calls
219
+ `PyOS_double_to_string` directly from `libpython`; no pip package can
220
+ intercept that without `LD_PRELOAD`.
221
+ - **Patching is process-global.** One `install()` per process; affects
222
+ all threads, all modules.
223
+ - **`Py_TPFLAGS_IMMUTABLETYPE` bypass.** We write directly to
224
+ `PyFloat_Type` slots, bypassing the public type-attribute API that
225
+ honors the flag. This is a known-stable internal technique (CPython
226
+ itself does it during bootstrap) but explicitly not part of the stable
227
+ ABI.
228
+ - **Free-threaded builds.** Supported; patching must happen at import
229
+ time (the only safe window). Method-cache invalidation uses
230
+ `PyType_Modified()` except on 3.15 debug builds (where that call
231
+ asserts on static types), in which case the wrapper falls back to
232
+ writing `tp_version_tag = 0` directly.
233
+ - **`float("1_000.5")` and `float("inf")` take the fallback path.** Both
234
+ parse correctly (output is bit-identical to stock) but don't benefit
235
+ from `fast_float`'s speed.
236
+
237
+ ## License
238
+
239
+ - floatium wrapper code: **MIT**.
240
+ - `{fmt}` (vendored in `third_party/fmt/`): **MIT**.
241
+ - `fast_float` (vendored in `third_party/fast_float/`): **Apache-2.0 OR
242
+ MIT OR Boost-1.0**.
243
+ - Ryu `d2fixed` (vendored in `third_party/ryu/`): **Apache-2.0 OR
244
+ Boost-1.0**.
245
+ - `src/format_short.cc` is a port of code from CPython `Python/pystrtod.c`,
246
+ which is under the **PSF License**. The port preserves the original
247
+ attribution and is compatible with downstream MIT redistribution under
248
+ the PSF license's permissive terms.
249
+
250
+ See `LICENSE` and the per-directory `LICENSE*` files.
251
+
252
+ ## Status
253
+
254
+ Pre-alpha. v0.1.x — the API will stay small (the four functions in
255
+ `floatium/__init__.py`) but the internals will change.
@@ -0,0 +1,10 @@
1
+ floatium/__init__.py,sha256=yiG0lcQ3Ts49twxoOmHEN9dqIcr7u2qsDsHdAkXV9HU,2564
2
+ floatium/_autopatch.py,sha256=im9IJDolZxMtkNtUl0ZtpqgVd0hT4yz6DF3FsVLem-M,1702
3
+ floatium/_ext.cp313-win_amd64.pyd,sha256=130diF7L-PO9MClwzS4ogz_CHr5Ifgux3vVaQwwF2Oo,171008
4
+ floatium/_ext.pyi,sha256=RJjP0kcsVSiuayojKaprtCTodNSVbBHbtNCB7FwwNS0,540
5
+ floatium/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ floatium.pth,sha256=p5A7cl0UiWSCQjKyWw-U4VoEWIC2VyBrbBcHsLSAu54,28
7
+ floatium-0.11.0.dist-info/METADATA,sha256=-hfiXfBe1ZYzhPmDUYC6mQOLguS4ZYqw1DX88K6Z64s,11377
8
+ floatium-0.11.0.dist-info/WHEEL,sha256=UZrbbE4r80xj7Ncfa6JoeTVe-77bdXLkKUA63V8pKWQ,106
9
+ floatium-0.11.0.dist-info/licenses/LICENSE,sha256=_v_9HBJ-rQ1tDIG-rGKZy2MBAEfk3iep9_nI6sfRtI0,1517
10
+ floatium-0.11.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: scikit-build-core 0.12.2
3
+ Root-Is-Purelib: false
4
+ Tag: cp313-cp313-win_amd64
5
+
@@ -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.
floatium.pth ADDED
@@ -0,0 +1 @@
1
+ import floatium._autopatch