From e96cb24edd4d1cb4c0de6d3f851e5dcf629e3f20 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 21 May 2026 09:36:48 +0200 Subject: [PATCH 01/13] =?UTF-8?q?floatium=20v0.14.5=20=E2=80=94=20require?= =?UTF-8?q?=20Python=203.13+;=20drop=203.11/3.12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump the floor to Python 3.13. CI matrix, wheel build matrix, ruff target-version, classifiers, and the requires-python pin all drop the 3.11 / 3.12 cells. Also strip the now-dead `PY_VERSION_HEX < 0x030C0000` branch in src/slots.cc (the function had a fallback for borrowing tp_dict; everything we support has PyType_GetDict). The PEP 562 module-level `__getattr__` experiment from this branch is reverted — eager definitions remain in floatium/__init__.py, and the autopatch hook goes through floatium.install (not floatium._ext.install directly). `from __future__ import annotations` is dropped from __init__.py and _autopatch.py since 3.13+ supports PEP 604 unions natively at runtime; that saves an `import __future__` (~90 µs) on the autopatch hot path without needing PEP 562. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 2 +- .github/workflows/wheels.yml | 2 +- floatium/__init__.py | 4 +--- floatium/_autopatch.py | 2 -- pyproject.toml | 12 +++++------- src/slots.cc | 19 ++++++------------- 6 files changed, 14 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f24dd93..9051943 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-14, windows-latest] - python: ["3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"] + python: ["3.13", "3.14", "3.14t", "3.15", "3.15t"] exclude: # 3.15 is still pre-release (alpha at time of writing) and the # free-threaded Windows binary distributed via setup-python's diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index cf50033..12d4aec 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 - with: { python-version: "3.12" } + with: { python-version: "3.13" } - run: | python -m pip install --upgrade pip build python -m build --sdist --outdir dist diff --git a/floatium/__init__.py b/floatium/__init__.py index 47ff8e4..0409ef9 100644 --- a/floatium/__init__.py +++ b/floatium/__init__.py @@ -22,8 +22,6 @@ ``python -m floatium status`` for the current state. """ -from __future__ import annotations - from floatium import _ext __all__ = [ @@ -36,7 +34,7 @@ "__version__", ] -__version__ = "0.14.4" +__version__ = "0.14.5" def install( diff --git a/floatium/_autopatch.py b/floatium/_autopatch.py index 761e753..4aebc33 100644 --- a/floatium/_autopatch.py +++ b/floatium/_autopatch.py @@ -21,8 +21,6 @@ to opt in. v0.13.0 flipped the default to opt-out. """ -from __future__ import annotations - import os diff --git a/pyproject.toml b/pyproject.toml index 94d9d81..8f0c38e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,19 +4,17 @@ build-backend = "scikit_build_core.build" [project] name = "floatium" -version = "0.14.4" +version = "0.14.5" description = "Drop-in replacement for CPython float formatting/parsing, backed by {fmt} and fast_float. Demonstrator for a forthcoming CPython PEP." readme = "README.md" license = { text = "MIT" } authors = [{ name = "Pieter Eendebak", email = "pieter.eendebak@gmail.com" }] -requires-python = ">=3.11" +requires-python = ">=3.13" classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.15", @@ -51,12 +49,12 @@ FLOATIUM_FORMAT_BACKEND = "fmt_opt" FLOATIUM_PARSE_BACKEND = "fast_float" [tool.cibuildwheel] -# CPython 3.11–3.14, incl. free-threaded (cp313t / cp314t). 3.15 is not +# CPython 3.13–3.14, incl. free-threaded (cp313t / cp314t). 3.15 is not # listed: cibuildwheel 3.4.1 has no 3.15 in its interpreter catalog yet # and rejects a `cp315-*` selector outright. Add `cp315-* cp315t-*` here # (and bump cibuildwheel) once 3.15 reaches beta and cibuildwheel ships # configs for it; 3.15 users install from the sdist until then. -build = "cp311-* cp312-* cp313-* cp313t-* cp314-* cp314t-*" +build = "cp313-* cp313t-* cp314-* cp314t-*" skip = "*-musllinux_i686 pp*" test-extras = ["test"] test-command = "pytest {project}/tests -x -q --ignore={project}/tests/cpython_suite" @@ -79,7 +77,7 @@ filterwarnings = ["error"] [tool.ruff] line-length = 100 -target-version = "py311" +target-version = "py313" [tool.ruff.lint] select = ["E", "F", "I", "W", "UP", "B", "SIM", "RUF"] diff --git a/src/slots.cc b/src/slots.cc index 24c4561..eb87b46 100644 --- a/src/slots.cc +++ b/src/slots.cc @@ -24,18 +24,11 @@ #include -// Pre-3.12, `PyFloat_Type.tp_dict` is a valid borrowed reference. Starting -// in 3.12 the field can be NULL until the dict is lazily materialized; -// PyType_GetDict() is the supported way to reach it and returns a new -// strong reference. +// PyFloat_Type.tp_dict can be NULL until the dict is lazily materialized +// (since CPython 3.12); PyType_GetDict() is the supported way to reach +// it and returns a new strong reference. static inline PyObject *floatium_get_type_dict(PyTypeObject *tp) { -#if PY_VERSION_HEX >= 0x030C0000 return PyType_GetDict(tp); -#else - PyObject *d = tp->tp_dict; - if (d) Py_INCREF(d); - return d; -#endif } // --------------------------------------------------------------------------- @@ -367,9 +360,9 @@ int floatium_install(const FloatiumFormatBackend *fmt_b, return -1; } - // Use PyType_GetDict (3.12+): PyFloat_Type.tp_dict can be NULL since - // types now lazily materialize their dict. PyType_GetDict returns a new - // strong reference that we must DECREF. + // PyType_GetDict: PyFloat_Type.tp_dict can be NULL since types lazily + // materialize their dict. Returns a new strong reference that we must + // DECREF. PyObject *type_dict = floatium_get_type_dict(&PyFloat_Type); if (!type_dict) { PyErr_SetString(PyExc_RuntimeError, From 06f6f0ee02876eac39ab3bb77aabd053446d0595 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 19 Oct 2026 22:32:35 +0200 Subject: [PATCH 02/13] ci: build CPython 3.15 / 3.15t wheels; add all-backends test job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python 3.15.0 final shipped 2026-10-09. cibuildwheel 4.3.1 carries cp315 / cp315t in its default catalog and needs no `enable` token for free-threaded 3.14+, so bump to it and add cp315-* cp315t-* to the build list. cibuildwheel 4.0 dropped the experimental cp313t builds together with the cpython-freethreading enable, so wheels.yml keeps one pinned cibuildwheel 3.4.1 step that builds only cp313t-*. ci.yml / cpython-suite.yml: 3.15 and 3.15t resolve to final releases on all three OSes now, so drop allow-prereleases and the 3.15t × windows exclusion. New test-all-backends job compiles every backend (FLOATIUM_*_BACKEND=all) so the non-default ones are exercised in CI. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/ci.yml | 42 ++++++++++++++++++++++------- .github/workflows/cpython-suite.yml | 1 - .github/workflows/wheels.yml | 24 +++++++++++------ pyproject.toml | 13 ++++----- 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9051943..76610d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,22 +19,15 @@ jobs: matrix: os: [ubuntu-latest, macos-14, windows-latest] python: ["3.13", "3.14", "3.14t", "3.15", "3.15t"] - exclude: - # 3.15 is still pre-release (alpha at time of writing) and the - # free-threaded Windows binary distributed via setup-python's - # python-versions repo has flaked here at least once - # (concurrent jobs racing for the same download). Re-add this - # cell once 3.15t has shipped a stable Windows release; until - # then ubuntu and macos provide free-threaded 3.15 coverage. - - os: windows-latest - python: "3.15t" steps: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 with: + # Every entry is a final release (3.15.0 shipped 2026-10-09; + # setup-python's manifest carries 3.15 and 3.15t for all three + # OSes), so no allow-prereleases. python-version: ${{ matrix.python }} - allow-prereleases: true - name: Install build deps and package run: | @@ -53,3 +46,32 @@ jobs: run: | python -m pytest tests/cpython_suite --tb=short || true shell: bash + + test-all-backends: + # Every optional backend (fmt, ryu_opt, wuffs, stock) compiled in, + # so tests/test_backends.py can check each one against stock. The + # wheels only ship the defaults; this job is what keeps the others + # honest. + name: test / all backends / py3.14 / ubuntu + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-python@v6 + with: + python-version: "3.14" + + - name: Install with every backend compiled in + run: | + python -m pip install --upgrade pip + python -m pip install ".[test]" \ + -C cmake.define.FLOATIUM_FORMAT_BACKEND=all \ + -C cmake.define.FLOATIUM_PARSE_BACKEND=all + + - name: Show info + run: | + python -c "import floatium; print(floatium.info())" + + - name: Run unit + parity + edge + backend tests + run: | + python -m pytest tests -x --tb=short --ignore=tests/cpython_suite diff --git a/.github/workflows/cpython-suite.yml b/.github/workflows/cpython-suite.yml index 7f29fe3..4191e01 100644 --- a/.github/workflows/cpython-suite.yml +++ b/.github/workflows/cpython-suite.yml @@ -34,7 +34,6 @@ jobs: - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python }} - allow-prereleases: true - run: python -m pip install --upgrade pip && python -m pip install . diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 12d4aec..c11eefe 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -40,18 +40,26 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: pypa/cibuildwheel@v3.4.1 + - name: Build wheels (cp313, cp314, cp314t, cp315, cp315t) + uses: pypa/cibuildwheel@v4.3.1 env: # Build config lives in [tool.cibuildwheel] in pyproject.toml. - # cpython-freethreading is still required in cibuildwheel - # 3.4.1 for the cp313t / cp314t selectors — without it they - # fail with "Invalid build selector ... wasn't enabled". The - # token emits a deprecation warning (it is being renamed in a - # future cibuildwheel) but still works and is mandatory here. - # No cpython-prerelease: the build list is all released - # CPython (3.15 is omitted — 3.4.1 has no config for it). + # cibuildwheel 4.x: cp315/cp315t are in the default catalog + # (3.15.0 final since 4.3.1) and free-threaded 3.14+ builds + # need no `enable` token — the old cpython-freethreading enable + # was removed in 4.0.0 along with the 3.13t builds. CIBW_BUILD_VERBOSITY: 1 + + - name: Build wheels (cp313t) + # cibuildwheel 4.x can no longer build 3.13 free-threaded wheels. + # Keep the last 3.x release around for that single identifier + # until 3.13t wheels are dropped; it still needs the (deprecated + # in 3.x) cpython-freethreading enable token. + uses: pypa/cibuildwheel@v3.4.1 + env: + CIBW_BUILD: "cp313t-*" CIBW_ENABLE: cpython-freethreading + CIBW_BUILD_VERBOSITY: 1 - uses: actions/upload-artifact@v5 with: diff --git a/pyproject.toml b/pyproject.toml index 8f0c38e..2d055ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,12 +49,13 @@ FLOATIUM_FORMAT_BACKEND = "fmt_opt" FLOATIUM_PARSE_BACKEND = "fast_float" [tool.cibuildwheel] -# CPython 3.13–3.14, incl. free-threaded (cp313t / cp314t). 3.15 is not -# listed: cibuildwheel 3.4.1 has no 3.15 in its interpreter catalog yet -# and rejects a `cp315-*` selector outright. Add `cp315-* cp315t-*` here -# (and bump cibuildwheel) once 3.15 reaches beta and cibuildwheel ships -# configs for it; 3.15 users install from the sdist until then. -build = "cp313-* cp313t-* cp314-* cp314t-*" +# CPython 3.13–3.15, incl. free-threaded 3.14t / 3.15t. cibuildwheel 4.x +# builds cp315 / cp315t (3.15.0 final) by default and needs no `enable` +# flag for free-threaded 3.14+. cp313t is NOT in this list: cibuildwheel +# 4.0 dropped the experimental 3.13 free-threaded builds, so wheels.yml +# runs a second, pinned cibuildwheel 3.4.1 step with CIBW_BUILD=cp313t-* +# to keep shipping 3.13t wheels. +build = "cp313-* cp314-* cp314t-* cp315-* cp315t-*" skip = "*-musllinux_i686 pp*" test-extras = ["test"] test-command = "pytest {project}/tests -x -q --ignore={project}/tests/cpython_suite" From edb7386ed3b95f706a9f5fb6a4b999d11672e14c Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 19 Oct 2026 22:32:35 +0200 Subject: [PATCH 03/13] fix parity and lifecycle bugs found in review; test every backend - format(x, "r") returned repr(); stock float.__format__ raises ValueError. Drop the 'r' branch from parse_simple_spec so the spec falls through to the original and errors exactly like stock. The parity test used to skip "r" instead of checking it; it now asserts the same exception type and message. - 3.15+ debug builds: install()/uninstall() left PyFloat_Type with tp_version_tag == 0 until the next lookup, and init_static_type() asserts a non-zero tag on static builtin types whenever a subinterpreter is created. Re-assign a tag immediately via PyUnstable_Type_AssignVersionTag() after invalidating. - floatium.enabled(False) re-installed the build-default backends on exit instead of the ones that were active. - The parse hook now only forwards strings made of [0-9.eE+-] to the backend, so parity no longer depends on how permissive a backend's strtod is: libc strtod (the "stock" backend) accepted hex floats and nan(...), and honoured LC_NUMERIC (float("1,5") == 1.5 under de_DE). - Wuffs: integer-part digits beyond the 800-digit HPD cap did not advance the decimal point, so "1" + 800 zeros + "e-800" parsed as 0.1. Local one-line fix in the vendored header, re-applied by tools/sync_wuffs.sh and documented in README.vendor; also present in upstream Wuffs main. - New tests/test_backends.py checks every compiled-in format and parse backend against stock in-process (random bit patterns, subnormals, long mantissas, 30+ specs). Co-Authored-By: Claude Fable 5.1 --- README.md | 7 +- floatium/__init__.py | 21 +++- src/slots.cc | 81 ++++++++-------- tests/test_backends.py | 143 ++++++++++++++++++++++++++++ tests/test_edge.py | 51 ++++++++++ tests/test_parity.py | 28 +++++- third_party/wuffs/README.vendor | 11 ++- third_party/wuffs/floatconv_wuffs.h | 12 ++- tools/sync_wuffs.sh | 34 +++++++ 9 files changed, 336 insertions(+), 52 deletions(-) create mode 100644 tests/test_backends.py diff --git a/README.md b/README.md index 1c11b9f..3342e36 100644 --- a/README.md +++ b/README.md @@ -194,8 +194,11 @@ See [PARITY.md](PARITY.md) for the full list. Summary: - **Free-threaded builds.** Supported; patching must happen at import time (the only safe window). Method-cache invalidation uses `PyType_Modified()` except on 3.15 debug builds (where that call - asserts on static types), in which case the wrapper falls back to - writing `tp_version_tag = 0` directly. + asserts on static types), in which case the wrapper clears + `tp_version_tag` directly. Either way the type is immediately given a + fresh tag via `PyUnstable_Type_AssignVersionTag()`, because 3.15+ + requires static builtin types to carry a non-zero tag whenever a + subinterpreter starts. - **`float("1_000.5")` and `float("inf")` take the fallback path.** Both parse correctly (output is bit-identical to stock) but don't benefit from `fast_float`'s speed. diff --git a/floatium/__init__.py b/floatium/__init__.py index 0409ef9..879c668 100644 --- a/floatium/__init__.py +++ b/floatium/__init__.py @@ -84,7 +84,8 @@ class enabled: assert repr(0.1) == '0.1' ``enabled(False)`` is the inverse: it ensures floatium is NOT - installed within the block, then restores. Useful for measuring + installed within the block, then restores (with the backends that + were active on entry). Useful for measuring stock behavior from a process that has autopatch on, or for handing a single hot section to stock CPython:: @@ -95,7 +96,13 @@ class enabled: when ``active=True`` and floatium isn't already installed. """ - __slots__ = ("_active", "_format_backend", "_parse_backend", "_was_patched") + __slots__ = ( + "_active", + "_format_backend", + "_parse_backend", + "_prev_backends", + "_was_patched", + ) def __init__( self, @@ -107,9 +114,16 @@ def __init__( self._format_backend = format_backend self._parse_backend = parse_backend self._was_patched = False + self._prev_backends: tuple[str | None, str | None] = (None, None) def __enter__(self) -> None: self._was_patched = is_patched() + if self._was_patched: + # Remember which backends were active so that the exit path + # restores *those*, not the build defaults (floatium <= 0.14.5 + # silently reset a ryu_opt/wuffs process to fmt_opt/fast_float). + state = info() + self._prev_backends = (state["format_backend"], state["parse_backend"]) if self._active and not self._was_patched: install(format_backend=self._format_backend, parse_backend=self._parse_backend) elif not self._active and self._was_patched: @@ -118,7 +132,8 @@ def __enter__(self) -> None: def __exit__(self, exc_type, exc, tb) -> None: if self._was_patched and not is_patched(): - install(format_backend=self._format_backend, parse_backend=self._parse_backend) + prev_format, prev_parse = self._prev_backends + install(format_backend=prev_format, parse_backend=prev_parse) elif not self._was_patched and is_patched(): uninstall() return None diff --git a/src/slots.cc b/src/slots.cc index eb87b46..ef42905 100644 --- a/src/slots.cc +++ b/src/slots.cc @@ -81,9 +81,10 @@ static PyObject *floatium_float_repr(PyObject *v) { // demo we'd have to port all of that parsing code. That's on the roadmap; // for v0.1 we intercept the common fast path (type in {r, g, G, e, E, f, // F, empty} with optional precision, no fill/align/width/#/,/_) and -// fall back to the original for anything complex. Every unit test we -// ship checks this intercept is byte-identical to stock CPython on the -// handled subset. +// fall back to the original for anything complex (including anything +// stock would reject, so error behaviour stays stock's). Every unit test +// we ship checks this intercept is byte-identical to stock CPython on +// the handled subset. static PyObject *original_format_call(PyObject *self, PyObject *spec) { // g_state.orig_format is a method descriptor (PyMethodDescrObject). @@ -99,8 +100,11 @@ static PyObject *original_format_call(PyObject *self, PyObject *spec) { // Returns 1 if the spec is one of the simple forms we handle inline: // "" -> repr -// "r" -> repr // "[.precision]{g,G,e,E,f,F}" +// +// 'r' is deliberately NOT accepted: it is not a float format code and +// stock float.__format__ raises ValueError on it (floatium <= 0.14.5 +// returned repr() — a parity bug). // Returns 0 and leaves *out_code / *out_precision untouched otherwise. static int parse_simple_spec(const char *spec, Py_ssize_t len, char *out_code, int *out_precision, @@ -133,12 +137,6 @@ static int parse_simple_spec(const char *spec, Py_ssize_t len, *out_code = t; *out_precision = precision < 0 ? 6 : precision; return 1; - case 'r': - if (precision >= 0) return 0; - *out_code = 'r'; - *out_precision = 0; - *out_flags = FLOATIUM_ADD_DOT_0; - return 1; default: return 0; } @@ -251,13 +249,20 @@ static PyObject *parse_ascii_with_backend(const char *buf, Py_ssize_t len) { } if (len == 0) return nullptr; - // Reject strings containing chars we don't inline-handle (underscores, - // embedded whitespace, non-ASCII). The _Py_fast_float_strtod backend - // already stops at the first non-numeric char, but CPython's accepted - // "1_000.5" etc. which require stripping. Leave those to the original. + // Only hand the backend a string made of the characters a decimal + // float literal can contain. Everything else goes to the original + // tp_new: PEP 515 underscores ("1_000.5"), non-ASCII digits and + // whitespace (CPython transliterates those), hex floats, "inf" / + // "nan" / "nan(...)", locale decimal commas. This keeps parity + // independent of how permissive a backend's strtod is — libc strtod + // (the "stock" backend) accepts "0x10" and "nan(1)" and honours + // LC_NUMERIC, none of which float() does. for (Py_ssize_t i = 0; i < len; ++i) { unsigned char c = (unsigned char)buf[i]; - if (c == '_' || c > 0x7f) return nullptr; + if (!((c >= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' + || c == '+' || c == '-')) { + return nullptr; + } } // Copy to a null-terminated buffer for strtod (backend doesn't take a @@ -346,6 +351,26 @@ static PyObject *floatium_float_vectorcall(PyObject *type, // --------------------------------------------------------------------------- // install() / uninstall() // --------------------------------------------------------------------------- +// Invalidate PyFloat_Type's method cache after swapping slots / __format__. +// +// PyType_Modified() is the supported call, but 3.15+ debug builds assert +// against it on static builtin types (Objects/typeobject.c: +// _PyType_Modified_Unlocked). Either path leaves tp_version_tag == 0 +// until the next attribute lookup — and 3.15+ requires static builtin +// types to carry a non-zero tag at all times: init_static_type() asserts +// it when a *subinterpreter* is created. So after invalidating, hand the +// type a fresh tag straight away via the public unstable API (3.12+). +// That is exactly what the lazy path would have assigned on the next +// lookup, so semantics are unchanged; it just closes the window. +static void floatium_invalidate_float_type(void) { +#if PY_VERSION_HEX >= 0x030F0000 && defined(Py_DEBUG) + PyFloat_Type.tp_version_tag = 0; +#else + PyType_Modified(&PyFloat_Type); +#endif + (void)PyUnstable_Type_AssignVersionTag(&PyFloat_Type); +} + extern "C" { int floatium_install(const FloatiumFormatBackend *fmt_b, @@ -413,21 +438,7 @@ int floatium_install(const FloatiumFormatBackend *fmt_b, PyFloat_Type.tp_new = floatium_float_new; PyFloat_Type.tp_vectorcall = floatium_float_vectorcall; - // Invalidate PyFloat_Type's method cache by clearing the version tag. - // PyType_Modified() asserts against static builtin types in debug builds - // (3.15+). Clearing tp_version_tag is the kernel of what PyType_Modified - // does for the mcache and is safe on static types. Subtypes of float - // (if any existed) would need their own invalidation, but nothing in - // stdlib subclasses float. - // PyType_Modified is the supported way to invalidate method caches, - // but 3.15 debug builds assert against it on static builtin types - // (Objects/typeobject.c:type_modified_unlocked). Use the version-tag - // trick there; use PyType_Modified everywhere else. -#if PY_VERSION_HEX >= 0x030F0000 && defined(Py_DEBUG) - PyFloat_Type.tp_version_tag = 0; -#else - PyType_Modified(&PyFloat_Type); -#endif + floatium_invalidate_float_type(); g_state.patched = true; return 0; @@ -449,15 +460,7 @@ int floatium_uninstall(void) { Py_CLEAR(g_state.orig_format); } - // PyType_Modified is the supported way to invalidate method caches, - // but 3.15 debug builds assert against it on static builtin types - // (Objects/typeobject.c:type_modified_unlocked). Use the version-tag - // trick there; use PyType_Modified everywhere else. -#if PY_VERSION_HEX >= 0x030F0000 && defined(Py_DEBUG) - PyFloat_Type.tp_version_tag = 0; -#else - PyType_Modified(&PyFloat_Type); -#endif + floatium_invalidate_float_type(); g_state.tp_repr = nullptr; g_state.tp_new = nullptr; diff --git a/tests/test_backends.py b/tests/test_backends.py new file mode 100644 index 0000000..ebccca0 --- /dev/null +++ b/tests/test_backends.py @@ -0,0 +1,143 @@ +"""Every compiled-in backend must match stock CPython, not just the default. + +The wheels ship fmt_opt + fast_float, but the CMake "all" build (used by +the test-all-backends CI job) also compiles fmt, ryu_opt, wuffs and +stock. Before this file only one test ever exercised a non-default +backend. Comparisons are done in-process: stock output is collected with +floatium uninstalled, then each backend is installed in turn. +""" + +from __future__ import annotations + +import math +import random +import struct + +import pytest + +import floatium + +_INFO = floatium.info() +FORMAT_BACKENDS = _INFO["available_format_backends"].split(",") +PARSE_BACKENDS = _INFO["available_parse_backends"].split(",") + + +def _bits(b: int) -> float: + return struct.unpack(" dict: + """{(index, spec): outcome} for every value/spec; "repr" is a pseudo-spec. + + Keys use the *index* into VALUES, not the value: 0.0 and -0.0 compare + equal and would otherwise collapse into one dict slot. + """ + out = {} + for i, x in enumerate(VALUES): + out[(i, "repr")] = _outcome(repr, x) + for spec in SPECS: + out[(i, spec)] = _outcome(format, x, spec) + return out + + +@pytest.fixture(scope="module") +def stock_format(): + floatium.uninstall() + return _format_table() + + +@pytest.fixture(scope="module") +def stock_parse(): + floatium.uninstall() + return {s: _parse_outcome(s) for s in PARSE_STRINGS} + + +@pytest.mark.parametrize("backend", FORMAT_BACKENDS) +def test_format_backend_matches_stock(backend, stock_format): + floatium.uninstall() + floatium.install(format_backend=backend) + try: + assert floatium.info()["format_backend"] == backend + ours = _format_table() + bad = [((VALUES[i], spec), expected, ours[(i, spec)]) + for (i, spec), expected in stock_format.items() + if ours[(i, spec)] != expected] + assert not bad, f"{len(bad)} divergences, first: {bad[:5]}" + finally: + floatium.uninstall() + floatium.install() + + +@pytest.mark.parametrize("backend", PARSE_BACKENDS) +def test_parse_backend_matches_stock(backend, stock_parse): + floatium.uninstall() + floatium.install(parse_backend=backend) + try: + assert floatium.info()["parse_backend"] == backend + bad = [(s[:60], exp, _parse_outcome(s)) for s, exp in stock_parse.items() + if _parse_outcome(s) != exp] + assert not bad, f"{len(bad)} divergences, first: {bad[:5]}" + finally: + floatium.uninstall() + floatium.install() diff --git a/tests/test_edge.py b/tests/test_edge.py index 2f996fa..606969f 100644 --- a/tests/test_edge.py +++ b/tests/test_edge.py @@ -220,3 +220,54 @@ def test_round_trip_known_problem_values(): ] for x in tough: assert float(repr(x)) == x, repr(x) + + +def _non_default_backend(kind: str) -> str | None: + info = floatium.info() + default = info[f"default_{kind}_backend"] + others = [b for b in info[f"available_{kind}_backends"].split(",") if b != default] + return others[0] if others else None + + +def test_enabled_false_restores_the_backends_that_were_active(): + # Regression: floatium <= 0.14.5 re-installed the *build defaults* on + # exit, so a process running ryu_opt/wuffs silently came back as + # fmt_opt/fast_float after any enabled(False) block. + fb = _non_default_backend("format") + pb = _non_default_backend("parse") + if fb is None and pb is None: + pytest.skip("only the default backends are compiled in") + floatium.uninstall() + floatium.install(format_backend=fb, parse_backend=pb) + try: + expected = (floatium.info()["format_backend"], floatium.info()["parse_backend"]) + with floatium.enabled(False): + assert not floatium.is_patched() + info = floatium.info() + assert floatium.is_patched() + assert (info["format_backend"], info["parse_backend"]) == expected + finally: + floatium.uninstall() + floatium.install() + + +@pytest.mark.parametrize("s", [ + "0x10", "0x1p3", "0X1A", # hex floats: libc strtod accepts them + "nan(1)", "nan(abc)", "NAN()", # nan(n-char-sequence): libc accepts + "1,5", "1.5,", ",5", # locale decimal comma (de_DE etc.) + "1e5e5", "--1", "1e", "e5", +]) +def test_every_parse_backend_rejects_non_decimal_syntax(s): + # The hook pre-screens the character set so that parity cannot depend + # on how permissive a backend's strtod is. The "stock" (libc) backend + # in floatium <= 0.14.5 let hex floats and nan(...) through, and + # under LC_NUMERIC=de_DE parsed "1,5" as 1.5. + for pb in floatium.info()["available_parse_backends"].split(","): + floatium.uninstall() + floatium.install(parse_backend=pb) + try: + with pytest.raises(ValueError): + float(s) + finally: + floatium.uninstall() + floatium.install() diff --git a/tests/test_parity.py b/tests/test_parity.py index 551de36..d7cedfc 100644 --- a/tests/test_parity.py +++ b/tests/test_parity.py @@ -36,7 +36,7 @@ _FORMAT_SPECS = ["", ".2f", ".5f", ".0f", ".2e", ".5e", ".10e", "g", "G", "e", "E", "f", "F", - ".3g", ".6g", ".10g", "r"] + ".3g", ".6g", ".10g"] def _stock(expr: str) -> str: @@ -71,9 +71,6 @@ def test_str_parity(x): @pytest.mark.parametrize("x", _CORPUS) @pytest.mark.parametrize("spec", _FORMAT_SPECS) def test_format_parity(x, spec): - # Skip 'r' for floats where the built-in behaves oddly; not a real spec. - if spec == "r": - pytest.skip("'r' is not a real format spec for __format__") stock = _stock(f"format({x!r}, {spec!r})") with floatium.enabled(): ours = format(x, spec) @@ -82,6 +79,29 @@ def test_format_parity(x, spec): ) +@pytest.mark.parametrize("spec", ["r", ".2r", "r2"]) +@pytest.mark.parametrize("x", [0.0, 1.5, -2.5e100]) +def test_format_r_spec_raises_like_stock(x, spec): + # 'r' is not a float format code. Stock raises ValueError; floatium + # <= 0.14.5 answered with repr() instead (a parity bug). Compare the + # exception type and message, not just "raises". + import os + env = os.environ.copy() + env["FLOATIUM_AUTOPATCH"] = "0" + code = ( + "import sys\n" + f"try:\n format({x!r}, {spec!r})\n" + "except Exception as e:\n sys.stdout.write(type(e).__name__ + ': ' + str(e))\n" + "else:\n sys.stdout.write('no error')\n" + ) + stock = subprocess.run([sys.executable, "-c", code], env=env, + capture_output=True, text=True, check=True).stdout + assert stock.startswith("ValueError") + with floatium.enabled(), pytest.raises(ValueError) as ei: + format(x, spec) + assert f"ValueError: {ei.value}" == stock + + @pytest.mark.parametrize("s", [ "0", "0.0", "1", "1.0", "-1", "3.14159", "1e10", "1e-10", "1.5e+5", "-2.5e-3", "1234567890", "0.00001", diff --git a/third_party/wuffs/README.vendor b/third_party/wuffs/README.vendor index b7b75e1..f1ba990 100644 --- a/third_party/wuffs/README.vendor +++ b/third_party/wuffs/README.vendor @@ -40,7 +40,16 @@ extractions, not algorithmic changes. From the file header: 5. Added _MSC_VER/_M_X64 branch to multiply_u64 and clz_u64. 6. No algorithmic changes. -These edits are tracked by the dtoa_wuff branch on +Plus one floatium-local bug fix, re-applied by tools/sync_wuffs.sh: + + 7. high_prec_dec parse: integer-part digits beyond the 800-digit HPD + cap now still advance the decimal point (`dp++`). Upstream (also in + wuffs main as of 2026-10, 0.4.0-alpha.11) drops them, so e.g. + "1" + "0"*800 + "e-800" parses as 0.1 instead of 1.0, and any + decimal whose integer part exceeds 800 digits comes out 10^k too + small. Reported upstream; drop this hunk once Wuffs fixes it. + +Edits 1-6 are tracked by the dtoa_wuff branch on https://github.com/eendebakpt/cpython and synced here verbatim. Regeneration: tools/sync_wuffs.sh (default: copies from the diff --git a/third_party/wuffs/floatconv_wuffs.h b/third_party/wuffs/floatconv_wuffs.h index 4ade248..3c9e421 100644 --- a/third_party/wuffs/floatconv_wuffs.h +++ b/third_party/wuffs/floatconv_wuffs.h @@ -1024,9 +1024,15 @@ wuffs_private_impl__high_prec_dec__parse(wuffs_private_impl__high_prec_dec* h, if (nd < WUFFS_PRIVATE_IMPL__HPD__DIGITS_PRECISION) { h->digits[nd++] = (uint8_t)(*p - '0'); dp = (int32_t)nd; - } else if ('0' != *p) { - // Long-tail non-zeroes set the truncated bit. - h->truncated = true; + } else { + // floatium-local fix (see README.vendor, local modification 7): + // integer-part digits past the precision cap still shift the + // decimal point, otherwise "1" + 800 zeros parses as 1e799. + dp++; + if ('0' != *p) { + // Long-tail non-zeroes set the truncated bit. + h->truncated = true; + } } } else if (*p == ((options & diff --git a/tools/sync_wuffs.sh b/tools/sync_wuffs.sh index 5d17f56..524c344 100755 --- a/tools/sync_wuffs.sh +++ b/tools/sync_wuffs.sh @@ -44,6 +44,40 @@ mkdir -p "$dest" git -C "$CP" show "$BRANCH:Python/_ryu/floatconv_wuffs.h" > "$dest/floatconv_wuffs.h" git -C "$CP" show "$BRANCH:Python/_ryu/LICENSE-Wuffs" > "$dest/LICENSE-Wuffs" +# Local fix (README.vendor, local modification 7; reported upstream): +# integer-part digits beyond the 800-digit HPD cap must still advance +# the decimal point. Fails loudly if the upstream hunk changed shape — +# check whether upstream fixed it and drop this step if so. +python3 - "$dest/floatconv_wuffs.h" <<'PY' +import pathlib, sys +p = pathlib.Path(sys.argv[1]) +s = p.read_text() +old = ( + " dp = (int32_t)nd;\n" + " } else if ('0' != *p) {\n" + " // Long-tail non-zeroes set the truncated bit.\n" + " h->truncated = true;\n" + " }" +) +new = ( + " dp = (int32_t)nd;\n" + " } else {\n" + " // floatium-local fix (see README.vendor, local modification 7):\n" + " // integer-part digits past the precision cap still shift the\n" + " // decimal point, otherwise \"1\" + 800 zeros parses as 1e799.\n" + " dp++;\n" + " if ('0' != *p) {\n" + " // Long-tail non-zeroes set the truncated bit.\n" + " h->truncated = true;\n" + " }\n" + " }" +) +if s.count(old) != 1: + sys.exit("sync_wuffs: HPD integer-digit hunk not found exactly once; " + "upstream may have changed (or fixed) it — review by hand") +p.write_text(s.replace(old, new)) +PY + # Adapter (pystrtod_wuffs.h): apply the only local mod — rewrite # `#include "_ryu/floatconv_wuffs.h"` → `#include "floatconv_wuffs.h"` # so it resolves against floatium's flatter third_party/wuffs/ layout. From 9b9bae4e155c5e7b49d11ce81f7e79dec7c85168 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 19 Oct 2026 22:32:35 +0200 Subject: [PATCH 04/13] vendor: fmt 12.1.0 -> 12.2.0, fast_float v8.2.5 -> v8.3.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fmt 12.2.0 (1be298e): Dragonbox full cache is now on by default (FMT_USE_FULL_CACHE_DRAGONBOX=1 unless __OPTIMIZE_SIZE__), which is the shortest-repr path floatium uses for repr()/str(). No changes to the format_float / Dragon4 code reached by modes 2 and 3. fast_float v8.3.2 (42f2548): unrolled integer-digit scan, 4-digit SWAR on GCC as well as clang, skipped rounding-mode probe for long mantissas, compile-time format folding — all on the from_chars hot path — plus compiler compatibility fixes. No correctness changes to the double path. Synced with tools/sync_fmt.sh and tools/sync_fast_float.sh; vendored files remain byte-identical to upstream. Ryu is already at upstream HEAD; the Wuffs float-parsing code is unchanged upstream since 2023. Co-Authored-By: Claude Fable 5.1 --- third_party/fast_float/README.vendor | 4 +- third_party/fast_float/ascii_number.h | 240 +++- third_party/fast_float/bigint.h | 4 +- third_party/fast_float/decimal_to_binary.h | 38 +- third_party/fast_float/digit_comparison.h | 8 +- third_party/fast_float/fast_float.h | 12 +- third_party/fast_float/float_common.h | 319 +++-- third_party/fast_float/parse_number.h | 190 ++- third_party/fmt/LICENSE | 7 - third_party/fmt/README.vendor | 4 +- third_party/fmt/base.h | 576 ++++---- third_party/fmt/format-inl.h | 1394 ++++++++++---------- third_party/fmt/format.h | 345 ++--- 13 files changed, 1704 insertions(+), 1437 deletions(-) diff --git a/third_party/fast_float/README.vendor b/third_party/fast_float/README.vendor index 9033d50..759c1fa 100644 --- a/third_party/fast_float/README.vendor +++ b/third_party/fast_float/README.vendor @@ -2,8 +2,8 @@ fast_float — Eisel-Lemire string-to-double parser ================================================= Upstream: https://github.com/fastfloat/fast_float -Tag: v8.2.5 -Commit: 05087a3 (2026-04-16) +Tag: v8.3.2 +Commit: 42f2548 (2026-10-09) License: Apache-2.0 OR MIT OR BSL-1.0 (see LICENSE-APACHE, LICENSE-MIT, LICENSE-BOOST) diff --git a/third_party/fast_float/ascii_number.h b/third_party/fast_float/ascii_number.h index 12c2fdd..28d6113 100644 --- a/third_party/fast_float/ascii_number.h +++ b/third_party/fast_float/ascii_number.h @@ -32,7 +32,13 @@ template fastfloat_really_inline constexpr bool has_simd_opt() { // able to optimize it well. template fastfloat_really_inline constexpr bool is_integer(UC c) noexcept { - return (unsigned)(c - UC('0')) <= 9u; + // UC can be signed (wchar_t is a signed int on Linux and macOS), and the + // subtraction is then promoted to int and overflows for code units near the + // bottom of the range. Subtracting in the unsigned type wraps instead, which + // selects the same code units, as ch_to_digit already does. + using UnsignedUC = typename std::make_unsigned::type; + return static_cast(static_cast(c) - + static_cast(UC('0'))) <= 9u; } fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { @@ -223,8 +229,8 @@ simd_parse_if_eight_digits_unrolled(char16_t const *chars, return false; FASTFLOAT_SIMD_RESTORE_WARNINGS #else - (void)chars; - (void)i; + static_cast(chars); + static_cast(i); return false; #endif // FASTFLOAT_SSE2 } @@ -266,6 +272,19 @@ loop_parse_if_eight_digits(char const *&p, char const *const pend, p)); // in rare cases, this will overflow, but that's ok p += 8; } + // Consume a remaining 4-7 digit run in a single SWAR step instead of + // byte-by-byte (reuses the existing 4-digit helpers). The parsed result is + // identical either way. Historically gated to clang because gcc regressed on + // short remainders, but that verdict predates the span-elision restructure; + // with the leaner hot path the 4-digit step now wins on gcc as well. + if ((pend - p) >= 4) { + uint32_t const val4 = read4_to_u32(p); + if (is_made_of_four_digits_fast(val4)) { + i = i * 10000 + + parse_four_digits_unrolled(val4); // may overflow, that's ok + p += 4; + } + } } enum class parse_error { @@ -274,7 +293,7 @@ enum class parse_error { missing_integer_after_sign, // A sign must be followed by an integer or dot. missing_integer_or_dot_after_sign, - // [JSON-only] The integer part must not have leading zeros. + // [JSON/JavaScript-only] The integer part must not have leading zeros. leading_zeros_in_integer_part, // [JSON-only] The integer part must have at least one digit. no_digits_in_integer_part, @@ -285,19 +304,26 @@ enum class parse_error { no_digits_in_mantissa, // Scientific notation requires an exponential part. missing_exponential_part, + // [JavaScript-only, sloppy mode] The integer part is a legacy octal literal + // (a leading zero followed by octal digits only), which is not a decimal + // number. Parse it in base 8 instead. + legacy_octal_integer_part, }; template struct parsed_number_string_t { int64_t exponent{0}; uint64_t mantissa{0}; UC const *lastmatch{nullptr}; + // The field order matters: placing 'error' next to the booleans avoids + // padding, keeping the struct at 64 bytes on 64-bit systems instead of 72. + // See https://github.com/fastfloat/fast_float/issues/418 + parse_error error{parse_error::no_error}; bool negative{false}; bool valid{false}; bool too_many_digits{false}; // contains the range of the significant digits span integer{}; // non-nullable span fraction{}; // nullable - parse_error error{parse_error::no_error}; }; using byte_span = span; @@ -315,10 +341,18 @@ report_parse_error(UC const *p, parse_error error) { // Assuming that you use no more than 19 digits, this will // parse an ASCII string. -template +// +// store_spans is a *runtime* flag (not a template parameter, deliberately: a +// template would create a second instantiation of this whole function and the +// extra icache pressure wipes out the gain). When false, the integer/fraction +// spans (read only by the rare digit_comp slow path) are not materialized, +// which keeps the fat parsed_number_string_t off the hot path. The caller +// re-parses with store_spans=true if the slow path is actually reached. +template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t -parse_number_string(UC const *p, UC const *pend, - parse_options_t options) noexcept { +parse_number_string_impl(UC const *p, UC const *pend, + parse_options_t options, + bool store_spans) noexcept { chars_format const fmt = detail::adjust_for_feature_macros(options.format); UC const decimal_point = options.decimal_point; @@ -354,17 +388,42 @@ parse_number_string(UC const *p, UC const *pend, uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) - while ((p != pend) && is_integer(*p)) { - // a multiplication by 10 is cheaper than an arbitrary integer - // multiplication - i = 10 * i + - uint64_t(*p - - UC('0')); // might overflow, we will handle the overflow later + // Straight-line unroll of the integer-part scan: most integer parts are + // 1-5 digits, so peeling the first iterations eliminates the loop back-edge + // for the common case. Semantics are identical to the original `while` loop: + // i = 10*i + digit, advancing p. + if ((p != pend) && is_integer(*p)) { + i = uint64_t(*p - UC('0')); ++p; + if ((p != pend) && is_integer(*p)) { + i = 10 * i + uint64_t(*p - UC('0')); + ++p; + if ((p != pend) && is_integer(*p)) { + i = 10 * i + uint64_t(*p - UC('0')); + ++p; + if ((p != pend) && is_integer(*p)) { + i = 10 * i + uint64_t(*p - UC('0')); + ++p; + if ((p != pend) && is_integer(*p)) { + i = 10 * i + uint64_t(*p - UC('0')); + ++p; + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - UC('0')); // might overflow, handled later + ++p; + } + } + } + } + } } UC const *const end_of_integer_part = p; int64_t digit_count = int64_t(end_of_integer_part - start_digits); - answer.integer = span(start_digits, size_t(digit_count)); + if (store_spans) { + answer.integer = span(start_digits, size_t(digit_count)); + } FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { // at least 1 digit in integer part, without leading zeros if (digit_count == 0) { @@ -375,6 +434,30 @@ parse_number_string(UC const *p, UC const *pend, parse_error::leading_zeros_in_integer_part); } } + else FASTFLOAT_IF_CONSTEXPR17(basic_javascript_fmt) { + // ECMAScript DecimalIntegerLiteral is "0" or a non-zero digit followed by + // digits: no leading zeros. Unlike JSON, the integer part may be empty + // (".5"); the no_digits_in_mantissa check below still rejects ".". + if ((digit_count > 1) && (start_digits[0] == UC('0'))) { + if (!uint64_t(fmt & detail::basic_javascript_sloppy_fmt)) { + return report_parse_error( + start_digits, parse_error::leading_zeros_in_integer_part); + } + // Sloppy mode (Annex B): a NonOctalDecimalIntegerLiteral has a leading + // zero and at least one digit that is 8 or 9 ("08.5" is 8.5). With + // octal digits only, it is a LegacyOctalIntegerLiteral ("0775"), which + // is not a decimal number: report it so the caller can parse it in + // base 8. + bool has_non_octal_digit = false; + for (UC const *q = start_digits; q != end_of_integer_part; ++q) { + has_non_octal_digit |= (*q >= UC('8')); + } + if (!has_non_octal_digit) { + return report_parse_error(start_digits, + parse_error::legacy_octal_integer_part); + } + } + } int64_t exponent = 0; bool const has_decimal_point = (p != pend) && (*p == decimal_point); @@ -391,7 +474,9 @@ parse_number_string(UC const *p, UC const *pend, i = i * 10 + digit; // in rare cases, this will overflow, but that's ok } exponent = before - p; - answer.fraction = span(before, size_t(p - before)); + if (store_spans) { + answer.fraction = span(before, size_t(p - before)); + } digit_count -= exponent; } FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { @@ -476,29 +561,35 @@ parse_number_string(UC const *p, UC const *pend, if (digit_count > 19) { answer.too_many_digits = true; - // Let us start again, this time, avoiding overflows. - // We don't need to call if is_integer, since we use the - // pre-tokenized spans from above. - i = 0; - p = answer.integer.ptr; - UC const *int_end = p + answer.integer.len(); - uint64_t const minimal_nineteen_digit_integer{1000000000000000000}; - while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { - i = i * 10 + uint64_t(*p - UC('0')); - ++p; - } - if (i >= minimal_nineteen_digit_integer) { // We have a big integer - exponent = end_of_integer_part - p + exp_number; - } else { // We have a value with a fractional component. - p = answer.fraction.ptr; - UC const *frac_end = p + answer.fraction.len(); - while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + // The truncation recompute below reads the integer/fraction spans. When + // store_spans is false we didn't materialize them, so just flag + // too_many_digits; the caller re-parses with store_spans=true to obtain + // the corrected mantissa/exponent before taking the slow path. + if (store_spans) { + // Let us start again, this time, avoiding overflows. + // We don't need to call if is_integer, since we use the + // pre-tokenized spans from above. + i = 0; + p = answer.integer.ptr; + UC const *int_end = p + answer.integer.len(); + uint64_t const minimal_nineteen_digit_integer{1000000000000000000}; + while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { i = i * 10 + uint64_t(*p - UC('0')); ++p; } - exponent = answer.fraction.ptr - p + exp_number; + if (i >= minimal_nineteen_digit_integer) { // We have a big integer + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p = answer.fraction.ptr; + UC const *frac_end = p + answer.fraction.len(); + while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + uint64_t(*p - UC('0')); + ++p; + } + exponent = answer.fraction.ptr - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value } - // We have now corrected both exponent and i, to a truncated value } } answer.exponent = exponent; @@ -506,6 +597,40 @@ parse_number_string(UC const *p, UC const *pend, return answer; } +// Cold instantiation of the parser: the ECMAScript integer-part rule costs two +// error returns plus the Annex B octal scan, and parse_number_string_impl is +// force-inlined, so an inlined javascript body would enlarge the frame of every +// caller that never asks for it. Out of line, it costs those callers nothing. +template +fastfloat_never_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t +parse_number_string_javascript(UC const *p, UC const *pend, + parse_options_t options, + bool store_spans) noexcept { + return parse_number_string_impl(p, pend, options, + store_spans); +} + +// Public entry point, behaviour unchanged: chars_format::javascript is still +// honoured, it is just selected here once instead of being re-tested inside the +// parser loop. Callers that have already ruled the format out (see +// from_chars_float_advanced) should call parse_number_string_impl directly so +// that not even this test reaches their hot path. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t +parse_number_string(UC const *p, UC const *pend, parse_options_t options, + bool store_spans = true) noexcept { + // JSON and JavaScript are mutually exclusive, so only the non-JSON + // instantiation has to look at the flag. + FASTFLOAT_IF_CONSTEXPR17(!basic_json_fmt) { + if fastfloat_unlikely (uint64_t(options.format & + detail::basic_javascript_fmt)) { + return parse_number_string_javascript(p, pend, options, store_spans); + } + } + return parse_number_string_impl(p, pend, options, + store_spans); +} + template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t parse_int_string(UC const *p, UC const *pend, T &value, @@ -545,9 +670,10 @@ parse_int_string(UC const *p, UC const *pend, T &value, UC const *const start_digits = p; - FASTFLOAT_IF_CONSTEXPR17((std::is_same::value)) { + FASTFLOAT_IF_CONSTEXPR17( + (std::is_same::value && sizeof(UC) == 1)) { if (base == 10) { - const size_t len = (size_t)(pend - p); + const size_t len = static_cast(pend - p); if (len == 0) { if (has_leading_zeros) { value = 0; @@ -592,9 +718,10 @@ parse_int_string(UC const *p, UC const *pend, T &value, uint32_t magic = ((digits + 0x46464646u) | (digits - 0x30303030u)) & 0x80808080u; - uint32_t tz = (uint32_t)countr_zero_32(magic); // 7, 15, 23, 31, or 32 + uint32_t tz = + static_cast(countr_zero_32(magic)); // 7, 15, 23, 31, or 32 uint32_t nd = (tz == 32) ? 4 : (tz >> 3); - nd = (uint32_t)(nd < len ? nd : len); + nd = static_cast(nd < len ? nd : len); if (nd == 0) { if (has_leading_zeros) { value = 0; @@ -630,14 +757,15 @@ parse_int_string(UC const *p, UC const *pend, T &value, answer.ptr = p + nd; return answer; } - value = (uint8_t)((0x640a01 * digits) >> 24); + value = static_cast((0x640a01 * digits) >> 24); answer.ec = std::errc(); answer.ptr = p + nd; return answer; } } - FASTFLOAT_IF_CONSTEXPR17((std::is_same::value)) { + FASTFLOAT_IF_CONSTEXPR17( + (std::is_same::value && sizeof(UC) == 1)) { if (base == 10) { const size_t len = size_t(pend - p); if (len == 0) { @@ -724,14 +852,32 @@ parse_int_string(UC const *p, UC const *pend, T &value, } // this check can be eliminated for all other types, but they will all require // a max_digits(base) equivalent - if (digit_count == max_digits && i < min_safe_u64(base)) { - answer.ec = std::errc::result_out_of_range; - return answer; + if (digit_count == max_digits) { + // At the max_digits boundary the accumulator `i` may have wrapped around + // 2^64. A plain `i < min_safe_u64(base)` test is not sufficient: for any + // base whose max_digits-length range exceeds 2^64 (base 10 reaches + // ~5.4 * 2^64 at 20 digits) the value can wrap a whole multiple of 2^64 and + // land back above min_safe, slipping through. Decide exactly in O(1) using + // the leading digit, following the approach used in simdjson: + // ms == min_safe_u64(base) == base^(max_digits-1), the smallest + // max_digits-length value. + // dmax == the largest leading digit whose number can still fit in u64. + // The leading-digit band [d*ms, (d+1)*ms) has width ms < 2^64, so within + // the single band where d == dmax the value straddles 2^64 at most once, + // and a single threshold separates wrapped from non-wrapped values. A + // leading digit above dmax always overflows; below dmax always fits. + uint64_t const ms = min_safe_u64(base); + uint64_t const dmax = (std::numeric_limits::max)() / ms; + uint64_t const lead = ch_to_digit(*start_digits); + if (lead > dmax || (lead == dmax && i < dmax * ms)) { + answer.ec = std::errc::result_out_of_range; + return answer; + } } // check other types overflow if (!std::is_same::value) { - if (i > uint64_t(std::numeric_limits::max()) + uint64_t(negative)) { + if (i > uint64_t((std::numeric_limits::max)()) + uint64_t(negative)) { answer.ec = std::errc::result_out_of_range; return answer; } @@ -748,8 +894,8 @@ parse_int_string(UC const *p, UC const *pend, T &value, // - reinterpret_casting (~i + 1) would work, but it is not constexpr // this is always optimized into a neg instruction (note: T is an integer // type) - value = T(-std::numeric_limits::max() - - T(i - uint64_t(std::numeric_limits::max()))); + value = T(-(std::numeric_limits::max)() - + T(i - uint64_t((std::numeric_limits::max)()))); #ifdef FASTFLOAT_VISUAL_STUDIO #pragma warning(pop) #endif diff --git a/third_party/fast_float/bigint.h b/third_party/fast_float/bigint.h index 74901e3..665debb 100644 --- a/third_party/fast_float/bigint.h +++ b/third_party/fast_float/bigint.h @@ -619,8 +619,8 @@ struct bigint : pow5_tables<> { // 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])))); + FASTFLOAT_TRY(small_mul(vec, limb((static_cast(small_power_of_5[0]), + small_power_of_5[exp])))); } return true; diff --git a/third_party/fast_float/decimal_to_binary.h b/third_party/fast_float/decimal_to_binary.h index 9487682..5389701 100644 --- a/third_party/fast_float/decimal_to_binary.h +++ b/third_party/fast_float/decimal_to_binary.h @@ -156,10 +156,21 @@ compute_float(int64_t q, uint64_t w) noexcept { return answer; } // next line is safe because -answer.power2 + 1 < 64 - answer.mantissa >>= -answer.power2 + 1; - // Thankfully, we can't have both "round-to-even" and subnormals because - // "round-to-even" only occurs for powers close to 0 in the 32-bit and - // and 64-bit case (with no more than 19 digits). + int const subnormal_shift = -answer.power2 + 1; + answer.mantissa >>= subnormal_shift; + // A subnormal result can also fall exactly between two floats. With at + // most 19 digits this never happens for float and double, but it does for + // std::float16_t (e.g., 2^-25 = 298023223876953125e-25), so we apply the + // same round-to-even test as in the normal case below. + // See script/format_parameters.py. + if (binary::subnormal_ties_possible() && (product.low <= 1) && + (q >= binary::min_exponent_round_to_even()) && + (q <= binary::max_exponent_round_to_even()) && + ((answer.mantissa & 3) == 1)) { + if (((answer.mantissa << subnormal_shift) << shift) == product.high) { + answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up + } + } answer.mantissa += (answer.mantissa & 1); // round up answer.mantissa >>= 1; // There is a weird scenario where we don't have a subnormal but just. @@ -194,16 +205,31 @@ compute_float(int64_t q, uint64_t w) noexcept { answer.mantissa += (answer.mantissa & 1); // round up answer.mantissa >>= 1; - if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { + // Both fix-ups below are rare. They are marked unlikely so that clang keeps + // them as branches instead of folding them into conditional moves that + // every conversion pays for. +#ifdef __clang__ +#pragma clang diagnostic push +#if (!defined(__APPLE_CC__) && __clang_major__ >= 10) || (__clang_major__ >= 13) +#pragma clang diagnostic ignored "-Wc++20-extensions" +#endif +#endif + if fastfloat_clang_unlikely (answer.mantissa >= + (uint64_t(2) + << binary::mantissa_explicit_bits())) { answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); answer.power2++; // undo previous addition } answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); - if (answer.power2 >= binary::infinite_power()) { // infinity + if fastfloat_clang_unlikely (answer.power2 >= binary::infinite_power()) { + // infinity answer.power2 = binary::infinite_power(); answer.mantissa = 0; } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif return answer; } diff --git a/third_party/fast_float/digit_comparison.h b/third_party/fast_float/digit_comparison.h index c2c83b0..6aac952 100644 --- a/third_party/fast_float/digit_comparison.h +++ b/third_party/fast_float/digit_comparison.h @@ -330,10 +330,6 @@ parse_mantissa(bigint &result, parsed_number_string_t &num, } } } - - if (counter != 0) { - add_native(result, limb(powers_of_ten_uint64[counter]), value); - } } template @@ -400,8 +396,8 @@ inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp( round(answer, [ord](adjusted_mantissa &a, int32_t shift) { round_nearest_tie_even( a, shift, [ord](bool is_odd, bool _, bool __) -> bool { - (void)_; // not needed, since we've done our comparison - (void)__; // not needed, since we've done our comparison + static_cast(_); // not needed, since we've done our comparison + static_cast(__); // not needed, since we've done our comparison if (ord > 0) { return true; } else if (ord < 0) { diff --git a/third_party/fast_float/fast_float.h b/third_party/fast_float/fast_float.h index eb822f5..1e79ac6 100644 --- a/third_party/fast_float/fast_float.h +++ b/third_party/fast_float/fast_float.h @@ -7,12 +7,12 @@ namespace fast_float { /** * This function parses the character sequence [first,last) for a number. It - * parses floating-point numbers expecting a locale-indepent format equivalent - * to what is used by std::strtod in the default ("C") locale. 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. That is, we provide exact parsing - * according to the IEEE standard. + * parses floating-point numbers expecting a locale-independent format + * equivalent to what is used by std::strtod in the default ("C") locale. 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. That is, we provide exact + * parsing according to the IEEE standard. * * Given a successful parse, the pointer (`ptr`) in the returned value is set to * point right after the parsed number, and the `value` referenced is set to the diff --git a/third_party/fast_float/float_common.h b/third_party/fast_float/float_common.h index 403eea1..60c28c7 100644 --- a/third_party/fast_float/float_common.h +++ b/third_party/fast_float/float_common.h @@ -17,8 +17,8 @@ #include "constexpr_feature_detect.h" #define FASTFLOAT_VERSION_MAJOR 8 -#define FASTFLOAT_VERSION_MINOR 2 -#define FASTFLOAT_VERSION_PATCH 5 +#define FASTFLOAT_VERSION_MINOR 3 +#define FASTFLOAT_VERSION_PATCH 2 #define FASTFLOAT_STRINGIZE_IMPL(x) #x #define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x) @@ -39,6 +39,8 @@ enum class chars_format : uint64_t; namespace detail { constexpr chars_format basic_json_fmt = chars_format(1 << 5); constexpr chars_format basic_fortran_fmt = chars_format(1 << 6); +constexpr chars_format basic_javascript_fmt = chars_format(1 << 9); +constexpr chars_format basic_javascript_sloppy_fmt = chars_format(1 << 10); } // namespace detail enum class chars_format : uint64_t { @@ -54,6 +56,24 @@ enum class chars_format : uint64_t { general = fixed | scientific, allow_leading_plus = 1 << 7, skip_white_space = 1 << 8, + // ECMAScript DecimalLiteral (strict mode): + // https://tc39.es/ecma262/#prod-DecimalLiteral + // Like JSON, the integer part must not have leading zeros ("01" is + // rejected), but unlike JSON the integer part may be empty (".5") and the + // fractional part may be empty ("5.", "5.e3"). A leading minus sign is + // accepted, and a leading plus sign with allow_leading_plus. + javascript = + uint64_t(detail::basic_javascript_fmt) | fixed | scientific | no_infnan, + // ECMAScript DecimalLiteral in sloppy mode, which adds Annex B's + // NonOctalDecimalIntegerLiteral: a leading zero is accepted when one of + // the digits is 8 or 9 ("08.5" is 8.5). A leading zero followed only by + // octal digits is a LegacyOctalIntegerLiteral ("0775"): it is not a + // decimal number, so it is rejected with legacy_octal_integer_part and the + // caller may parse it in base 8. + // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals + javascript_sloppy = uint64_t(detail::basic_javascript_fmt) | + uint64_t(detail::basic_javascript_sloppy_fmt) | fixed | + scientific | no_infnan, }; template struct from_chars_result_t { @@ -197,14 +217,72 @@ using parse_options = parse_options_t; #define fastfloat_really_inline inline __attribute__((always_inline)) #endif +// Opposite of fastfloat_really_inline. Used for a rare, format-specific variant +// of a force-inlined function, so that its body does not land in the frame of +// every caller that will never execute it. +#ifdef FASTFLOAT_VISUAL_STUDIO +#define fastfloat_never_inline __declspec(noinline) +#elif defined(__GNUC__) || defined(__clang__) +#define fastfloat_never_inline __attribute__((noinline)) +#else +#define fastfloat_never_inline +#endif + +// fastfloat_really_inline under clang only, a no-op elsewhere. Clang declines +// to inline the parser into its callers, so the parser instantiation with a +// compile-time format (from_chars_fixed_format) must force the one thin +// forwarder between it and the parser body. GCC inlines the chain on its own, +// and forcing it there changes its inlining order for the worse. +#ifdef __clang__ +#define fastfloat_clang_really_inline fastfloat_really_inline +#else +#define fastfloat_clang_really_inline +#endif + +// fastfloat_unlikely under clang only, a plain condition elsewhere. Clang +// if-converts rare checks into chains of conditional moves that every +// conversion executes; the hint keeps them as branches. GCC already emits +// branches there, and the hint only reorders its blocks, for the worse. +#ifdef __clang__ +#define fastfloat_clang_unlikely(x) fastfloat_unlikely(x) +#else +#define fastfloat_clang_unlikely(x) (x) +#endif + +// Branch-probability hint marking the rare slow-path branches as cold, so the +// optimizer keeps the out-of-line slow-path re-parse off the hot path (and does +// not duplicate the force-inlined hot scanner into the caller, which bloated +// the hot frame and hurt ILP on some targets). Used at the call site as +// if fastfloat_unlikely(cond) { ... } +// (the macro supplies the parentheses). It expands to the standard [[unlikely]] +// attribute when supported, otherwise to __builtin_expect on GCC/Clang, or +// to a no-op elsewhere (e.g. pre-C++20 MSVC, which has no equivalent hint). +#ifdef __has_cpp_attribute +#if __has_cpp_attribute(unlikely) >= 201803L +// g++-9 hits hits this branch, but then fails to compile +// [[unlikely]]. This happens only with g++-9. +#if !defined(__GNUC__) || (__GNUC__ != 9) +#define FASTFLOAT_USE_UNLIKELY_ATTR +#endif +#endif +#endif + +#ifdef FASTFLOAT_USE_UNLIKELY_ATTR +#define fastfloat_unlikely(x) (x) [[unlikely]] +#elif defined(__GNUC__) || defined(__clang__) +#define fastfloat_unlikely(x) (__builtin_expect(!!(x), 0)) +#else +#define fastfloat_unlikely(x) (x) +#endif + #ifndef FASTFLOAT_ASSERT #define FASTFLOAT_ASSERT(x) \ - { ((void)(x)); } + { static_cast(x); } #endif #ifndef FASTFLOAT_DEBUG_ASSERT #define FASTFLOAT_DEBUG_ASSERT(x) \ - { ((void)(x)); } + { static_cast(x); } #endif // rust style `try!()` macro, or `?` operator @@ -268,147 +346,22 @@ struct is_supported_char_type > { }; -template -inline FASTFLOAT_CONSTEXPR14 bool -fastfloat_strncasecmp3(UC const *actual_mixedcase, - UC const *expected_lowercase) { - uint64_t mask{0}; - FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) { mask = 0x2020202020202020; } - else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 2) { - mask = 0x0020002000200020; - } - else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) { - mask = 0x0000002000000020; - } - else { - return false; - } - - uint64_t val1{0}, val2{0}; - if (cpp20_and_in_constexpr()) { - for (size_t i = 0; i < 3; i++) { - if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) { - return false; - } - } - return true; - } else { - FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1 || sizeof(UC) == 2) { - ::memcpy(&val1, actual_mixedcase, 3 * sizeof(UC)); - ::memcpy(&val2, expected_lowercase, 3 * sizeof(UC)); - val1 |= mask; - val2 |= mask; - return val1 == val2; - } - else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) { - ::memcpy(&val1, actual_mixedcase, 2 * sizeof(UC)); - ::memcpy(&val2, expected_lowercase, 2 * sizeof(UC)); - val1 |= mask; - if (val1 != val2) { - return false; - } - return (actual_mixedcase[2] | 32) == (expected_lowercase[2]); - } - else { - return false; - } - } -} - -template -inline FASTFLOAT_CONSTEXPR14 bool -fastfloat_strncasecmp5(UC const *actual_mixedcase, - UC const *expected_lowercase) { - uint64_t mask{0}; - uint64_t val1{0}, val2{0}; - if (cpp20_and_in_constexpr()) { - for (size_t i = 0; i < 5; i++) { - if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) { - return false; - } - } - return true; - } else { - FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) { - mask = 0x2020202020202020; - ::memcpy(&val1, actual_mixedcase, 5 * sizeof(UC)); - ::memcpy(&val2, expected_lowercase, 5 * sizeof(UC)); - val1 |= mask; - val2 |= mask; - return val1 == val2; - } - else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 2) { - mask = 0x0020002000200020; - ::memcpy(&val1, actual_mixedcase, 4 * sizeof(UC)); - ::memcpy(&val2, expected_lowercase, 4 * sizeof(UC)); - val1 |= mask; - if (val1 != val2) { - return false; - } - return (actual_mixedcase[4] | 32) == (expected_lowercase[4]); - } - else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) { - mask = 0x0000002000000020; - ::memcpy(&val1, actual_mixedcase, 2 * sizeof(UC)); - ::memcpy(&val2, expected_lowercase, 2 * sizeof(UC)); - val1 |= mask; - if (val1 != val2) { - return false; - } - ::memcpy(&val1, actual_mixedcase + 2, 2 * sizeof(UC)); - ::memcpy(&val2, expected_lowercase + 2, 2 * sizeof(UC)); - val1 |= mask; - if (val1 != val2) { - return false; - } - return (actual_mixedcase[4] | 32) == (expected_lowercase[4]); - } - else { - return false; - } - } -} - -// Compares two ASCII strings in a case insensitive manner. +// Compares two ASCII strings in a case insensitive manner. The expected +// string is lowercase ASCII, so OR-ing 0x20 into each actual character maps +// 'A'..'Z' onto 'a'..'z' and leaves everything else mismatched. The lengths +// used are tiny constants (3 and 5), so the compiler unrolls this loop; keep +// it small so that the (rarely executed) inf/nan path does not bloat the hot +// parser it is inlined into. template inline FASTFLOAT_CONSTEXPR14 bool fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, size_t length) { - uint64_t mask{0}; - FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 1) { mask = 0x2020202020202020; } - else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 2) { - mask = 0x0020002000200020; - } - else FASTFLOAT_IF_CONSTEXPR17(sizeof(UC) == 4) { - mask = 0x0000002000000020; - } - else { - return false; - } - - if (cpp20_and_in_constexpr()) { - for (size_t i = 0; i < length; i++) { - if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) { - return false; - } - } - return true; - } else { - uint64_t val1{0}, val2{0}; - size_t sz{8 / (sizeof(UC))}; - for (size_t i = 0; i < length; i += sz) { - val1 = val2 = 0; - sz = sz < (length - i) ? sz : length - i; - ::memcpy(&val1, actual_mixedcase + i, sz * sizeof(UC)); - ::memcpy(&val2, expected_lowercase + i, sz * sizeof(UC)); - val1 |= mask; - val2 |= mask; - if (val1 != val2) { - return false; - } + for (size_t i = 0; i < length; ++i) { + if ((actual_mixedcase[i] | 32) != expected_lowercase[i]) { + return false; } - return true; } + return true; } #ifndef FLT_EVAL_METHOD @@ -483,7 +436,7 @@ leading_zeroes(uint64_t input_num) { // Search the mask data from most significant bit (MSB) // to least significant bit (LSB) for a set bit (1). _BitScanReverse64(&leading_zero, input_num); - return (int)(63 - leading_zero); + return static_cast(63 - leading_zero); #else return leading_zeroes_generic(input_num); #endif @@ -530,7 +483,7 @@ countr_zero_32(uint32_t input_num) { #ifdef FASTFLOAT_VISUAL_STUDIO unsigned long trailing_zero = 0; if (_BitScanForward(&trailing_zero, input_num)) { - return (int)trailing_zero; + return static_cast(trailing_zero); } return 32; #else @@ -540,18 +493,21 @@ countr_zero_32(uint32_t input_num) { // slow emulation routine for 32-bit fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) { - return x * (uint64_t)y; + return x * static_cast(y); } fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { - uint64_t ad = emulu((uint32_t)(ab >> 32), (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_carry = (uint64_t)(adbc < ad); + uint64_t ad = + emulu(static_cast(ab >> 32), static_cast(cd)); + uint64_t bd = emulu(static_cast(ab), static_cast(cd)); + uint64_t adbc = + ad + emulu(static_cast(ab), static_cast(cd >> 32)); + uint64_t adbc_carry = static_cast(adbc < ad); uint64_t lo = bd + (adbc << 32); - *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + - (adbc_carry << 32) + (uint64_t)(lo < bd); + *hi = + emulu(static_cast(ab >> 32), static_cast(cd >> 32)) + + (adbc >> 32) + (adbc_carry << 32) + static_cast(lo < bd); return lo; } @@ -586,7 +542,7 @@ full_multiplication(uint64_t a, uint64_t b) { !defined(_M_ARM64) && !defined(__GNUC__)) answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 #elif defined(FASTFLOAT_64BIT) && defined(__SIZEOF_INT128__) - __uint128_t r = ((__uint128_t)a) * b; + __uint128_t r = static_cast<__uint128_t>(a) * b; answer.low = uint64_t(r); answer.high = uint64_t(r >> 64); #else @@ -632,6 +588,8 @@ template struct binary_format : binary_format_lookup_tables { static constexpr uint64_t max_mantissa_fast_path(int64_t power); static constexpr uint64_t max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST + static constexpr bool fast_path_can_overflow(); + static constexpr bool subnormal_ties_possible(); static constexpr int largest_power_of_ten(); static constexpr int smallest_power_of_ten(); static constexpr T exact_power_of_ten(int64_t power); @@ -663,6 +621,7 @@ template struct binary_format_lookup_tables { 0x20000000000000 / (constant_55555 * constant_55555 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5), 0x20000000000000 / @@ -678,9 +637,7 @@ template struct binary_format_lookup_tables { 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * - constant_55555 * 5 * 5 * 5), - 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * - constant_55555 * 5 * 5 * 5 * 5)}; + constant_55555 * 5 * 5 * 5)}; }; #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE @@ -848,7 +805,7 @@ template <> inline constexpr std::float16_t binary_format::exact_power_of_ten(int64_t power) { // Work around clang bug https://godbolt.org/z/zedh7rrhc - return (void)powers_of_ten[0], powers_of_ten[power]; + return static_cast(powers_of_ten[0]), powers_of_ten[power]; } template <> @@ -892,12 +849,14 @@ binary_format::max_mantissa_fast_path(int64_t power) { // power >= 0 && power <= 4 // // Work around clang bug https://godbolt.org/z/zedh7rrhc - return (void)max_mantissa[0], max_mantissa[power]; + return static_cast(max_mantissa[0]), max_mantissa[power]; } template <> inline constexpr int binary_format::min_exponent_fast_path() { - return 0; + // w / 10^k with w <= 2^11 and k <= 4 rounds correctly even when evaluated + // in float or double first (checked in script/format_parameters.py). + return -4; } template <> @@ -909,7 +868,9 @@ binary_format::max_exponent_round_to_even() { template <> inline constexpr int binary_format::min_exponent_round_to_even() { - return -22; + // -22 covers the normal ties; subnormal ties such as + // 2^-25 = 298023223876953125e-25 need q = -25 and q = -26. + return -26; } template <> @@ -933,7 +894,8 @@ inline constexpr int binary_format::largest_power_of_ten() { template <> inline constexpr int binary_format::smallest_power_of_ten() { - return -27; + // (10^19 - 1) * 10^-27 < 2^-25, so any q < -26 rounds to zero. + return -26; } template <> @@ -971,7 +933,7 @@ template <> inline constexpr std::bfloat16_t binary_format::exact_power_of_ten(int64_t power) { // Work around clang bug https://godbolt.org/z/zedh7rrhc - return (void)powers_of_ten[0], powers_of_ten[power]; + return static_cast(powers_of_ten[0]), powers_of_ten[power]; } template <> @@ -1015,12 +977,13 @@ binary_format::max_mantissa_fast_path(int64_t power) { // power >= 0 && power <= 3 // // Work around clang bug https://godbolt.org/z/zedh7rrhc - return (void)max_mantissa[0], max_mantissa[power]; + return static_cast(max_mantissa[0]), max_mantissa[power]; } template <> inline constexpr int binary_format::min_exponent_fast_path() { - return 0; + // Same argument as for std::float16_t (w <= 2^8, k <= 3). + return -3; } template <> @@ -1056,7 +1019,8 @@ inline constexpr int binary_format::largest_power_of_ten() { template <> inline constexpr int binary_format::smallest_power_of_ten() { - return -60; + // (10^19 - 1) * 10^-60 < 2^-134, so any q < -59 rounds to zero. + return -59; } template <> @@ -1065,6 +1029,25 @@ inline constexpr size_t binary_format::max_digits() { } #endif // __STDCPP_BFLOAT16_T__ +// Whether Clinger's fast path can overflow: only for std::float16_t, where +// 2^11 * 10^4 > 65504. +template +inline constexpr bool binary_format::fast_path_can_overflow() { + return double(max_mantissa_fast_path()) * + double(exact_power_of_ten(max_exponent_fast_path())) > + double((std::numeric_limits::max)()); +} + +// A subnormal needs w * 10^q < 2^(minimum_exponent() + 1), so q is at most +// (minimum_exponent() + 1) * log10(2), with 1233/4096 < log10(2). Only +// std::float16_t has such q in its round-to-even range. We compare +// 4096 * q with (minimum_exponent() + 1) * 1233 to avoid right-shifting a +// negative value (implementation-defined before C++20). +template +inline constexpr bool binary_format::subnormal_ties_possible() { + return min_exponent_round_to_even() * 4096 <= (minimum_exponent() + 1) * 1233; +} + template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { @@ -1072,7 +1055,7 @@ binary_format::max_mantissa_fast_path(int64_t power) { // power >= 0 && power <= 22 // // Work around clang bug https://godbolt.org/z/zedh7rrhc - return (void)max_mantissa[0], max_mantissa[power]; + return static_cast(max_mantissa[0]), max_mantissa[power]; } template <> @@ -1082,20 +1065,20 @@ binary_format::max_mantissa_fast_path(int64_t power) { // power >= 0 && power <= 10 // // Work around clang bug https://godbolt.org/z/zedh7rrhc - return (void)max_mantissa[0], max_mantissa[power]; + return static_cast(max_mantissa[0]), max_mantissa[power]; } template <> inline constexpr double binary_format::exact_power_of_ten(int64_t power) { // Work around clang bug https://godbolt.org/z/zedh7rrhc - return (void)powers_of_ten[0], powers_of_ten[power]; + return static_cast(powers_of_ten[0]), powers_of_ten[power]; } template <> inline constexpr float binary_format::exact_power_of_ten(int64_t power) { // Work around clang bug https://godbolt.org/z/zedh7rrhc - return (void)powers_of_ten[0], powers_of_ten[power]; + return static_cast(powers_of_ten[0]), powers_of_ten[power]; } template <> inline constexpr int binary_format::largest_power_of_ten() { @@ -1197,7 +1180,11 @@ template constexpr bool space_lut::value[]; #endif template constexpr bool is_space(UC c) { - return c < 256 && space_lut<>::value[uint8_t(c)]; + // wchar_t and char can be signed, so a negative code unit slips past a plain + // `c < 256` and then indexes the table by its truncated low byte. Compare as + // unsigned, matching the care taken in ch_to_digit. + using UnsignedUC = typename std::make_unsigned::type; + return static_cast(c) < 256 && space_lut<>::value[uint8_t(c)]; } template static constexpr uint64_t int_cmp_zeros() { diff --git a/third_party/fast_float/parse_number.h b/third_party/fast_float/parse_number.h index ff9c53d..f8409d0 100644 --- a/third_party/fast_float/parse_number.h +++ b/third_party/fast_float/parse_number.h @@ -35,7 +35,7 @@ from_chars_result_t ++first; } if (last - first >= 3) { - if (fastfloat_strncasecmp3(first, str_const_nan())) { + if (fastfloat_strncasecmp(first, str_const_nan(), 3)) { answer.ptr = (first += 3); value = minusSign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); @@ -54,9 +54,9 @@ from_chars_result_t } return answer; } - if (fastfloat_strncasecmp3(first, str_const_inf())) { + if (fastfloat_strncasecmp(first, str_const_inf(), 3)) { if ((last - first >= 8) && - fastfloat_strncasecmp5(first + 3, str_const_inf() + 3)) { + fastfloat_strncasecmp(first + 3, str_const_inf() + 3, 5)) { answer.ptr = first + 8; } else { answer.ptr = first + 3; @@ -96,7 +96,7 @@ fastfloat_really_inline bool rounds_to_nearest() noexcept { // asm). The value does not need to be std::numeric_limits::min(), any // small value so that 1 + x should round to 1 would do (after accounting for // excess precision, as in 387 instructions). - static float volatile fmin = std::numeric_limits::min(); + static float volatile fmin = (std::numeric_limits::min)(); float fmini = fmin; // we copy it so that it gets loaded at most once. // // Explanation: @@ -139,9 +139,10 @@ fastfloat_really_inline bool rounds_to_nearest() noexcept { template struct from_chars_caller { template - FASTFLOAT_CONSTEXPR20 static from_chars_result_t - call(UC const *first, UC const *last, T &value, - parse_options_t options) noexcept { + fastfloat_clang_really_inline + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { return from_chars_advanced(first, last, value, options); } }; @@ -180,10 +181,29 @@ template <> struct from_chars_caller { }; #endif +#ifdef __clang__ +// Parser instantiated for a format fixed at compile time, so that every test +// on the format folds away inside it. GCC gets the same effect by inlining the +// whole parser into each caller, where the format is a constant; clang keeps +// it out of line and would otherwise re-test each format flag per conversion. +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_fixed_format(UC const *first, UC const *last, T &value) noexcept { + return from_chars_caller::call(first, last, value, + parse_options_t(Fmt)); +} +#endif + template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars(UC const *first, UC const *last, T &value, chars_format fmt /*= chars_format::general*/) noexcept { +#ifdef __clang__ + if (fmt == chars_format::general) { + return from_chars_fixed_format(first, last, + value); + } +#endif return from_chars_caller::call(first, last, value, parse_options_t(fmt)); } @@ -198,7 +218,14 @@ clinger_fast_path_impl(uint64_t mantissa, int64_t exponent, bool is_negative, // We proceed optimistically, assuming that detail::rounds_to_nearest() // returns true. if (binary_format::min_exponent_fast_path() <= exponent && - exponent <= binary_format::max_exponent_fast_path()) { + exponent <= binary_format::max_exponent_fast_path() && + mantissa <= binary_format::max_mantissa_fast_path()) { + // The mantissa bound above is a necessary condition for BOTH branches + // below: the rounding-mode-dependent branch checks the tighter + // max_mantissa_fast_path(exponent) <= max_mantissa_fast_path(). Testing + // it before detail::rounds_to_nearest() spares long-mantissa inputs + // (which can never take the fast path) the volatile-float probe. + // // Unfortunately, the conventional Clinger's fast path is only possible // when the system rounds to the nearest float. // @@ -209,18 +236,22 @@ clinger_fast_path_impl(uint64_t mantissa, int64_t exponent, bool is_negative, if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { // We have that fegetround() == FE_TONEAREST. // Next is Clinger's fast path. - if (mantissa <= binary_format::max_mantissa_fast_path()) { - value = T(mantissa); - if (exponent < 0) { - value = value / binary_format::exact_power_of_ten(-exponent); - } else { - value = value * binary_format::exact_power_of_ten(exponent); - } - if (is_negative) { - value = -value; + value = T(mantissa); + if (exponent < 0) { + value = value / binary_format::exact_power_of_ten(-exponent); + } else { + value = value * binary_format::exact_power_of_ten(exponent); + // Only std::float16_t can overflow here (e.g., "656e2"); let the + // slow path report result_out_of_range. + if (binary_format::fast_path_can_overflow() && + value > (std::numeric_limits::max)()) { + return false; } - return true; } + if (is_negative) { + value = -value; + } + return true; } else { // We do not have that fegetround() == FE_TONEAREST. // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's @@ -289,6 +320,56 @@ from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { return answer; } +// Slow path: re-parse materializing the integer/fraction spans the hot no-span +// parse skipped, then run the full algorithm. The two callers reach it only +// through a fastfloat_unlikely branch, so the optimizer keeps this re-parse off +// the hot path on its own (no function-level noinline needed). +// from_chars_advanced already handles both the too_many_digits disambiguation +// and the am.power2<0 digit_comp recompute, so both slow branches collapse to +// one helper call. +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +parse_number_slow_path(UC const *first, UC const *last, T &value, + parse_options_t options, bool bjf) noexcept { + parsed_number_string_t pns = + bjf ? parse_number_string_impl(first, last, options, + true) + : parse_number_string_impl(first, last, options, + true); + return from_chars_advanced(pns, value); +} + +// Cold: the whole chars_format::javascript conversion. Kept out of line and out +// of from_chars_float_advanced so the common frame holds neither the javascript +// parser body nor a call that would force the parsed number string onto the +// stack. Mirrors the main path, only with the javascript parser. +template +fastfloat_never_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_float_javascript(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + chars_format const fmt = detail::adjust_for_feature_macros(options.format); + if (uint64_t(fmt & chars_format::skip_white_space)) { + while ((first != last) && fast_float::is_space(*first)) { + first++; + } + } + from_chars_result_t answer; + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + if (first == last) { + return answer; + } + parsed_number_string_t pns = + parse_number_string_javascript(first, last, options, true); + if (!pns.valid) { + if (uint64_t(fmt & chars_format::no_infnan)) { + return answer; + } + return detail::parse_infnan(first, last, value, fmt); + } + return from_chars_advanced(pns, value); +} + template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_float_advanced(UC const *first, UC const *last, T &value, @@ -301,6 +382,13 @@ from_chars_float_advanced(UC const *first, UC const *last, T &value, chars_format const fmt = detail::adjust_for_feature_macros(options.format); + // Leave for the javascript conversion before anything else is live, so this + // compiles to a tail call and the common path keeps its registers. The cold + // function repeats the leading-whitespace and empty-input handling below. + if fastfloat_unlikely (uint64_t(fmt & detail::basic_javascript_fmt)) { + return from_chars_float_javascript(first, last, value, options); + } + from_chars_result_t answer; if (uint64_t(fmt & chars_format::skip_white_space)) { while ((first != last) && fast_float::is_space(*first)) { @@ -312,10 +400,17 @@ from_chars_float_advanced(UC const *first, UC const *last, T &value, answer.ptr = first; return answer; } + bool const bjf = uint64_t(fmt & detail::basic_json_fmt) != 0; + + // Fast path: parse WITHOUT materializing the integer/fraction spans (read + // only by the rare slow paths). Skipping their stores keeps the fat + // parsed_number_string_t off the hot path. store_spans is a runtime argument, + // so this reuses the single parse_number_string_impl instantiation. parsed_number_string_t pns = - uint64_t(fmt & detail::basic_json_fmt) - ? parse_number_string(first, last, options) - : parse_number_string(first, last, options); + bjf ? parse_number_string_impl(first, last, options, + false) + : parse_number_string_impl(first, last, options, + false); if (!pns.valid) { if (uint64_t(fmt & chars_format::no_infnan)) { answer.ec = std::errc::invalid_argument; @@ -326,8 +421,53 @@ from_chars_float_advanced(UC const *first, UC const *last, T &value, } } - // call overload that takes parsed_number_string_t directly. - return from_chars_advanced(pns, value); + // Slow path A (rare): > 19 significant digits. The no-span parse left the + // mantissa un-truncated and skipped the span-based recompute; the cold helper + // re-parses with spans and runs the full algorithm. + // +// We have to disable -Wc++20-extensions for the [[unlikely]] attribute +// See comment for @jwakely at +// https://github.com/fastfloat/fast_float/pull/387#discussion_r3366943539 +// This is unfortunate. +#ifdef __clang__ +#pragma clang diagnostic push +#if (!defined(__APPLE_CC__) && __clang_major__ >= 10) || (__clang_major__ >= 13) +#pragma clang diagnostic ignored "-Wc++20-extensions" +#endif +#endif + if fastfloat_unlikely (pns.too_many_digits) { + return parse_number_slow_path(first, last, value, options, bjf); + } + answer.ec = std::errc(); // be optimistic + answer.ptr = pns.lastmatch; + + if (clinger_fast_path_impl(pns.mantissa, pns.exponent, pns.negative, value)) { + return answer; + } + + adjusted_mantissa am = + compute_float>(pns.exponent, pns.mantissa); + // Slow path B (rare): Eisel-Lemire could not resolve; digit_comp needs the + // integer/fraction spans. Route to the cold helper (clinger there is a + // dead-effect since it already failed here; the cold re-parse + digit_comp + // via from_chars_advanced reproduces this branch). + if fastfloat_unlikely (am.power2 < 0) { + return parse_number_slow_path(first, last, value, options, bjf); + } + to_float(pns.negative, am, value); + // Test for over/underflow. Marked unlikely so that clang keeps it as a + // branch instead of folding it into a chain of conditional moves that + // every conversion pays for. + if fastfloat_clang_unlikely ((pns.mantissa != 0 && am.mantissa == 0 && + am.power2 == 0) || + am.power2 == + binary_format::infinite_power()) { + answer.ec = std::errc::result_out_of_range; + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return answer; } template @@ -363,7 +503,9 @@ FASTFLOAT_CONSTEXPR20 typename std::enable_if::value, T>::type integer_times_pow10(int64_t mantissa, int decimal_exponent) noexcept { const bool is_negative = mantissa < 0; - const uint64_t m = static_cast(is_negative ? -mantissa : mantissa); + // Negate as unsigned: -mantissa overflows for the most negative int64_t. + const uint64_t m = is_negative ? uint64_t(0) - static_cast(mantissa) + : static_cast(mantissa); T value; if (clinger_fast_path_impl(m, decimal_exponent, is_negative, value)) diff --git a/third_party/fmt/LICENSE b/third_party/fmt/LICENSE index 1cd1ef9..795fdae 100644 --- a/third_party/fmt/LICENSE +++ b/third_party/fmt/LICENSE @@ -18,10 +18,3 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- Optional exception to the license --- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into a machine-executable object form of such -source code, you may redistribute such embedded portions in such object form -without including the above copyright and permission notices. diff --git a/third_party/fmt/README.vendor b/third_party/fmt/README.vendor index 6cc06c4..fad5b32 100644 --- a/third_party/fmt/README.vendor +++ b/third_party/fmt/README.vendor @@ -2,8 +2,8 @@ ================================== Upstream: https://github.com/fmtlib/fmt -Tag: 12.1.0 -Commit: 407c905 (2025-10-29) +Tag: 12.2.0 +Commit: 1be298e1 (2026-06-16) License: MIT (see LICENSE in this directory) Files vendored diff --git a/third_party/fmt/base.h b/third_party/fmt/base.h index 0d57867..5d0241c 100644 --- a/third_party/fmt/base.h +++ b/third_party/fmt/base.h @@ -1,6 +1,6 @@ // Formatting library for C++ - the base API for char/UTF-8 // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. @@ -21,7 +21,7 @@ #endif // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 120100 +#define FMT_VERSION 120200 // Detect compiler versions. #if defined(__clang__) && !defined(__ibmxl__) @@ -126,8 +126,8 @@ # define FMT_USE_CONSTEVAL 0 #elif defined(__apple_build_version__) && __apple_build_version__ < 14000029L # define FMT_USE_CONSTEVAL 0 // consteval is broken in Apple clang < 14. -#elif FMT_MSC_VERSION && FMT_MSC_VERSION < 1929 -# define FMT_USE_CONSTEVAL 0 // consteval is broken in MSVC VS2019 < 16.10. +#elif FMT_MSC_VERSION && FMT_MSC_VERSION < 1940 +# define FMT_USE_CONSTEVAL 0 // consteval is broken in some MSVC2022 versions. #elif defined(__cpp_consteval) # define FMT_USE_CONSTEVAL 1 #elif FMT_GCC_VERSION >= 1002 || FMT_CLANG_VERSION >= 1101 @@ -223,20 +223,25 @@ #else # define FMT_PRAGMA_CLANG(x) #endif -#if FMT_MSC_VERSION -# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) -#else -# define FMT_MSC_WARNING(...) + +#ifndef FMT_USE_OPTIMIZE_PRAGMA +# define FMT_USE_OPTIMIZE_PRAGMA 1 #endif // Enable minimal optimizations for more compact code in debug mode. FMT_PRAGMA_GCC(push_options) -#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE) +#if FMT_USE_OPTIMIZE_PRAGMA && !defined(__OPTIMIZE__) && \ + !defined(__CUDACC__) && !defined(FMT_MODULE) FMT_PRAGMA_GCC(optimize("Og")) -# define FMT_GCC_OPTIMIZED #endif -FMT_PRAGMA_CLANG(diagnostic push) -FMT_PRAGMA_GCC(diagnostic push) + +#ifdef FMT_DEPRECATED +// Use the provided definition. +#elif FMT_HAS_CPP14_ATTRIBUTE(deprecated) +# define FMT_DEPRECATED [[deprecated]] +#else +# define FMT_DEPRECATED /* deprecated */ +#endif #ifdef FMT_ALWAYS_INLINE // Use the provided definition. @@ -246,7 +251,7 @@ FMT_PRAGMA_GCC(diagnostic push) # define FMT_ALWAYS_INLINE inline #endif // A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode. -#if defined(NDEBUG) || defined(FMT_GCC_OPTIMIZED) +#ifdef NDEBUG # define FMT_INLINE FMT_ALWAYS_INLINE #else # define FMT_INLINE inline @@ -321,6 +326,8 @@ using underlying_t = typename std::underlying_type::type; template using decay_t = typename std::decay::type; using nullptr_t = decltype(nullptr); +using ullong = unsigned long long; + #if (FMT_GCC_VERSION && FMT_GCC_VERSION < 500) || FMT_MSC_VERSION // A workaround for gcc 4.9 & MSVC v141 to make void_t work in a SFINAE context. template struct void_t_impl { @@ -376,15 +383,7 @@ constexpr auto is_constant_evaluated(bool default_value = false) noexcept #endif } -// Suppresses "conditional expression is constant" warnings. -template FMT_ALWAYS_INLINE constexpr auto const_check(T val) -> T { - return val; -} - -FMT_NORETURN FMT_API void assert_fail(const char* file, int line, - const char* message); - -#if defined(FMT_ASSERT) +#ifdef FMT_ASSERT // Use the provided definition. #elif defined(NDEBUG) // FMT_ASSERT is not empty to avoid -Wempty-body. @@ -402,38 +401,21 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line, #elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \ !(FMT_CLANG_VERSION && FMT_MSC_VERSION) # define FMT_USE_INT128 1 -using int128_opt = __int128_t; // An optional native 128-bit integer. -using uint128_opt = __uint128_t; -inline auto map(int128_opt x) -> int128_opt { return x; } -inline auto map(uint128_opt x) -> uint128_opt { return x; } +using native_int128 = __int128_t; +using native_uint128 = __uint128_t; +inline auto map(native_int128 x) -> native_int128 { return x; } +inline auto map(native_uint128 x) -> native_uint128 { return x; } #else # define FMT_USE_INT128 0 #endif #if !FMT_USE_INT128 -enum class int128_opt {}; -enum class uint128_opt {}; -// Reduce template instantiations. -inline auto map(int128_opt) -> monostate { return {}; } -inline auto map(uint128_opt) -> monostate { return {}; } -#endif - -#ifdef FMT_USE_BITINT -// Use the provided definition. -#elif FMT_CLANG_VERSION >= 1500 && !defined(__CUDACC__) -# define FMT_USE_BITINT 1 -#else -# define FMT_USE_BITINT 0 +// Fallbacks to reduce conditional compilation and SFINAE. +enum class native_int128 {}; +enum class native_uint128 {}; +inline auto map(native_int128) -> monostate { return {}; } +inline auto map(native_uint128) -> monostate { return {}; } #endif -#if FMT_USE_BITINT -FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension") -template using bitint = _BitInt(N); -template using ubitint = unsigned _BitInt(N); -#else -template struct bitint {}; -template struct ubitint {}; -#endif // FMT_USE_BITINT - // Casts a nonnegative integer to unsigned. template FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t { @@ -499,22 +481,30 @@ struct is_back_insert_iterator< // Extracts a reference to the container from *insert_iterator. template -inline FMT_CONSTEXPR20 auto get_container(OutputIt it) -> +inline FMT_CONSTEXPR auto get_container(OutputIt it) -> typename OutputIt::container_type& { struct accessor : OutputIt { - FMT_CONSTEXPR20 accessor(OutputIt base) : OutputIt(base) {} + constexpr accessor(OutputIt base) : OutputIt(base) {} using OutputIt::container; }; return *accessor(it).container; } + +template +struct is_contiguous : std::false_type {}; +template +struct is_contiguous().data()), + decltype(std::declval().size()), + decltype(std::declval()[size_t()])>> + : std::true_type {}; } // namespace detail // Parsing-related public API and forward declarations. FMT_BEGIN_EXPORT /** - * An implementation of `std::basic_string_view` for pre-C++17. It provides a - * subset of the API. `fmt::basic_string_view` is used for format strings even + * An implementation of `std::basic_string_view` for pre-C++17 providing a + * subset of the API. `fmt::basic_string_view` is used in the public API even * if `std::basic_string_view` is available to prevent issues when a library is * compiled with a different `-std` option than the client code (which is not * recommended). @@ -529,18 +519,13 @@ template class basic_string_view { using iterator = const Char*; constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} - - /// Constructs a string view object from a C string and a size. constexpr basic_string_view(const Char* s, size_t count) noexcept : data_(s), size_(count) {} - constexpr basic_string_view(nullptr_t) = delete; - - /// Constructs a string view object from a C string. #if FMT_GCC_VERSION FMT_ALWAYS_INLINE #endif - FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) { + FMT_CONSTEXPR basic_string_view(const Char* s) : data_(s) { #if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION if (std::is_same::value && !detail::is_constant_evaluated()) { size_ = __builtin_strlen(detail::narrow(s)); // strlen is not constexpr. @@ -552,18 +537,14 @@ template class basic_string_view { size_ = len; } - /// Constructs a string view from a `std::basic_string` or a - /// `std::basic_string_view` object. - template ::value&& std::is_same< - typename S::value_type, Char>::value)> - FMT_CONSTEXPR basic_string_view(const S& s) noexcept + template < + typename S, + FMT_ENABLE_IF(detail::is_std_string_like::value&& // + std::is_same::value)> + constexpr basic_string_view(const S& s) noexcept : data_(s.data()), size_(s.size()) {} - /// Returns a pointer to the string data. constexpr auto data() const noexcept -> const Char* { return data_; } - - /// Returns the string size. constexpr auto size() const noexcept -> size_t { return size_; } constexpr auto begin() const noexcept -> iterator { return data_; } @@ -578,21 +559,19 @@ template class basic_string_view { size_ -= n; } - FMT_CONSTEXPR auto starts_with(basic_string_view sv) const noexcept - -> bool { + FMT_CONSTEXPR auto starts_with(basic_string_view sv) const noexcept -> bool { return size_ >= sv.size_ && detail::compare(data_, sv.data_, sv.size_) == 0; } FMT_CONSTEXPR auto starts_with(Char c) const noexcept -> bool { return size_ >= 1 && *data_ == c; } FMT_CONSTEXPR auto starts_with(const Char* s) const -> bool { - return starts_with(basic_string_view(s)); + return starts_with(basic_string_view(s)); } FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { - int result = - detail::compare(data_, other.data_, min_of(size_, other.size_)); - if (result != 0) return result; + int cmp = detail::compare(data_, other.data_, min_of(size_, other.size_)); + if (cmp != 0) return cmp; return size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); } @@ -616,15 +595,11 @@ template class basic_string_view { return lhs.compare(rhs) >= 0; } }; - using string_view = basic_string_view; template class basic_appender; using appender = basic_appender; -// Checks whether T is a container with contiguous storage. -template struct is_contiguous : std::false_type {}; - class context; template class generic_context; template class parse_context; @@ -643,6 +618,8 @@ using buffered_context = conditional_t::value, context, generic_context, Char>>; +template struct is_contiguous : detail::is_contiguous {}; + template class basic_format_arg; template class basic_format_args; @@ -657,6 +634,9 @@ struct formatter { formatter() = delete; }; +template +struct locking : std::false_type {}; + /// Reports a format error at compile time or, via a `format_error` exception, /// at runtime. // This function is intentionally not constexpr to give a compile-time error. @@ -736,8 +716,7 @@ class basic_specs { char fill_data_[max_fill_size] = {' '}; FMT_CONSTEXPR void set_fill_size(size_t size) { - data_ = (data_ & ~fill_size_mask) | - (static_cast(size) << fill_size_shift); + data_ = (data_ & ~fill_size_mask) | (unsigned(size) << fill_size_shift); } public: @@ -745,21 +724,21 @@ class basic_specs { return static_cast(data_ & type_mask); } FMT_CONSTEXPR void set_type(presentation_type t) { - data_ = (data_ & ~type_mask) | static_cast(t); + data_ = (data_ & ~type_mask) | unsigned(t); } constexpr auto align() const -> align { return static_cast((data_ & align_mask) >> align_shift); } FMT_CONSTEXPR void set_align(fmt::align a) { - data_ = (data_ & ~align_mask) | (static_cast(a) << align_shift); + data_ = (data_ & ~align_mask) | (unsigned(a) << align_shift); } constexpr auto dynamic_width() const -> arg_id_kind { return static_cast((data_ & width_mask) >> width_shift); } FMT_CONSTEXPR void set_dynamic_width(arg_id_kind w) { - data_ = (data_ & ~width_mask) | (static_cast(w) << width_shift); + data_ = (data_ & ~width_mask) | (unsigned(w) << width_shift); } FMT_CONSTEXPR auto dynamic_precision() const -> arg_id_kind { @@ -767,8 +746,7 @@ class basic_specs { precision_shift); } FMT_CONSTEXPR void set_dynamic_precision(arg_id_kind p) { - data_ = (data_ & ~precision_mask) | - (static_cast(p) << precision_shift); + data_ = (data_ & ~precision_mask) | (unsigned(p) << precision_shift); } constexpr auto dynamic() const -> bool { @@ -779,7 +757,7 @@ class basic_specs { return static_cast((data_ & sign_mask) >> sign_shift); } FMT_CONSTEXPR void set_sign(fmt::sign s) { - data_ = (data_ & ~sign_mask) | (static_cast(s) << sign_shift); + data_ = (data_ & ~sign_mask) | (unsigned(s) << sign_shift); } constexpr auto upper() const -> bool { return (data_ & uppercase_mask) != 0; } @@ -809,9 +787,8 @@ class basic_specs { template constexpr auto fill_unit() const -> Char { using uchar = unsigned char; - return static_cast(static_cast(fill_data_[0]) | - (static_cast(fill_data_[1]) << 8) | - (static_cast(fill_data_[2]) << 16)); + return Char(uchar(fill_data_[0]) | uchar(fill_data_[1]) << 8 | + uchar(fill_data_[2]) << 16); } FMT_CONSTEXPR void set_fill(char c) { @@ -825,14 +802,13 @@ class basic_specs { set_fill_size(size); if (size == 1) { unsigned uchar = static_cast>(s[0]); - fill_data_[0] = static_cast(uchar); - fill_data_[1] = static_cast(uchar >> 8); - fill_data_[2] = static_cast(uchar >> 16); + fill_data_[0] = char(uchar); + fill_data_[1] = char(uchar >> 8); + fill_data_[2] = char(uchar >> 16); return; } FMT_ASSERT(size <= max_fill_size, "invalid fill"); - for (size_t i = 0; i < size; ++i) - fill_data_[i & 3] = static_cast(s[i]); + for (size_t i = 0; i < size; ++i) fill_data_[i & 3] = char(s[i]); } FMT_CONSTEXPR void copy_fill_from(const basic_specs& specs) { @@ -927,10 +903,13 @@ class locale_ref { template locale_ref(const Locale& loc) : locale_(&loc) { // Check if std::isalpha is found via ADL to reduce the chance of misuse. - isalpha('x', loc); + detail::ignore_unused(sizeof(isalpha('x', loc))); } inline explicit operator bool() const noexcept { return locale_ != nullptr; } +#else + public: + inline explicit operator bool() const noexcept { return false; } #endif // FMT_USE_LOCALE public: @@ -1017,9 +996,9 @@ struct type_constant : std::integral_constant {}; FMT_TYPE_CONSTANT(int, int_type); FMT_TYPE_CONSTANT(unsigned, uint_type); FMT_TYPE_CONSTANT(long long, long_long_type); -FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); -FMT_TYPE_CONSTANT(int128_opt, int128_type); -FMT_TYPE_CONSTANT(uint128_opt, uint128_type); +FMT_TYPE_CONSTANT(ullong, ulong_long_type); +FMT_TYPE_CONSTANT(native_int128, int128_type); +FMT_TYPE_CONSTANT(native_uint128, uint128_type); FMT_TYPE_CONSTANT(bool, bool_type); FMT_TYPE_CONSTANT(Char, char_type); FMT_TYPE_CONSTANT(float, float_type); @@ -1036,9 +1015,9 @@ constexpr auto is_arithmetic_type(type t) -> bool { return t > type::none_type && t <= type::last_numeric_type; } -constexpr auto set(type rhs) -> int { return 1 << static_cast(rhs); } +constexpr auto set(type rhs) -> int { return 1 << int(rhs); } constexpr auto in(type t, int set) -> bool { - return ((set >> static_cast(t)) & 1) != 0; + return ((set >> int(t)) & 1) != 0; } // Bitsets of types. @@ -1063,14 +1042,15 @@ struct is_view : std::false_type {}; template struct is_view> : std::is_base_of {}; -template struct named_arg; +// DEPRECATED! named_arg will be moved to the fmt namespace. +template struct named_arg; template struct is_named_arg : std::false_type {}; template struct is_static_named_arg : std::false_type {}; -template -struct is_named_arg> : std::true_type {}; +template +struct is_named_arg> : std::true_type {}; -template struct named_arg : view { +template struct named_arg : view { const Char* name; const T& value; @@ -1134,7 +1114,7 @@ FMT_CONSTEXPR void init_static_named_arg(named_arg_info* named_args, // either to int or to long long depending on its size. enum { long_short = sizeof(long) == sizeof(int) && FMT_BUILTIN_TYPES }; using long_type = conditional_t; -using ulong_type = conditional_t; +using ulong_type = conditional_t; template using format_as_result = @@ -1188,17 +1168,11 @@ template struct type_mapper { static auto map(long) -> long_type; static auto map(unsigned long) -> ulong_type; static auto map(long long) -> long long; - static auto map(unsigned long long) -> unsigned long long; - static auto map(int128_opt) -> int128_opt; - static auto map(uint128_opt) -> uint128_opt; + static auto map(ullong) -> ullong; + static auto map(native_int128) -> native_int128; + static auto map(native_uint128) -> native_uint128; static auto map(bool) -> bool; - template - static auto map(bitint) -> conditional_t; - template - static auto map(ubitint) - -> conditional_t; - template ::value)> static auto map(T) -> conditional_t< std::is_same::value || std::is_same::value, Char, void>; @@ -1258,9 +1232,9 @@ class compile_parse_context : public parse_context { using base = parse_context; public: - FMT_CONSTEXPR explicit compile_parse_context(basic_string_view fmt, - int num_args, const type* types, - int next_arg_id = 0) + constexpr explicit compile_parse_context(basic_string_view fmt, + int num_args, const type* types, + int next_arg_id = 0) : base(fmt, next_arg_id), num_args_(num_args), types_(types) {} constexpr auto num_args() const -> int { return num_args_; } @@ -1279,7 +1253,6 @@ class compile_parse_context : public parse_context { using base::check_arg_id; FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { - ignore_unused(arg_id); if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) report_error("width/precision is not integer"); } @@ -1305,13 +1278,13 @@ template struct dynamic_format_specs : format_specs { // Converts a character to ASCII. Returns '\0' on conversion failure. template ::value)> constexpr auto to_ascii(Char c) -> char { - return c <= 0xff ? static_cast(c) : '\0'; + return c <= 0xff ? char(c) : '\0'; } // Returns the number of code units in a code point or 1 on error. template FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { - if (const_check(sizeof(Char) != 1)) return 1; + if FMT_CONSTEXPR20 (sizeof(Char) != 1) return 1; auto c = static_cast(*begin); return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 3) + 1; } @@ -1331,13 +1304,13 @@ FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, } while (p != end && '0' <= *p && *p <= '9'); auto num_digits = p - begin; begin = p; - int digits10 = static_cast(sizeof(int) * CHAR_BIT * 3 / 10); - if (num_digits <= digits10) return static_cast(value); + int digits10 = int(sizeof(int) * CHAR_BIT * 3 / 10); + if (num_digits <= digits10) return int(value); // Check for overflow. unsigned max = INT_MAX; return num_digits == digits10 + 1 && prev * 10ull + unsigned(p[-1] - '0') <= max - ? static_cast(value) + ? int(value) : error_value; } @@ -1781,9 +1754,10 @@ template class buffer { protected: // Don't initialize ptr_ since it is not accessed to save a few cycles. - FMT_MSC_WARNING(suppress : 26495) FMT_CONSTEXPR buffer(grow_fun grow, size_t sz) noexcept - : size_(sz), capacity_(sz), grow_(grow) {} + : size_(sz), capacity_(sz), grow_(grow) { + if (FMT_MSC_VERSION != 0) ptr_ = nullptr; // Suppress warning 26495. + } constexpr buffer(grow_fun grow, T* p = nullptr, size_t sz = 0, size_t cap = 0) noexcept @@ -1846,13 +1820,8 @@ template class buffer { /// Appends data to the end of the buffer. template -// Workaround for MSVC2019 to fix error C2893: Failed to specialize function -// template 'void fmt::v11::detail::buffer::append(const U *,const U *)'. -#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940 - FMT_CONSTEXPR20 -#endif - void - append(const U* begin, const U* end) { + FMT_CONSTEXPR20 void append(const U* begin, const U* end) { + static_assert(std::is_same() || std::is_same(), ""); while (begin != end) { auto size = size_; auto free_cap = capacity_ - size; @@ -1865,7 +1834,7 @@ template class buffer { } // A loop is faster than memcpy on small sizes. T* out = ptr_ + size; - for (size_t i = 0; i < count; ++i) out[i] = begin[i]; + for (size_t i = 0; i < count; ++i) out[i] = static_cast(begin[i]); size_ += count; begin += count; } @@ -1875,7 +1844,7 @@ template class buffer { return ptr_[index]; } template - FMT_CONSTEXPR auto operator[](Idx index) const -> const T& { + constexpr auto operator[](Idx index) const -> const T& { return ptr_[index]; } }; @@ -1901,6 +1870,54 @@ class fixed_buffer_traits { } }; +template +struct has_append : std::false_type {}; + +template +struct has_append()) + .append(std::declval(), + std::declval()))>> + : std::true_type {}; + +template +struct has_insert : std::false_type {}; + +template +struct has_insert< + OutputIt, T, + void_t()) + .insert({}, std::declval(), std::declval()))>> + : std::true_type {}; + +// An optimized version of std::copy with the output value type (T). +template () && + has_append())> +FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + get_container(out).append(begin, end); + return out; +} + +template () && + !has_append() && + has_insert())> +FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + auto& c = get_container(out); + c.insert(c.end(), begin, end); + return out; +} + +template () || + !(has_append() || + has_insert()))> +FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + while (begin != end) *out++ = static_cast(*begin++); + return out; +} + // A buffer that writes to an output iterator when flushed. template class iterator_buffer : public Traits, public buffer { @@ -1918,7 +1935,7 @@ class iterator_buffer : public Traits, public buffer { this->clear(); const T* begin = data_; const T* end = begin + this->limit(size); - while (begin != end) *out_++ = *begin++; + out_ = copy(begin, end, out_); } public: @@ -2045,7 +2062,7 @@ template class counting_buffer : public buffer { } public: - FMT_CONSTEXPR counting_buffer() : buffer(grow, data_, 0, buffer_size) {} + constexpr counting_buffer() : buffer(grow, data_, 0, buffer_size) {} constexpr auto count() const noexcept -> size_t { return count_ + this->size(); @@ -2055,66 +2072,6 @@ template class counting_buffer : public buffer { template struct is_back_insert_iterator> : std::true_type {}; -template -struct has_back_insert_iterator_container_append : std::false_type {}; -template -struct has_back_insert_iterator_container_append< - OutputIt, InputIt, - void_t()) - .append(std::declval(), - std::declval()))>> : std::true_type {}; - -template -struct has_back_insert_iterator_container_insert_at_end : std::false_type {}; - -template -struct has_back_insert_iterator_container_insert_at_end< - OutputIt, InputIt, - void_t()) - .insert(get_container(std::declval()).end(), - std::declval(), - std::declval()))>> : std::true_type {}; - -// An optimized version of std::copy with the output value type (T). -template ::value&& - has_back_insert_iterator_container_append< - OutputIt, InputIt>::value)> -FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) - -> OutputIt { - get_container(out).append(begin, end); - return out; -} - -template ::value && - !has_back_insert_iterator_container_append< - OutputIt, InputIt>::value && - has_back_insert_iterator_container_insert_at_end< - OutputIt, InputIt>::value)> -FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) - -> OutputIt { - auto& c = get_container(out); - c.insert(c.end(), begin, end); - return out; -} - -template ::value && - (has_back_insert_iterator_container_append< - OutputIt, InputIt>::value || - has_back_insert_iterator_container_insert_at_end< - OutputIt, InputIt>::value)))> -FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { - while (begin != end) *out++ = static_cast(*begin++); - return out; -} - -template -FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { - return copy(s.begin(), s.end(), out); -} - template struct is_buffer_appender : std::false_type {}; template @@ -2184,9 +2141,9 @@ template class value { int int_value; unsigned uint_value; long long long_long_value; - unsigned long long ulong_long_value; - int128_opt int128_value; - uint128_opt uint128_value; + ullong ulong_long_value; + native_int128 int128_value; + native_uint128 uint128_value; bool bool_value; char_type char_value; float float_value; @@ -2205,25 +2162,15 @@ template class value { constexpr FMT_INLINE value(unsigned short x FMT_BUILTIN) : uint_value(x) {} constexpr FMT_INLINE value(int x) : int_value(x) {} constexpr FMT_INLINE value(unsigned x FMT_BUILTIN) : uint_value(x) {} - FMT_CONSTEXPR FMT_INLINE value(long x FMT_BUILTIN) : value(long_type(x)) {} - FMT_CONSTEXPR FMT_INLINE value(unsigned long x FMT_BUILTIN) + constexpr FMT_INLINE value(long x FMT_BUILTIN) : value(long_type(x)) {} + constexpr FMT_INLINE value(unsigned long x FMT_BUILTIN) : value(ulong_type(x)) {} constexpr FMT_INLINE value(long long x FMT_BUILTIN) : long_long_value(x) {} - constexpr FMT_INLINE value(unsigned long long x FMT_BUILTIN) - : ulong_long_value(x) {} - FMT_INLINE value(int128_opt x FMT_BUILTIN) : int128_value(x) {} - FMT_INLINE value(uint128_opt x FMT_BUILTIN) : uint128_value(x) {} + constexpr FMT_INLINE value(ullong x FMT_BUILTIN) : ulong_long_value(x) {} + FMT_INLINE value(native_int128 x FMT_BUILTIN) : int128_value(x) {} + FMT_INLINE value(native_uint128 x FMT_BUILTIN) : uint128_value(x) {} constexpr FMT_INLINE value(bool x FMT_BUILTIN) : bool_value(x) {} - template - constexpr FMT_INLINE value(bitint x FMT_BUILTIN) : long_long_value(x) { - static_assert(N <= 64, "unsupported _BitInt"); - } - template - constexpr FMT_INLINE value(ubitint x FMT_BUILTIN) : ulong_long_value(x) { - static_assert(N <= 64, "unsupported _BitInt"); - } - template ::value)> constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) { static_assert( @@ -2252,17 +2199,20 @@ template class value { string.data = sv.data(); string.size = sv.size(); } - FMT_INLINE value(void* x FMT_BUILTIN) : pointer(x) {} - FMT_INLINE value(const void* x FMT_BUILTIN) : pointer(x) {} - FMT_INLINE value(volatile void* x FMT_BUILTIN) + constexpr FMT_INLINE value(void* x FMT_BUILTIN) : pointer(x) {} + constexpr FMT_INLINE value(const void* x FMT_BUILTIN) : pointer(x) {} + constexpr FMT_INLINE value(volatile void* x FMT_BUILTIN) : pointer(const_cast(x)) {} - FMT_INLINE value(const volatile void* x FMT_BUILTIN) + constexpr FMT_INLINE value(const volatile void* x FMT_BUILTIN) : pointer(const_cast(x)) {} - FMT_INLINE value(nullptr_t) : pointer(nullptr) {} + constexpr FMT_INLINE value(nullptr_t) : pointer(nullptr) {} - template ::value || - std::is_member_pointer::value)> - value(const T&) { + template ::value || + std::is_member_pointer::value) && + !std::is_void::type>::value)> + constexpr value(const T&) { // Formatting of arbitrary pointers is disallowed. If you want to format a // pointer cast it to `void*` or `const void*`. In particular, this forbids // formatting of `[const] volatile char*` printed as bool by iostreams. @@ -2271,16 +2221,16 @@ template class value { } template ::value)> - value(const T& x) : value(format_as(x)) {} + constexpr value(const T& x) : value(format_as(x)) {} template ::value)> - value(const T& x) : value(formatter::format_as(x)) {} + constexpr value(const T& x) : value(formatter::format_as(x)) {} template ::value)> - value(const T& named_arg) : value(named_arg.value) {} + constexpr value(const T& named_arg) : value(named_arg.value) {} template ::value || !FMT_BUILTIN_TYPES)> - FMT_CONSTEXPR20 FMT_INLINE value(T& x) : value(x, custom_tag()) {} + FMT_CONSTEXPR FMT_INLINE value(T& x) : value(x, custom_tag()) {} FMT_ALWAYS_INLINE value(const named_arg_info* args, size_t size) : named_args{args, size} {} @@ -2306,7 +2256,7 @@ template class value { template ())> FMT_CONSTEXPR value(const T&, custom_tag) { // Cannot format an argument; to make type T formattable provide a - // formatter specialization: https://fmt.dev/latest/api.html#udt. + // formatter specialization: https://fmt.dev/latest/api#udt. type_is_unformattable_for _; } @@ -2327,8 +2277,8 @@ template class value { enum { packed_arg_bits = 4 }; // Maximum number of arguments with packed types. enum { max_packed_args = 62 / packed_arg_bits }; -enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; -enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; +enum : ullong { is_unpacked_bit = 1ULL << 63 }; +enum : ullong { has_named_args_bit = 1ULL << 62 }; template struct is_output_iterator : std::false_type {}; @@ -2341,18 +2291,16 @@ struct is_output_iterator< enable_if_t&>()++), T>::value>> : std::true_type {}; -template constexpr auto encode_types() -> unsigned long long { - return 0; -} +template constexpr auto encode_types() -> ullong { return 0; } template -constexpr auto encode_types() -> unsigned long long { - return static_cast(stored_type_constant::value) | +constexpr auto encode_types() -> ullong { + return unsigned(stored_type_constant::value) | (encode_types() << packed_arg_bits); } template -constexpr auto make_descriptor() -> unsigned long long { +constexpr auto make_descriptor() -> ullong { return NUM_ARGS <= max_packed_args ? encode_types() : is_unpacked_bit | NUM_ARGS; } @@ -2361,13 +2309,11 @@ template using arg_t = conditional_t, basic_format_arg>; -template +template struct named_arg_store { // args_[0].named_args points to named_args to avoid bloating format_args. - arg_t args[1u + NUM_ARGS]; - named_arg_info - named_args[static_cast(NUM_NAMED_ARGS)]; + arg_t args[NUM_ARGS + 1u]; + named_arg_info named_args[NUM_NAMED_ARGS + 0u]; template FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values) @@ -2394,8 +2340,7 @@ struct named_arg_store { // An array of references to arguments. It can be implicitly converted to // `basic_format_args` for passing into type-erased formatting functions // such as `vformat`. It is a plain struct to reduce binary size in debug mode. -template +template struct format_arg_store { // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. using type = @@ -2411,12 +2356,10 @@ template struct native_formatter { dynamic_format_specs specs_; public: - using nonlocking = void; - FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, TYPE); - if (const_check(TYPE == type::char_type)) check_char_specs(specs_); + if FMT_CONSTEXPR20 (TYPE == type::char_type) check_char_specs(specs_); return end; } @@ -2427,25 +2370,26 @@ template struct native_formatter { specs_.set_type(set ? presentation_type::debug : presentation_type::none); } - FMT_PRAGMA_CLANG(diagnostic ignored "-Wundefined-inline") template FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const -> decltype(ctx.out()); }; -template -struct locking - : bool_constant::value == type::custom_type> {}; -template -struct locking>::nonlocking>> - : std::false_type {}; +template constexpr bool enforce_compile_checks() { +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert( + FMT_USE_CONSTEVAL && B, + "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); +#endif + return true; +} -template FMT_CONSTEXPR inline auto is_locking() -> bool { - return locking::value; +template constexpr auto is_locking() -> bool { + return locking>::value; } template -FMT_CONSTEXPR inline auto is_locking() -> bool { - return locking::value || is_locking(); +constexpr auto is_locking() -> bool { + return locking>::value || is_locking(); } FMT_API void vformat_to(buffer& buf, string_view fmt, format_args args, @@ -2460,6 +2404,9 @@ inline void vprint_mojibake(FILE*, string_view, const format_args&, bool) {} // The main public API. +template +using named_arg = detail::named_arg; + template FMT_CONSTEXPR void parse_context::do_check_arg_id(int arg_id) { // Argument id is only checked at compile time during parsing because @@ -2488,15 +2435,15 @@ template class basic_appender { public: using container_type = detail::buffer; - FMT_CONSTEXPR basic_appender(detail::buffer& buf) : container(&buf) {} + constexpr basic_appender(detail::buffer& buf) : container(&buf) {} - FMT_CONSTEXPR20 auto operator=(T c) -> basic_appender& { + FMT_CONSTEXPR auto operator=(T c) -> basic_appender& { container->push_back(c); return *this; } - FMT_CONSTEXPR20 auto operator*() -> basic_appender& { return *this; } - FMT_CONSTEXPR20 auto operator++() -> basic_appender& { return *this; } - FMT_CONSTEXPR20 auto operator++(int) -> basic_appender { return *this; } + FMT_CONSTEXPR auto operator*() -> basic_appender& { return *this; } + FMT_CONSTEXPR auto operator++() -> basic_appender& { return *this; } + FMT_CONSTEXPR auto operator++(int) -> basic_appender { return *this; } }; // A formatting argument. Context is a template parameter for the compiled API @@ -2588,7 +2535,7 @@ template class basic_format_args { // If the number of arguments is less or equal to max_packed_args then // argument types are passed in the descriptor. This reduces binary code size // per formatting function call. - unsigned long long desc_; + ullong desc_; union { // If is_packed() returns true then argument values are stored in values_; // otherwise they are stored in args_. This is done to improve cache @@ -2612,7 +2559,7 @@ template class basic_format_args { return static_cast((desc_ >> shift) & mask); } - template + template using store = detail::format_arg_store; @@ -2622,14 +2569,14 @@ template class basic_format_args { constexpr basic_format_args() : desc_(0), args_(nullptr) {} /// Constructs a `basic_format_args` object from `format_arg_store`. - template constexpr FMT_ALWAYS_INLINE basic_format_args( const store& s) : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), values_(s.args) {} - template detail::max_packed_args)> constexpr basic_format_args(const store& s) : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), @@ -2646,10 +2593,10 @@ template class basic_format_args { FMT_CONSTEXPR auto get(int id) const -> format_arg { auto arg = format_arg(); if (!is_packed()) { - if (id < max_size()) arg = args_[id]; + if (unsigned(id) < unsigned(max_size())) arg = args_[id]; return arg; } - if (static_cast(id) >= detail::max_packed_args) return arg; + if (unsigned(id) >= detail::max_packed_args) return arg; arg.type_ = type(id); if (arg.type_ != detail::type::none_type) arg.value_ = values_[id]; return arg; @@ -2673,9 +2620,8 @@ template class basic_format_args { } auto max_size() const -> int { - unsigned long long max_packed = detail::max_packed_args; - return static_cast(is_packed() ? max_packed - : desc_ & ~detail::is_unpacked_bit); + return int(is_packed() ? ullong(detail::max_packed_args) + : desc_ & ~detail::is_unpacked_bit); } }; @@ -2694,7 +2640,7 @@ class context { /// Constructs a `context` object. References to the arguments are stored /// in the object so make sure they have appropriate lifetimes. - FMT_CONSTEXPR context(iterator out, format_args args, locale_ref loc = {}) + constexpr context(iterator out, format_args args, locale_ref loc = {}) : out_(out), args_(args), loc_(loc) {} context(context&&) = default; context(const context&) = delete; @@ -2710,12 +2656,12 @@ class context { auto args() const -> const format_args& { return args_; } // Returns an iterator to the beginning of the output range. - FMT_CONSTEXPR auto out() const -> iterator { return out_; } + constexpr auto out() const -> iterator { return out_; } // Advances the begin iterator to `it`. FMT_CONSTEXPR void advance_to(iterator) {} - FMT_CONSTEXPR auto locale() const -> locale_ref { return loc_; } + constexpr auto locale() const -> locale_ref { return loc_; } }; template struct runtime_format_string { @@ -2740,7 +2686,7 @@ template struct fstring { detail::count_static_named_args(); using checker = detail::format_string_checker< - char, static_cast(sizeof...(T)), num_static_named_args, + char, int(sizeof...(T)), num_static_named_args, num_static_named_args != detail::count_named_args()>; using arg_pack = detail::arg_pack; @@ -2756,38 +2702,30 @@ template struct fstring { static_assert(count<(is_view>::value && std::is_reference::value)...>() == 0, "passing views as lvalues is disallowed"); - if (FMT_USE_CONSTEVAL) parse_format_string(s, checker(s, arg_pack())); -#ifdef FMT_ENFORCE_COMPILE_STRING - static_assert( - FMT_USE_CONSTEVAL && sizeof(s) != 0, - "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); -#endif + if (FMT_USE_CONSTEVAL) + parse_format_string(str, checker(str, arg_pack())); + constexpr bool unused = detail::enforce_compile_checks(); + (void)unused; } template ::value)> FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const S& s) : str(s) { - auto sv = string_view(str); if (FMT_USE_CONSTEVAL) - detail::parse_format_string(sv, checker(sv, arg_pack())); -#ifdef FMT_ENFORCE_COMPILE_STRING - static_assert( - FMT_USE_CONSTEVAL && sizeof(s) != 0, - "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); -#endif + detail::parse_format_string(str, checker(str, arg_pack())); + constexpr bool unused = detail::enforce_compile_checks(); + (void)unused; } template ::value&& std::is_same::value)> FMT_ALWAYS_INLINE fstring(const S&) : str(S()) { FMT_CONSTEXPR auto sv = string_view(S()); - FMT_CONSTEXPR int unused = - (parse_format_string(sv, checker(sv, arg_pack())), 0); - detail::ignore_unused(unused); + FMT_CONSTEXPR int x = (parse_format_string(sv, checker(sv, arg_pack())), 0); + detail::ignore_unused(x); } fstring(runtime_format_string<> fmt) : str(fmt.str) {} - // Returning by reference generates better code in debug mode. - FMT_ALWAYS_INLINE operator const string_view&() const { return str; } + FMT_DEPRECATED operator const string_view&() const { return str; } auto get() const -> string_view { return str; } }; @@ -2797,7 +2735,7 @@ template using is_formattable = bool_constant::value, int*, T>, Char>, void>::value>; -#ifdef __cpp_concepts +#if defined(__cpp_concepts) && __cpp_concepts >= 201907L template concept formattable = is_formattable, Char>::value; #endif @@ -2818,19 +2756,17 @@ struct formatter(), - unsigned long long DESC = detail::make_descriptor()> + ullong DESC = detail::make_descriptor()> constexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args) -> detail::format_arg_store { - // Suppress warnings for pathological types convertible to detail::value. - FMT_PRAGMA_GCC(diagnostic ignored "-Wconversion") return {{args...}}; } template using vargs = - detail::format_arg_store(), detail::make_descriptor()>; @@ -2841,9 +2777,13 @@ using vargs = * **Example**: * * fmt::print("The answer is {answer}.", fmt::arg("answer", 42)); + * + * Named arguments passed with `fmt::arg` are not supported + * in compile-time checks, but `"answer"_a=42` are compile-time checked in + * sufficiently new compilers. See `operator""_a()`. */ -template -inline auto arg(const Char* name, const T& arg) -> detail::named_arg { +template +inline auto arg(const char* name, const T& arg) -> named_arg { return {name, arg}; } @@ -2851,6 +2791,7 @@ inline auto arg(const Char* name, const T& arg) -> detail::named_arg { template , char>::value)> +// DEPRECATED! Passing out as a forwarding reference. auto vformat_to(OutputIt&& out, string_view fmt, format_args args) -> remove_cvref_t { auto&& buf = detail::get_buffer(out); @@ -2877,10 +2818,8 @@ FMT_INLINE auto format_to(OutputIt&& out, format_string fmt, T&&... args) } template struct format_to_n_result { - /// Iterator past the end of the output range. - OutputIt out; - /// Total (not truncated) output size. - size_t size; + OutputIt out; ///< Iterator past the end of the output range. + size_t size; ///< Total (not truncated) output size. }; template fmt, } struct format_to_result { - /// Pointer to just after the last successful write in the array. - char* out; - /// Specifies if the output was truncated. - bool truncated; + char* out; ///< Pointer to just after the last successful write. + bool truncated; ///< Specifies if the output was truncated. FMT_CONSTEXPR operator char*() const { // Report truncation to prevent silent data loss. @@ -2920,8 +2857,8 @@ struct format_to_result { }; template -auto vformat_to(char (&out)[N], string_view fmt, format_args args) - -> format_to_result { +FMT_DEPRECATED auto vformat_to(char (&out)[N], string_view fmt, + format_args args) -> format_to_result { auto result = vformat_to_n(out, N, fmt, args); return {result.out, result.size > N}; } @@ -2958,10 +2895,10 @@ FMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args); template FMT_INLINE void print(format_string fmt, T&&... args) { vargs va = {{args...}}; - if (detail::const_check(!detail::use_utf8)) + if FMT_CONSTEXPR20 (!detail::use_utf8) return detail::vprint_mojibake(stdout, fmt.str, va, false); - return detail::is_locking() ? vprint_buffered(stdout, fmt.str, va) - : vprint(fmt.str, va); + detail::is_locking() ? vprint_buffered(stdout, fmt.str, va) + : vprint(fmt.str, va); } /** @@ -2975,10 +2912,10 @@ FMT_INLINE void print(format_string fmt, T&&... args) { template FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { vargs va = {{args...}}; - if (detail::const_check(!detail::use_utf8)) + if FMT_CONSTEXPR20 (!detail::use_utf8) return detail::vprint_mojibake(f, fmt.str, va, false); - return detail::is_locking() ? vprint_buffered(f, fmt.str, va) - : vprint(f, fmt.str, va); + detail::is_locking() ? vprint_buffered(f, fmt.str, va) + : vprint(f, fmt.str, va); } /// Formats `args` according to specifications in `fmt` and writes the output @@ -2986,20 +2923,17 @@ FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { template FMT_INLINE void println(FILE* f, format_string fmt, T&&... args) { vargs va = {{args...}}; - return detail::const_check(detail::use_utf8) - ? vprintln(f, fmt.str, va) - : detail::vprint_mojibake(f, fmt.str, va, true); + if FMT_CONSTEXPR20 (detail::use_utf8) return vprintln(f, fmt.str, va); + detail::vprint_mojibake(f, fmt.str, va, true); } /// Formats `args` according to specifications in `fmt` and writes the output /// to `stdout` followed by a newline. template FMT_INLINE void println(format_string fmt, T&&... args) { - return fmt::println(stdout, fmt, static_cast(args)...); + fmt::println(stdout, fmt, static_cast(args)...); } -FMT_PRAGMA_GCC(diagnostic pop) -FMT_PRAGMA_CLANG(diagnostic pop) FMT_PRAGMA_GCC(pop_options) FMT_END_EXPORT FMT_END_NAMESPACE diff --git a/third_party/fmt/format-inl.h b/third_party/fmt/format-inl.h index 945cb91..665f730 100644 --- a/third_party/fmt/format-inl.h +++ b/third_party/fmt/format-inl.h @@ -1,6 +1,6 @@ // Formatting library for C++ - implementation // -// Copyright (c) 2012 - 2016, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. @@ -8,12 +8,17 @@ #ifndef FMT_FORMAT_INL_H_ #define FMT_FORMAT_INL_H_ +#ifdef __SANITIZE_THREAD__ +extern "C" void __tsan_acquire(void*); +extern "C" void __tsan_release(void*); +#endif + #ifndef FMT_MODULE +# include // ptrdiff_t + # include # include // errno -# include -# include -# include +# include // std::bad_alloc #endif #if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) @@ -30,6 +35,14 @@ # define FMT_FUNC #endif +#if defined(FMT_USE_FULL_CACHE_DRAGONBOX) +// Use the provided definition. +#elif defined(__OPTIMIZE_SIZE__) +# define FMT_USE_FULL_CACHE_DRAGONBOX 0 +#else +# define FMT_USE_FULL_CACHE_DRAGONBOX 1 +#endif + FMT_BEGIN_NAMESPACE #ifndef FMT_CUSTOM_ASSERT_FAIL @@ -70,14 +83,19 @@ template auto locale_ref::get() const -> Locale { namespace detail { +FMT_FUNC auto allocate(size_t size) -> void* { + void* p = malloc(size); + if (!p) FMT_THROW(std::bad_alloc()); + return p; +} + FMT_FUNC void format_error_code(detail::buffer& out, int error_code, string_view message) noexcept { - // Report error code making sure that the output fits into - // inline_buffer_size to avoid dynamic memory allocation and potential - // bad_alloc. + // Report error code making sure that the output fits into inline_buffer_size + // to avoid dynamic memory allocation and potential bad_alloc. out.try_resize(0); - static const char SEP[] = ": "; - static const char ERROR_STR[] = "error "; + static constexpr char SEP[] = ": "; + static constexpr char ERROR_STR[] = "error "; // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; auto abs_value = static_cast>(error_code); @@ -195,10 +213,9 @@ inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t { // Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. -inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept - -> uint128_fallback { +inline auto umul192_lower128(uint64_t x, uint128 y) noexcept -> uint128 { uint64_t high = x * y.high(); - uint128_fallback high_low = umul128(x, y.low()); + uint128 high_low = umul128(x, y.low()); return {high + high_low.high(), high_low.low()}; } @@ -366,673 +383,673 @@ template <> struct cache_accessor { template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; - using cache_entry_type = uint128_fallback; + using cache_entry_type = uint128; - static auto get_cached_power(int k) noexcept -> uint128_fallback { + static auto get_cached_power(int k) noexcept -> uint128 { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); - static constexpr uint128_fallback pow10_significands[] = { + static constexpr uint128 pow10_significands[] = { #if FMT_USE_FULL_CACHE_DRAGONBOX - {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, - {0x9faacf3df73609b1, 0x77b191618c54e9ad}, - {0xc795830d75038c1d, 0xd59df5b9ef6a2418}, - {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e}, - {0x9becce62836ac577, 0x4ee367f9430aec33}, - {0xc2e801fb244576d5, 0x229c41f793cda740}, - {0xf3a20279ed56d48a, 0x6b43527578c11110}, - {0x9845418c345644d6, 0x830a13896b78aaaa}, - {0xbe5691ef416bd60c, 0x23cc986bc656d554}, - {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9}, - {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa}, - {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54}, - {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69}, - {0x91376c36d99995be, 0x23100809b9c21fa2}, - {0xb58547448ffffb2d, 0xabd40a0c2832a78b}, - {0xe2e69915b3fff9f9, 0x16c90c8f323f516d}, - {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4}, - {0xb1442798f49ffb4a, 0x99cd11cfdf41779d}, - {0xdd95317f31c7fa1d, 0x40405643d711d584}, - {0x8a7d3eef7f1cfc52, 0x482835ea666b2573}, - {0xad1c8eab5ee43b66, 0xda3243650005eed0}, - {0xd863b256369d4a40, 0x90bed43e40076a83}, - {0x873e4f75e2224e68, 0x5a7744a6e804a292}, - {0xa90de3535aaae202, 0x711515d0a205cb37}, - {0xd3515c2831559a83, 0x0d5a5b44ca873e04}, - {0x8412d9991ed58091, 0xe858790afe9486c3}, - {0xa5178fff668ae0b6, 0x626e974dbe39a873}, - {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, - {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a}, - {0xa139029f6a239f72, 0x1c1fffc1ebc44e81}, - {0xc987434744ac874e, 0xa327ffb266b56221}, - {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9}, - {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa}, - {0xc4ce17b399107c22, 0xcb550fb4384d21d4}, - {0xf6019da07f549b2b, 0x7e2a53a146606a49}, - {0x99c102844f94e0fb, 0x2eda7444cbfc426e}, - {0xc0314325637a1939, 0xfa911155fefb5309}, - {0xf03d93eebc589f88, 0x793555ab7eba27cb}, - {0x96267c7535b763b5, 0x4bc1558b2f3458df}, - {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17}, - {0xea9c227723ee8bcb, 0x465e15a979c1cadd}, - {0x92a1958a7675175f, 0x0bfacd89ec191eca}, - {0xb749faed14125d36, 0xcef980ec671f667c}, - {0xe51c79a85916f484, 0x82b7e12780e7401b}, - {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811}, - {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16}, - {0xdfbdcece67006ac9, 0x67a791e093e1d49b}, - {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1}, - {0xaecc49914078536d, 0x58fae9f773886e19}, - {0xda7f5bf590966848, 0xaf39a475506a899f}, - {0x888f99797a5e012d, 0x6d8406c952429604}, - {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84}, - {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65}, - {0x855c3be0a17fcd26, 0x5cf2eea09a550680}, - {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, - {0xd0601d8efc57b08b, 0xf13b94daf124da27}, - {0x823c12795db6ce57, 0x76c53d08d6b70859}, - {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f}, - {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a}, - {0xfe5d54150b090b02, 0xd3f93b35435d7c4d}, - {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0}, - {0xc6b8e9b0709f109a, 0x359ab6419ca1091c}, - {0xf867241c8cc6d4c0, 0xc30163d203c94b63}, - {0x9b407691d7fc44f8, 0x79e0de63425dcf1e}, - {0xc21094364dfb5636, 0x985915fc12f542e5}, - {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e}, - {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43}, - {0xbd8430bd08277231, 0x50c6ff782a838354}, - {0xece53cec4a314ebd, 0xa4f8bf5635246429}, - {0x940f4613ae5ed136, 0x871b7795e136be9a}, - {0xb913179899f68584, 0x28e2557b59846e40}, - {0xe757dd7ec07426e5, 0x331aeada2fe589d0}, - {0x9096ea6f3848984f, 0x3ff0d2c85def7622}, - {0xb4bca50b065abe63, 0x0fed077a756b53aa}, - {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895}, - {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d}, - {0xb080392cc4349dec, 0xbd8d794d96aacfb4}, - {0xdca04777f541c567, 0xecf0d7a0fc5583a1}, - {0x89e42caaf9491b60, 0xf41686c49db57245}, - {0xac5d37d5b79b6239, 0x311c2875c522ced6}, - {0xd77485cb25823ac7, 0x7d633293366b828c}, - {0x86a8d39ef77164bc, 0xae5dff9c02033198}, - {0xa8530886b54dbdeb, 0xd9f57f830283fdfd}, - {0xd267caa862a12d66, 0xd072df63c324fd7c}, - {0x8380dea93da4bc60, 0x4247cb9e59f71e6e}, - {0xa46116538d0deb78, 0x52d9be85f074e609}, - {0xcd795be870516656, 0x67902e276c921f8c}, - {0x806bd9714632dff6, 0x00ba1cd8a3db53b7}, - {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5}, - {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce}, - {0xfad2a4b13d1b5d6c, 0x796b805720085f82}, - {0x9cc3a6eec6311a63, 0xcbe3303674053bb1}, - {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d}, - {0xf4f1b4d515acb93b, 0xee92fb5515482d45}, - {0x991711052d8bf3c5, 0x751bdd152d4d1c4b}, - {0xbf5cd54678eef0b6, 0xd262d45a78a0635e}, - {0xef340a98172aace4, 0x86fb897116c87c35}, - {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1}, - {0xbae0a846d2195712, 0x8974836059cca10a}, - {0xe998d258869facd7, 0x2bd1a438703fc94c}, - {0x91ff83775423cc06, 0x7b6306a34627ddd0}, - {0xb67f6455292cbf08, 0x1a3bc84c17b1d543}, - {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94}, - {0x8e938662882af53e, 0x547eb47b7282ee9d}, - {0xb23867fb2a35b28d, 0xe99e619a4f23aa44}, - {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5}, - {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05}, - {0xae0b158b4738705e, 0x9624ab50b148d446}, - {0xd98ddaee19068c76, 0x3badd624dd9b0958}, - {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7}, - {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d}, - {0xd47487cc8470652b, 0x7647c32000696720}, - {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074}, - {0xa5fb0a17c777cf09, 0xf468107100525891}, - {0xcf79cc9db955c2cc, 0x7182148d4066eeb5}, - {0x81ac1fe293d599bf, 0xc6f14cd848405531}, - {0xa21727db38cb002f, 0xb8ada00e5a506a7d}, - {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d}, - {0xfd442e4688bd304a, 0x908f4a166d1da664}, - {0x9e4a9cec15763e2e, 0x9a598e4e043287ff}, - {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe}, - {0xf7549530e188c128, 0xd12bee59e68ef47d}, - {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf}, - {0xc13a148e3032d6e7, 0xe36a52363c1faf02}, - {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2}, - {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba}, - {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8}, - {0xebdf661791d60f56, 0x111b495b3464ad22}, - {0x936b9fcebb25c995, 0xcab10dd900beec35}, - {0xb84687c269ef3bfb, 0x3d5d514f40eea743}, - {0xe65829b3046b0afa, 0x0cb4a5a3112a5113}, - {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac}, - {0xb3f4e093db73a093, 0x59ed216765690f57}, - {0xe0f218b8d25088b8, 0x306869c13ec3532d}, - {0x8c974f7383725573, 0x1e414218c73a13fc}, - {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, - {0xdbac6c247d62a583, 0xdf45f746b74abf3a}, - {0x894bc396ce5da772, 0x6b8bba8c328eb784}, - {0xab9eb47c81f5114f, 0x066ea92f3f326565}, - {0xd686619ba27255a2, 0xc80a537b0efefebe}, - {0x8613fd0145877585, 0xbd06742ce95f5f37}, - {0xa798fc4196e952e7, 0x2c48113823b73705}, - {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6}, - {0x82ef85133de648c4, 0x9a984d73dbe722fc}, - {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb}, - {0xcc963fee10b7d1b3, 0x318df905079926a9}, - {0xffbbcfe994e5c61f, 0xfdf17746497f7053}, - {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634}, - {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1}, - {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1}, - {0x9c1661a651213e2d, 0x06bea10ca65c084f}, - {0xc31bfa0fe5698db8, 0x486e494fcff30a63}, - {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb}, - {0x986ddb5c6b3a76b7, 0xf89629465a75e01d}, - {0xbe89523386091465, 0xf6bbb397f1135824}, - {0xee2ba6c0678b597f, 0x746aa07ded582e2d}, - {0x94db483840b717ef, 0xa8c2a44eb4571cdd}, - {0xba121a4650e4ddeb, 0x92f34d62616ce414}, - {0xe896a0d7e51e1566, 0x77b020baf9c81d18}, - {0x915e2486ef32cd60, 0x0ace1474dc1d122f}, - {0xb5b5ada8aaff80b8, 0x0d819992132456bb}, - {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a}, - {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, - {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3}, - {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf}, - {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c}, - {0xad4ab7112eb3929d, 0x86c16c98d2c953c7}, - {0xd89d64d57a607744, 0xe871c7bf077ba8b8}, - {0x87625f056c7c4a8b, 0x11471cd764ad4973}, - {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0}, - {0xd389b47879823479, 0x4aff1d108d4ec2c4}, - {0x843610cb4bf160cb, 0xcedf722a585139bb}, - {0xa54394fe1eedb8fe, 0xc2974eb4ee658829}, - {0xce947a3da6a9273e, 0x733d226229feea33}, - {0x811ccc668829b887, 0x0806357d5a3f5260}, - {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8}, - {0xc9bcff6034c13052, 0xfc89b393dd02f0b6}, - {0xfc2c3f3841f17c67, 0xbbac2078d443ace3}, - {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e}, - {0xc5029163f384a931, 0x0a9e795e65d4df12}, - {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6}, - {0x99ea0196163fa42e, 0x504bced1bf8e4e46}, - {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7}, - {0xf07da27a82c37088, 0x5d767327bb4e5a4d}, - {0x964e858c91ba2655, 0x3a6a07f8d510f870}, - {0xbbe226efb628afea, 0x890489f70a55368c}, - {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f}, - {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e}, - {0xb77ada0617e3bbcb, 0x09ce6ebb40173745}, - {0xe55990879ddcaabd, 0xcc420a6a101d0516}, - {0x8f57fa54c2a9eab6, 0x9fa946824a12232e}, - {0xb32df8e9f3546564, 0x47939822dc96abfa}, - {0xdff9772470297ebd, 0x59787e2b93bc56f8}, - {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b}, - {0xaefae51477a06b03, 0xede622920b6b23f2}, - {0xdab99e59958885c4, 0xe95fab368e45ecee}, - {0x88b402f7fd75539b, 0x11dbcb0218ebb415}, - {0xaae103b5fcd2a881, 0xd652bdc29f26a11a}, - {0xd59944a37c0752a2, 0x4be76d3346f04960}, - {0x857fcae62d8493a5, 0x6f70a4400c562ddc}, - {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953}, - {0xd097ad07a71f26b2, 0x7e2000a41346a7a8}, - {0x825ecc24c873782f, 0x8ed400668c0c28c9}, - {0xa2f67f2dfa90563b, 0x728900802f0f32fb}, - {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba}, - {0xfea126b7d78186bc, 0xe2f610c84987bfa9}, - {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca}, - {0xc6ede63fa05d3143, 0x91503d1c79720dbc}, - {0xf8a95fcf88747d94, 0x75a44c6397ce912b}, - {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb}, - {0xc24452da229b021b, 0xfbe85badce996169}, - {0xf2d56790ab41c2a2, 0xfae27299423fb9c4}, - {0x97c560ba6b0919a5, 0xdccd879fc967d41b}, - {0xbdb6b8e905cb600f, 0x5400e987bbc1c921}, - {0xed246723473e3813, 0x290123e9aab23b69}, - {0x9436c0760c86e30b, 0xf9a0b6720aaf6522}, - {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, - {0xe7958cb87392c2c2, 0xb60b1d1230b20e05}, - {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3}, - {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4}, - {0xe2280b6c20dd5232, 0x25c6da63c38de1b1}, - {0x8d590723948a535f, 0x579c487e5a38ad0f}, - {0xb0af48ec79ace837, 0x2d835a9df0c6d852}, - {0xdcdb1b2798182244, 0xf8e431456cf88e66}, - {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900}, - {0xac8b2d36eed2dac5, 0xe272467e3d222f40}, - {0xd7adf884aa879177, 0x5b0ed81dcc6abb10}, - {0x86ccbb52ea94baea, 0x98e947129fc2b4ea}, - {0xa87fea27a539e9a5, 0x3f2398d747b36225}, - {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae}, - {0x83a3eeeef9153e89, 0x1953cf68300424ad}, - {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8}, - {0xcdb02555653131b6, 0x3792f412cb06794e}, - {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1}, - {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5}, - {0xc8de047564d20a8b, 0xf245825a5a445276}, - {0xfb158592be068d2e, 0xeed6e2f0f0d56713}, - {0x9ced737bb6c4183d, 0x55464dd69685606c}, - {0xc428d05aa4751e4c, 0xaa97e14c3c26b887}, - {0xf53304714d9265df, 0xd53dd99f4b3066a9}, - {0x993fe2c6d07b7fab, 0xe546a8038efe402a}, - {0xbf8fdb78849a5f96, 0xde98520472bdd034}, - {0xef73d256a5c0f77c, 0x963e66858f6d4441}, - {0x95a8637627989aad, 0xdde7001379a44aa9}, - {0xbb127c53b17ec159, 0x5560c018580d5d53}, - {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7}, - {0x9226712162ab070d, 0xcab3961304ca70e9}, - {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23}, - {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b}, - {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243}, - {0xb267ed1940f1c61c, 0x55f038b237591ed4}, - {0xdf01e85f912e37a3, 0x6b6c46dec52f6689}, - {0x8b61313bbabce2c6, 0x2323ac4b3b3da016}, - {0xae397d8aa96c1b77, 0xabec975e0a0d081b}, - {0xd9c7dced53c72255, 0x96e7bd358c904a22}, - {0x881cea14545c7575, 0x7e50d64177da2e55}, - {0xaa242499697392d2, 0xdde50bd1d5d0b9ea}, - {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865}, - {0x84ec3c97da624ab4, 0xbd5af13bef0b113f}, - {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f}, - {0xcfb11ead453994ba, 0x67de18eda5814af3}, - {0x81ceb32c4b43fcf4, 0x80eacf948770ced8}, - {0xa2425ff75e14fc31, 0xa1258379a94d028e}, - {0xcad2f7f5359a3b3e, 0x096ee45813a04331}, - {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd}, - {0x9e74d1b791e07e48, 0x775ea264cf55347e}, - {0xc612062576589dda, 0x95364afe032a819e}, - {0xf79687aed3eec551, 0x3a83ddbd83f52205}, - {0x9abe14cd44753b52, 0xc4926a9672793543}, - {0xc16d9a0095928a27, 0x75b7053c0f178294}, - {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, - {0x971da05074da7bee, 0xd3f6fc16ebca5e04}, - {0xbce5086492111aea, 0x88f4bb1ca6bcf585}, - {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6}, - {0x9392ee8e921d5d07, 0x3aff322e62439fd0}, - {0xb877aa3236a4b449, 0x09befeb9fad487c3}, - {0xe69594bec44de15b, 0x4c2ebe687989a9b4}, - {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11}, - {0xb424dc35095cd80f, 0x538484c19ef38c95}, - {0xe12e13424bb40e13, 0x2865a5f206b06fba}, - {0x8cbccc096f5088cb, 0xf93f87b7442e45d4}, - {0xafebff0bcb24aafe, 0xf78f69a51539d749}, - {0xdbe6fecebdedd5be, 0xb573440e5a884d1c}, - {0x89705f4136b4a597, 0x31680a88f8953031}, - {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e}, - {0xd6bf94d5e57a42bc, 0x3d32907604691b4d}, - {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110}, - {0xa7c5ac471b478423, 0x0fcf80dc33721d54}, - {0xd1b71758e219652b, 0xd3c36113404ea4a9}, - {0x83126e978d4fdf3b, 0x645a1cac083126ea}, - {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4}, - {0xcccccccccccccccc, 0xcccccccccccccccd}, - {0x8000000000000000, 0x0000000000000000}, - {0xa000000000000000, 0x0000000000000000}, - {0xc800000000000000, 0x0000000000000000}, - {0xfa00000000000000, 0x0000000000000000}, - {0x9c40000000000000, 0x0000000000000000}, - {0xc350000000000000, 0x0000000000000000}, - {0xf424000000000000, 0x0000000000000000}, - {0x9896800000000000, 0x0000000000000000}, - {0xbebc200000000000, 0x0000000000000000}, - {0xee6b280000000000, 0x0000000000000000}, - {0x9502f90000000000, 0x0000000000000000}, - {0xba43b74000000000, 0x0000000000000000}, - {0xe8d4a51000000000, 0x0000000000000000}, - {0x9184e72a00000000, 0x0000000000000000}, - {0xb5e620f480000000, 0x0000000000000000}, - {0xe35fa931a0000000, 0x0000000000000000}, - {0x8e1bc9bf04000000, 0x0000000000000000}, - {0xb1a2bc2ec5000000, 0x0000000000000000}, - {0xde0b6b3a76400000, 0x0000000000000000}, - {0x8ac7230489e80000, 0x0000000000000000}, - {0xad78ebc5ac620000, 0x0000000000000000}, - {0xd8d726b7177a8000, 0x0000000000000000}, - {0x878678326eac9000, 0x0000000000000000}, - {0xa968163f0a57b400, 0x0000000000000000}, - {0xd3c21bcecceda100, 0x0000000000000000}, - {0x84595161401484a0, 0x0000000000000000}, - {0xa56fa5b99019a5c8, 0x0000000000000000}, - {0xcecb8f27f4200f3a, 0x0000000000000000}, - {0x813f3978f8940984, 0x4000000000000000}, - {0xa18f07d736b90be5, 0x5000000000000000}, - {0xc9f2c9cd04674ede, 0xa400000000000000}, - {0xfc6f7c4045812296, 0x4d00000000000000}, - {0x9dc5ada82b70b59d, 0xf020000000000000}, - {0xc5371912364ce305, 0x6c28000000000000}, - {0xf684df56c3e01bc6, 0xc732000000000000}, - {0x9a130b963a6c115c, 0x3c7f400000000000}, - {0xc097ce7bc90715b3, 0x4b9f100000000000}, - {0xf0bdc21abb48db20, 0x1e86d40000000000}, - {0x96769950b50d88f4, 0x1314448000000000}, - {0xbc143fa4e250eb31, 0x17d955a000000000}, - {0xeb194f8e1ae525fd, 0x5dcfab0800000000}, - {0x92efd1b8d0cf37be, 0x5aa1cae500000000}, - {0xb7abc627050305ad, 0xf14a3d9e40000000}, - {0xe596b7b0c643c719, 0x6d9ccd05d0000000}, - {0x8f7e32ce7bea5c6f, 0xe4820023a2000000}, - {0xb35dbf821ae4f38b, 0xdda2802c8a800000}, - {0xe0352f62a19e306e, 0xd50b2037ad200000}, - {0x8c213d9da502de45, 0x4526f422cc340000}, - {0xaf298d050e4395d6, 0x9670b12b7f410000}, - {0xdaf3f04651d47b4c, 0x3c0cdd765f114000}, - {0x88d8762bf324cd0f, 0xa5880a69fb6ac800}, - {0xab0e93b6efee0053, 0x8eea0d047a457a00}, - {0xd5d238a4abe98068, 0x72a4904598d6d880}, - {0x85a36366eb71f041, 0x47a6da2b7f864750}, - {0xa70c3c40a64e6c51, 0x999090b65f67d924}, - {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d}, - {0x82818f1281ed449f, 0xbff8f10e7a8921a5}, - {0xa321f2d7226895c7, 0xaff72d52192b6a0e}, - {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764491}, - {0xfee50b7025c36a08, 0x02f236d04753d5b5}, - {0x9f4f2726179a2245, 0x01d762422c946591}, - {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef6}, - {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb3}, - {0x9b934c3b330c8577, 0x63cc55f49f88eb30}, - {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fc}, - {0xf316271c7fc3908a, 0x8bef464e3945ef7b}, - {0x97edd871cfda3a56, 0x97758bf0e3cbb5ad}, - {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea318}, - {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bde}, - {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6b}, - {0xb975d6b6ee39e436, 0xb3e2fd538e122b45}, - {0xe7d34c64a9c85d44, 0x60dbbca87196b617}, - {0x90e40fbeea1d3a4a, 0xbc8955e946fe31ce}, - {0xb51d13aea4a488dd, 0x6babab6398bdbe42}, - {0xe264589a4dcdab14, 0xc696963c7eed2dd2}, - {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca3}, - {0xb0de65388cc8ada8, 0x3b25a55f43294bcc}, - {0xdd15fe86affad912, 0x49ef0eb713f39ebf}, - {0x8a2dbf142dfcc7ab, 0x6e3569326c784338}, - {0xacb92ed9397bf996, 0x49c2c37f07965405}, - {0xd7e77a8f87daf7fb, 0xdc33745ec97be907}, - {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a4}, - {0xa8acd7c0222311bc, 0xc40832ea0d68ce0d}, - {0xd2d80db02aabd62b, 0xf50a3fa490c30191}, - {0x83c7088e1aab65db, 0x792667c6da79e0fb}, - {0xa4b8cab1a1563f52, 0x577001b891185939}, - {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, - {0x80b05e5ac60b6178, 0x544f8158315b05b5}, - {0xa0dc75f1778e39d6, 0x696361ae3db1c722}, - {0xc913936dd571c84c, 0x03bc3a19cd1e38ea}, - {0xfb5878494ace3a5f, 0x04ab48a04065c724}, - {0x9d174b2dcec0e47b, 0x62eb0d64283f9c77}, - {0xc45d1df942711d9a, 0x3ba5d0bd324f8395}, - {0xf5746577930d6500, 0xca8f44ec7ee3647a}, - {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecc}, - {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67f}, - {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101f}, - {0x95d04aee3b80ece5, 0xbba1f1d158724a13}, - {0xbb445da9ca61281f, 0x2a8a6e45ae8edc98}, - {0xea1575143cf97226, 0xf52d09d71a3293be}, - {0x924d692ca61be758, 0x593c2626705f9c57}, - {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836d}, - {0xe498f455c38b997a, 0x0b6dfb9c0f956448}, - {0x8edf98b59a373fec, 0x4724bd4189bd5ead}, - {0xb2977ee300c50fe7, 0x58edec91ec2cb658}, - {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ee}, - {0x8b865b215899f46c, 0xbd79e0d20082ee75}, - {0xae67f1e9aec07187, 0xecd8590680a3aa12}, - {0xda01ee641a708de9, 0xe80e6f4820cc9496}, - {0x884134fe908658b2, 0x3109058d147fdcde}, - {0xaa51823e34a7eede, 0xbd4b46f0599fd416}, - {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91b}, - {0x850fadc09923329e, 0x03e2cf6bc604ddb1}, - {0xa6539930bf6bff45, 0x84db8346b786151d}, - {0xcfe87f7cef46ff16, 0xe612641865679a64}, - {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07f}, - {0xa26da3999aef7749, 0xe3be5e330f38f09e}, - {0xcb090c8001ab551c, 0x5cadf5bfd3072cc6}, - {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f7}, - {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afb}, - {0xc646d63501a1511d, 0xb281e1fd541501b9}, - {0xf7d88bc24209a565, 0x1f225a7ca91a4227}, - {0x9ae757596946075f, 0x3375788de9b06959}, - {0xc1a12d2fc3978937, 0x0052d6b1641c83af}, - {0xf209787bb47d6b84, 0xc0678c5dbd23a49b}, - {0x9745eb4d50ce6332, 0xf840b7ba963646e1}, - {0xbd176620a501fbff, 0xb650e5a93bc3d899}, - {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebf}, - {0x93ba47c980e98cdf, 0xc66f336c36b10138}, - {0xb8a8d9bbe123f017, 0xb80b0047445d4185}, - {0xe6d3102ad96cec1d, 0xa60dc059157491e6}, - {0x9043ea1ac7e41392, 0x87c89837ad68db30}, - {0xb454e4a179dd1877, 0x29babe4598c311fc}, - {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67b}, - {0x8ce2529e2734bb1d, 0x1899e4a65f58660d}, - {0xb01ae745b101e9e4, 0x5ec05dcff72e7f90}, - {0xdc21a1171d42645d, 0x76707543f4fa1f74}, - {0x899504ae72497eba, 0x6a06494a791c53a9}, - {0xabfa45da0edbde69, 0x0487db9d17636893}, - {0xd6f8d7509292d603, 0x45a9d2845d3c42b7}, - {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, - {0xa7f26836f282b732, 0x8e6cac7768d7141f}, - {0xd1ef0244af2364ff, 0x3207d795430cd927}, - {0x8335616aed761f1f, 0x7f44e6bd49e807b9}, - {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a7}, - {0xcd036837130890a1, 0x36dba887c37a8c10}, - {0x802221226be55a64, 0xc2494954da2c978a}, - {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6d}, - {0xc83553c5c8965d3d, 0x6f92829494e5acc8}, - {0xfa42a8b73abbf48c, 0xcb772339ba1f17fa}, - {0x9c69a97284b578d7, 0xff2a760414536efc}, - {0xc38413cf25e2d70d, 0xfef5138519684abb}, - {0xf46518c2ef5b8cd1, 0x7eb258665fc25d6a}, - {0x98bf2f79d5993802, 0xef2f773ffbd97a62}, - {0xbeeefb584aff8603, 0xaafb550ffacfd8fb}, - {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf39}, - {0x952ab45cfa97a0b2, 0xdd945a747bf26184}, - {0xba756174393d88df, 0x94f971119aeef9e5}, - {0xe912b9d1478ceb17, 0x7a37cd5601aab85e}, - {0x91abb422ccb812ee, 0xac62e055c10ab33b}, - {0xb616a12b7fe617aa, 0x577b986b314d600a}, - {0xe39c49765fdf9d94, 0xed5a7e85fda0b80c}, - {0x8e41ade9fbebc27d, 0x14588f13be847308}, - {0xb1d219647ae6b31c, 0x596eb2d8ae258fc9}, - {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bc}, - {0x8aec23d680043bee, 0x25de7bb9480d5855}, - {0xada72ccc20054ae9, 0xaf561aa79a10ae6b}, - {0xd910f7ff28069da4, 0x1b2ba1518094da05}, - {0x87aa9aff79042286, 0x90fb44d2f05d0843}, - {0xa99541bf57452b28, 0x353a1607ac744a54}, - {0xd3fa922f2d1675f2, 0x42889b8997915ce9}, - {0x847c9b5d7c2e09b7, 0x69956135febada12}, - {0xa59bc234db398c25, 0x43fab9837e699096}, - {0xcf02b2c21207ef2e, 0x94f967e45e03f4bc}, - {0x8161afb94b44f57d, 0x1d1be0eebac278f6}, - {0xa1ba1ba79e1632dc, 0x6462d92a69731733}, - {0xca28a291859bbf93, 0x7d7b8f7503cfdcff}, - {0xfcb2cb35e702af78, 0x5cda735244c3d43f}, - {0x9defbf01b061adab, 0x3a0888136afa64a8}, - {0xc56baec21c7a1916, 0x088aaa1845b8fdd1}, - {0xf6c69a72a3989f5b, 0x8aad549e57273d46}, - {0x9a3c2087a63f6399, 0x36ac54e2f678864c}, - {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7de}, - {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d6}, - {0x969eb7c47859e743, 0x9f644ae5a4b1b326}, - {0xbc4665b596706114, 0x873d5d9f0dde1fef}, - {0xeb57ff22fc0c7959, 0xa90cb506d155a7eb}, - {0x9316ff75dd87cbd8, 0x09a7f12442d588f3}, - {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30}, - {0xe5d3ef282a242e81, 0x8f1668c8a86da5fb}, - {0x8fa475791a569d10, 0xf96e017d694487bd}, - {0xb38d92d760ec4455, 0x37c981dcc395a9ad}, - {0xe070f78d3927556a, 0x85bbe253f47b1418}, - {0x8c469ab843b89562, 0x93956d7478ccec8f}, - {0xaf58416654a6babb, 0x387ac8d1970027b3}, - {0xdb2e51bfe9d0696a, 0x06997b05fcc0319f}, - {0x88fcf317f22241e2, 0x441fece3bdf81f04}, - {0xab3c2fddeeaad25a, 0xd527e81cad7626c4}, - {0xd60b3bd56a5586f1, 0x8a71e223d8d3b075}, - {0x85c7056562757456, 0xf6872d5667844e4a}, - {0xa738c6bebb12d16c, 0xb428f8ac016561dc}, - {0xd106f86e69d785c7, 0xe13336d701beba53}, - {0x82a45b450226b39c, 0xecc0024661173474}, - {0xa34d721642b06084, 0x27f002d7f95d0191}, - {0xcc20ce9bd35c78a5, 0x31ec038df7b441f5}, - {0xff290242c83396ce, 0x7e67047175a15272}, - {0x9f79a169bd203e41, 0x0f0062c6e984d387}, - {0xc75809c42c684dd1, 0x52c07b78a3e60869}, - {0xf92e0c3537826145, 0xa7709a56ccdf8a83}, - {0x9bbcc7a142b17ccb, 0x88a66076400bb692}, - {0xc2abf989935ddbfe, 0x6acff893d00ea436}, - {0xf356f7ebf83552fe, 0x0583f6b8c4124d44}, - {0x98165af37b2153de, 0xc3727a337a8b704b}, - {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5d}, - {0xeda2ee1c7064130c, 0x1162def06f79df74}, - {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba9}, - {0xb9a74a0637ce2ee1, 0x6d953e2bd7173693}, - {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0438}, - {0x910ab1d4db9914a0, 0x1d9c9892400a22a3}, - {0xb54d5e4a127f59c8, 0x2503beb6d00cab4c}, - {0xe2a0b5dc971f303a, 0x2e44ae64840fd61e}, - {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, - {0xb10d8e1456105dad, 0x7425a83e872c5f48}, - {0xdd50f1996b947518, 0xd12f124e28f7771a}, - {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa70}, - {0xace73cbfdc0bfb7b, 0x636cc64d1001550c}, - {0xd8210befd30efa5a, 0x3c47f7e05401aa4f}, - {0x8714a775e3e95c78, 0x65acfaec34810a72}, - {0xa8d9d1535ce3b396, 0x7f1839a741a14d0e}, - {0xd31045a8341ca07c, 0x1ede48111209a051}, - {0x83ea2b892091e44d, 0x934aed0aab460433}, - {0xa4e4b66b68b65d60, 0xf81da84d56178540}, - {0xce1de40642e3f4b9, 0x36251260ab9d668f}, - {0x80d2ae83e9ce78f3, 0xc1d72b7c6b42601a}, - {0xa1075a24e4421730, 0xb24cf65b8612f820}, - {0xc94930ae1d529cfc, 0xdee033f26797b628}, - {0xfb9b7cd9a4a7443c, 0x169840ef017da3b2}, - {0x9d412e0806e88aa5, 0x8e1f289560ee864f}, - {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e3}, - {0xf5b5d7ec8acb58a2, 0xae10af696774b1dc}, - {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef2a}, - {0xbff610b0cc6edd3f, 0x17fd090a58d32af4}, - {0xeff394dcff8a948e, 0xddfc4b4cef07f5b1}, - {0x95f83d0a1fb69cd9, 0x4abdaf101564f98f}, - {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f2}, - {0xea53df5fd18d5513, 0x84c86189216dc5ee}, - {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb5}, - {0xb7118682dbb66a77, 0x3fbc8c33221dc2a2}, - {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, - {0x8f05b1163ba6832d, 0x29cb4d87f2a7400f}, - {0xb2c71d5bca9023f8, 0x743e20e9ef511013}, - {0xdf78e4b2bd342cf6, 0x914da9246b255417}, - {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f}, - {0xae9672aba3d0c320, 0xa184ac2473b529b2}, - {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741f}, - {0x8865899617fb1871, 0x7e2fa67c7a658893}, - {0xaa7eebfb9df9de8d, 0xddbb901b98feeab8}, - {0xd51ea6fa85785631, 0x552a74227f3ea566}, - {0x8533285c936b35de, 0xd53a88958f872760}, - {0xa67ff273b8460356, 0x8a892abaf368f138}, - {0xd01fef10a657842c, 0x2d2b7569b0432d86}, - {0x8213f56a67f6b29b, 0x9c3b29620e29fc74}, - {0xa298f2c501f45f42, 0x8349f3ba91b47b90}, - {0xcb3f2f7642717713, 0x241c70a936219a74}, - {0xfe0efb53d30dd4d7, 0xed238cd383aa0111}, - {0x9ec95d1463e8a506, 0xf4363804324a40ab}, - {0xc67bb4597ce2ce48, 0xb143c6053edcd0d6}, - {0xf81aa16fdc1b81da, 0xdd94b7868e94050b}, - {0x9b10a4e5e9913128, 0xca7cf2b4191c8327}, - {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f1}, - {0xf24a01a73cf2dccf, 0xbc633b39673c8ced}, - {0x976e41088617ca01, 0xd5be0503e085d814}, - {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e19}, - {0xec9c459d51852ba2, 0xddf8e7d60ed1219f}, - {0x93e1ab8252f33b45, 0xcabb90e5c942b504}, - {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, - {0xe7109bfba19c0c9d, 0x0cc512670a783ad5}, - {0x906a617d450187e2, 0x27fb2b80668b24c6}, - {0xb484f9dc9641e9da, 0xb1f9f660802dedf7}, - {0xe1a63853bbd26451, 0x5e7873f8a0396974}, - {0x8d07e33455637eb2, 0xdb0b487b6423e1e9}, - {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda63}, - {0xdc5c5301c56b75f7, 0x7641a140cc7810fc}, - {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9e}, - {0xac2820d9623bf429, 0x546345fa9fbdcd45}, - {0xd732290fbacaf133, 0xa97c177947ad4096}, - {0x867f59a9d4bed6c0, 0x49ed8eabcccc485e}, - {0xa81f301449ee8c70, 0x5c68f256bfff5a75}, - {0xd226fc195c6a2f8c, 0x73832eec6fff3112}, - {0x83585d8fd9c25db7, 0xc831fd53c5ff7eac}, - {0xa42e74f3d032f525, 0xba3e7ca8b77f5e56}, - {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35ec}, - {0x80444b5e7aa7cf85, 0x7980d163cf5b81b4}, - {0xa0555e361951c366, 0xd7e105bcc3326220}, - {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa8}, - {0xfa856334878fc150, 0xb14f98f6f0feb952}, - {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d4}, - {0xc3b8358109e84f07, 0x0a862f80ec4700c9}, - {0xf4a642e14c6262c8, 0xcd27bb612758c0fb}, - {0x98e7e9cccfbd7dbd, 0x8038d51cb897789d}, - {0xbf21e44003acdd2c, 0xe0470a63e6bd56c4}, - {0xeeea5d5004981478, 0x1858ccfce06cac75}, - {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, - {0xbaa718e68396cffd, 0xd30560258f54e6bb}, - {0xe950df20247c83fd, 0x47c6b82ef32a206a}, - {0x91d28b7416cdd27e, 0x4cdc331d57fa5442}, - {0xb6472e511c81471d, 0xe0133fe4adf8e953}, - {0xe3d8f9e563a198e5, 0x58180fddd97723a7}, - {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7649}, - {0xb201833b35d63f73, 0x2cd2cc6551e513db}, - {0xde81e40a034bcf4f, 0xf8077f7ea65e58d2}, - {0x8b112e86420f6191, 0xfb04afaf27faf783}, - {0xadd57a27d29339f6, 0x79c5db9af1f9b564}, - {0xd94ad8b1c7380874, 0x18375281ae7822bd}, - {0x87cec76f1c830548, 0x8f2293910d0b15b6}, - {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb23}, - {0xd433179d9c8cb841, 0x5fa60692a46151ec}, - {0x849feec281d7f328, 0xdbc7c41ba6bcd334}, - {0xa5c7ea73224deff3, 0x12b9b522906c0801}, - {0xcf39e50feae16bef, 0xd768226b34870a01}, - {0x81842f29f2cce375, 0xe6a1158300d46641}, - {0xa1e53af46f801c53, 0x60495ae3c1097fd1}, - {0xca5e89b18b602368, 0x385bb19cb14bdfc5}, - {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, - {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, - {0xc5a05277621be293, 0xc7098b7305241886}, - {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8}, - {0x9a65406d44a5c903, 0x737f74f1dc043329}, - {0xc0fe908895cf3b44, 0x505f522e53053ff3}, - {0xf13e34aabb430a15, 0x647726b9e7c68ff0}, - {0x96c6e0eab509e64d, 0x5eca783430dc19f6}, - {0xbc789925624c5fe0, 0xb67d16413d132073}, - {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890}, - {0x933e37a534cbaae7, 0x8e91b962f7b6f15a}, - {0xb80dc58e81fe95a1, 0x723627bbb5a4adb1}, - {0xe61136f2227e3b09, 0xcec3b1aaa30dd91d}, - {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2}, - {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e}, - {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, - {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, - {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, - {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2}, + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0x9faacf3df73609b1, 0x77b191618c54e9ad}, + {0xc795830d75038c1d, 0xd59df5b9ef6a2418}, + {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e}, + {0x9becce62836ac577, 0x4ee367f9430aec33}, + {0xc2e801fb244576d5, 0x229c41f793cda740}, + {0xf3a20279ed56d48a, 0x6b43527578c11110}, + {0x9845418c345644d6, 0x830a13896b78aaaa}, + {0xbe5691ef416bd60c, 0x23cc986bc656d554}, + {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9}, + {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa}, + {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54}, + {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69}, + {0x91376c36d99995be, 0x23100809b9c21fa2}, + {0xb58547448ffffb2d, 0xabd40a0c2832a78b}, + {0xe2e69915b3fff9f9, 0x16c90c8f323f516d}, + {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4}, + {0xb1442798f49ffb4a, 0x99cd11cfdf41779d}, + {0xdd95317f31c7fa1d, 0x40405643d711d584}, + {0x8a7d3eef7f1cfc52, 0x482835ea666b2573}, + {0xad1c8eab5ee43b66, 0xda3243650005eed0}, + {0xd863b256369d4a40, 0x90bed43e40076a83}, + {0x873e4f75e2224e68, 0x5a7744a6e804a292}, + {0xa90de3535aaae202, 0x711515d0a205cb37}, + {0xd3515c2831559a83, 0x0d5a5b44ca873e04}, + {0x8412d9991ed58091, 0xe858790afe9486c3}, + {0xa5178fff668ae0b6, 0x626e974dbe39a873}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a}, + {0xa139029f6a239f72, 0x1c1fffc1ebc44e81}, + {0xc987434744ac874e, 0xa327ffb266b56221}, + {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9}, + {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa}, + {0xc4ce17b399107c22, 0xcb550fb4384d21d4}, + {0xf6019da07f549b2b, 0x7e2a53a146606a49}, + {0x99c102844f94e0fb, 0x2eda7444cbfc426e}, + {0xc0314325637a1939, 0xfa911155fefb5309}, + {0xf03d93eebc589f88, 0x793555ab7eba27cb}, + {0x96267c7535b763b5, 0x4bc1558b2f3458df}, + {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17}, + {0xea9c227723ee8bcb, 0x465e15a979c1cadd}, + {0x92a1958a7675175f, 0x0bfacd89ec191eca}, + {0xb749faed14125d36, 0xcef980ec671f667c}, + {0xe51c79a85916f484, 0x82b7e12780e7401b}, + {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811}, + {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16}, + {0xdfbdcece67006ac9, 0x67a791e093e1d49b}, + {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1}, + {0xaecc49914078536d, 0x58fae9f773886e19}, + {0xda7f5bf590966848, 0xaf39a475506a899f}, + {0x888f99797a5e012d, 0x6d8406c952429604}, + {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84}, + {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65}, + {0x855c3be0a17fcd26, 0x5cf2eea09a550680}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0xd0601d8efc57b08b, 0xf13b94daf124da27}, + {0x823c12795db6ce57, 0x76c53d08d6b70859}, + {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f}, + {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a}, + {0xfe5d54150b090b02, 0xd3f93b35435d7c4d}, + {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0}, + {0xc6b8e9b0709f109a, 0x359ab6419ca1091c}, + {0xf867241c8cc6d4c0, 0xc30163d203c94b63}, + {0x9b407691d7fc44f8, 0x79e0de63425dcf1e}, + {0xc21094364dfb5636, 0x985915fc12f542e5}, + {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e}, + {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43}, + {0xbd8430bd08277231, 0x50c6ff782a838354}, + {0xece53cec4a314ebd, 0xa4f8bf5635246429}, + {0x940f4613ae5ed136, 0x871b7795e136be9a}, + {0xb913179899f68584, 0x28e2557b59846e40}, + {0xe757dd7ec07426e5, 0x331aeada2fe589d0}, + {0x9096ea6f3848984f, 0x3ff0d2c85def7622}, + {0xb4bca50b065abe63, 0x0fed077a756b53aa}, + {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895}, + {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d}, + {0xb080392cc4349dec, 0xbd8d794d96aacfb4}, + {0xdca04777f541c567, 0xecf0d7a0fc5583a1}, + {0x89e42caaf9491b60, 0xf41686c49db57245}, + {0xac5d37d5b79b6239, 0x311c2875c522ced6}, + {0xd77485cb25823ac7, 0x7d633293366b828c}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xa8530886b54dbdeb, 0xd9f57f830283fdfd}, + {0xd267caa862a12d66, 0xd072df63c324fd7c}, + {0x8380dea93da4bc60, 0x4247cb9e59f71e6e}, + {0xa46116538d0deb78, 0x52d9be85f074e609}, + {0xcd795be870516656, 0x67902e276c921f8c}, + {0x806bd9714632dff6, 0x00ba1cd8a3db53b7}, + {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5}, + {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce}, + {0xfad2a4b13d1b5d6c, 0x796b805720085f82}, + {0x9cc3a6eec6311a63, 0xcbe3303674053bb1}, + {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d}, + {0xf4f1b4d515acb93b, 0xee92fb5515482d45}, + {0x991711052d8bf3c5, 0x751bdd152d4d1c4b}, + {0xbf5cd54678eef0b6, 0xd262d45a78a0635e}, + {0xef340a98172aace4, 0x86fb897116c87c35}, + {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1}, + {0xbae0a846d2195712, 0x8974836059cca10a}, + {0xe998d258869facd7, 0x2bd1a438703fc94c}, + {0x91ff83775423cc06, 0x7b6306a34627ddd0}, + {0xb67f6455292cbf08, 0x1a3bc84c17b1d543}, + {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94}, + {0x8e938662882af53e, 0x547eb47b7282ee9d}, + {0xb23867fb2a35b28d, 0xe99e619a4f23aa44}, + {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5}, + {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05}, + {0xae0b158b4738705e, 0x9624ab50b148d446}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7}, + {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d}, + {0xd47487cc8470652b, 0x7647c32000696720}, + {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074}, + {0xa5fb0a17c777cf09, 0xf468107100525891}, + {0xcf79cc9db955c2cc, 0x7182148d4066eeb5}, + {0x81ac1fe293d599bf, 0xc6f14cd848405531}, + {0xa21727db38cb002f, 0xb8ada00e5a506a7d}, + {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d}, + {0xfd442e4688bd304a, 0x908f4a166d1da664}, + {0x9e4a9cec15763e2e, 0x9a598e4e043287ff}, + {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe}, + {0xf7549530e188c128, 0xd12bee59e68ef47d}, + {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf}, + {0xc13a148e3032d6e7, 0xe36a52363c1faf02}, + {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2}, + {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba}, + {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8}, + {0xebdf661791d60f56, 0x111b495b3464ad22}, + {0x936b9fcebb25c995, 0xcab10dd900beec35}, + {0xb84687c269ef3bfb, 0x3d5d514f40eea743}, + {0xe65829b3046b0afa, 0x0cb4a5a3112a5113}, + {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac}, + {0xb3f4e093db73a093, 0x59ed216765690f57}, + {0xe0f218b8d25088b8, 0x306869c13ec3532d}, + {0x8c974f7383725573, 0x1e414218c73a13fc}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0xdbac6c247d62a583, 0xdf45f746b74abf3a}, + {0x894bc396ce5da772, 0x6b8bba8c328eb784}, + {0xab9eb47c81f5114f, 0x066ea92f3f326565}, + {0xd686619ba27255a2, 0xc80a537b0efefebe}, + {0x8613fd0145877585, 0xbd06742ce95f5f37}, + {0xa798fc4196e952e7, 0x2c48113823b73705}, + {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6}, + {0x82ef85133de648c4, 0x9a984d73dbe722fc}, + {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb}, + {0xcc963fee10b7d1b3, 0x318df905079926a9}, + {0xffbbcfe994e5c61f, 0xfdf17746497f7053}, + {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634}, + {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1}, + {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1}, + {0x9c1661a651213e2d, 0x06bea10ca65c084f}, + {0xc31bfa0fe5698db8, 0x486e494fcff30a63}, + {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb}, + {0x986ddb5c6b3a76b7, 0xf89629465a75e01d}, + {0xbe89523386091465, 0xf6bbb397f1135824}, + {0xee2ba6c0678b597f, 0x746aa07ded582e2d}, + {0x94db483840b717ef, 0xa8c2a44eb4571cdd}, + {0xba121a4650e4ddeb, 0x92f34d62616ce414}, + {0xe896a0d7e51e1566, 0x77b020baf9c81d18}, + {0x915e2486ef32cd60, 0x0ace1474dc1d122f}, + {0xb5b5ada8aaff80b8, 0x0d819992132456bb}, + {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3}, + {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf}, + {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c}, + {0xad4ab7112eb3929d, 0x86c16c98d2c953c7}, + {0xd89d64d57a607744, 0xe871c7bf077ba8b8}, + {0x87625f056c7c4a8b, 0x11471cd764ad4973}, + {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0}, + {0xd389b47879823479, 0x4aff1d108d4ec2c4}, + {0x843610cb4bf160cb, 0xcedf722a585139bb}, + {0xa54394fe1eedb8fe, 0xc2974eb4ee658829}, + {0xce947a3da6a9273e, 0x733d226229feea33}, + {0x811ccc668829b887, 0x0806357d5a3f5260}, + {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8}, + {0xc9bcff6034c13052, 0xfc89b393dd02f0b6}, + {0xfc2c3f3841f17c67, 0xbbac2078d443ace3}, + {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e}, + {0xc5029163f384a931, 0x0a9e795e65d4df12}, + {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6}, + {0x99ea0196163fa42e, 0x504bced1bf8e4e46}, + {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7}, + {0xf07da27a82c37088, 0x5d767327bb4e5a4d}, + {0x964e858c91ba2655, 0x3a6a07f8d510f870}, + {0xbbe226efb628afea, 0x890489f70a55368c}, + {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f}, + {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e}, + {0xb77ada0617e3bbcb, 0x09ce6ebb40173745}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0x8f57fa54c2a9eab6, 0x9fa946824a12232e}, + {0xb32df8e9f3546564, 0x47939822dc96abfa}, + {0xdff9772470297ebd, 0x59787e2b93bc56f8}, + {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b}, + {0xaefae51477a06b03, 0xede622920b6b23f2}, + {0xdab99e59958885c4, 0xe95fab368e45ecee}, + {0x88b402f7fd75539b, 0x11dbcb0218ebb415}, + {0xaae103b5fcd2a881, 0xd652bdc29f26a11a}, + {0xd59944a37c0752a2, 0x4be76d3346f04960}, + {0x857fcae62d8493a5, 0x6f70a4400c562ddc}, + {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953}, + {0xd097ad07a71f26b2, 0x7e2000a41346a7a8}, + {0x825ecc24c873782f, 0x8ed400668c0c28c9}, + {0xa2f67f2dfa90563b, 0x728900802f0f32fb}, + {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba}, + {0xfea126b7d78186bc, 0xe2f610c84987bfa9}, + {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca}, + {0xc6ede63fa05d3143, 0x91503d1c79720dbc}, + {0xf8a95fcf88747d94, 0x75a44c6397ce912b}, + {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb}, + {0xc24452da229b021b, 0xfbe85badce996169}, + {0xf2d56790ab41c2a2, 0xfae27299423fb9c4}, + {0x97c560ba6b0919a5, 0xdccd879fc967d41b}, + {0xbdb6b8e905cb600f, 0x5400e987bbc1c921}, + {0xed246723473e3813, 0x290123e9aab23b69}, + {0x9436c0760c86e30b, 0xf9a0b6720aaf6522}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0xe7958cb87392c2c2, 0xb60b1d1230b20e05}, + {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3}, + {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4}, + {0xe2280b6c20dd5232, 0x25c6da63c38de1b1}, + {0x8d590723948a535f, 0x579c487e5a38ad0f}, + {0xb0af48ec79ace837, 0x2d835a9df0c6d852}, + {0xdcdb1b2798182244, 0xf8e431456cf88e66}, + {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900}, + {0xac8b2d36eed2dac5, 0xe272467e3d222f40}, + {0xd7adf884aa879177, 0x5b0ed81dcc6abb10}, + {0x86ccbb52ea94baea, 0x98e947129fc2b4ea}, + {0xa87fea27a539e9a5, 0x3f2398d747b36225}, + {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae}, + {0x83a3eeeef9153e89, 0x1953cf68300424ad}, + {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8}, + {0xcdb02555653131b6, 0x3792f412cb06794e}, + {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1}, + {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5}, + {0xc8de047564d20a8b, 0xf245825a5a445276}, + {0xfb158592be068d2e, 0xeed6e2f0f0d56713}, + {0x9ced737bb6c4183d, 0x55464dd69685606c}, + {0xc428d05aa4751e4c, 0xaa97e14c3c26b887}, + {0xf53304714d9265df, 0xd53dd99f4b3066a9}, + {0x993fe2c6d07b7fab, 0xe546a8038efe402a}, + {0xbf8fdb78849a5f96, 0xde98520472bdd034}, + {0xef73d256a5c0f77c, 0x963e66858f6d4441}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xbb127c53b17ec159, 0x5560c018580d5d53}, + {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7}, + {0x9226712162ab070d, 0xcab3961304ca70e9}, + {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23}, + {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b}, + {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243}, + {0xb267ed1940f1c61c, 0x55f038b237591ed4}, + {0xdf01e85f912e37a3, 0x6b6c46dec52f6689}, + {0x8b61313bbabce2c6, 0x2323ac4b3b3da016}, + {0xae397d8aa96c1b77, 0xabec975e0a0d081b}, + {0xd9c7dced53c72255, 0x96e7bd358c904a22}, + {0x881cea14545c7575, 0x7e50d64177da2e55}, + {0xaa242499697392d2, 0xdde50bd1d5d0b9ea}, + {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865}, + {0x84ec3c97da624ab4, 0xbd5af13bef0b113f}, + {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f}, + {0xcfb11ead453994ba, 0x67de18eda5814af3}, + {0x81ceb32c4b43fcf4, 0x80eacf948770ced8}, + {0xa2425ff75e14fc31, 0xa1258379a94d028e}, + {0xcad2f7f5359a3b3e, 0x096ee45813a04331}, + {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd}, + {0x9e74d1b791e07e48, 0x775ea264cf55347e}, + {0xc612062576589dda, 0x95364afe032a819e}, + {0xf79687aed3eec551, 0x3a83ddbd83f52205}, + {0x9abe14cd44753b52, 0xc4926a9672793543}, + {0xc16d9a0095928a27, 0x75b7053c0f178294}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0x971da05074da7bee, 0xd3f6fc16ebca5e04}, + {0xbce5086492111aea, 0x88f4bb1ca6bcf585}, + {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6}, + {0x9392ee8e921d5d07, 0x3aff322e62439fd0}, + {0xb877aa3236a4b449, 0x09befeb9fad487c3}, + {0xe69594bec44de15b, 0x4c2ebe687989a9b4}, + {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11}, + {0xb424dc35095cd80f, 0x538484c19ef38c95}, + {0xe12e13424bb40e13, 0x2865a5f206b06fba}, + {0x8cbccc096f5088cb, 0xf93f87b7442e45d4}, + {0xafebff0bcb24aafe, 0xf78f69a51539d749}, + {0xdbe6fecebdedd5be, 0xb573440e5a884d1c}, + {0x89705f4136b4a597, 0x31680a88f8953031}, + {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e}, + {0xd6bf94d5e57a42bc, 0x3d32907604691b4d}, + {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110}, + {0xa7c5ac471b478423, 0x0fcf80dc33721d54}, + {0xd1b71758e219652b, 0xd3c36113404ea4a9}, + {0x83126e978d4fdf3b, 0x645a1cac083126ea}, + {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4}, + {0xcccccccccccccccc, 0xcccccccccccccccd}, + {0x8000000000000000, 0x0000000000000000}, + {0xa000000000000000, 0x0000000000000000}, + {0xc800000000000000, 0x0000000000000000}, + {0xfa00000000000000, 0x0000000000000000}, + {0x9c40000000000000, 0x0000000000000000}, + {0xc350000000000000, 0x0000000000000000}, + {0xf424000000000000, 0x0000000000000000}, + {0x9896800000000000, 0x0000000000000000}, + {0xbebc200000000000, 0x0000000000000000}, + {0xee6b280000000000, 0x0000000000000000}, + {0x9502f90000000000, 0x0000000000000000}, + {0xba43b74000000000, 0x0000000000000000}, + {0xe8d4a51000000000, 0x0000000000000000}, + {0x9184e72a00000000, 0x0000000000000000}, + {0xb5e620f480000000, 0x0000000000000000}, + {0xe35fa931a0000000, 0x0000000000000000}, + {0x8e1bc9bf04000000, 0x0000000000000000}, + {0xb1a2bc2ec5000000, 0x0000000000000000}, + {0xde0b6b3a76400000, 0x0000000000000000}, + {0x8ac7230489e80000, 0x0000000000000000}, + {0xad78ebc5ac620000, 0x0000000000000000}, + {0xd8d726b7177a8000, 0x0000000000000000}, + {0x878678326eac9000, 0x0000000000000000}, + {0xa968163f0a57b400, 0x0000000000000000}, + {0xd3c21bcecceda100, 0x0000000000000000}, + {0x84595161401484a0, 0x0000000000000000}, + {0xa56fa5b99019a5c8, 0x0000000000000000}, + {0xcecb8f27f4200f3a, 0x0000000000000000}, + {0x813f3978f8940984, 0x4000000000000000}, + {0xa18f07d736b90be5, 0x5000000000000000}, + {0xc9f2c9cd04674ede, 0xa400000000000000}, + {0xfc6f7c4045812296, 0x4d00000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xc5371912364ce305, 0x6c28000000000000}, + {0xf684df56c3e01bc6, 0xc732000000000000}, + {0x9a130b963a6c115c, 0x3c7f400000000000}, + {0xc097ce7bc90715b3, 0x4b9f100000000000}, + {0xf0bdc21abb48db20, 0x1e86d40000000000}, + {0x96769950b50d88f4, 0x1314448000000000}, + {0xbc143fa4e250eb31, 0x17d955a000000000}, + {0xeb194f8e1ae525fd, 0x5dcfab0800000000}, + {0x92efd1b8d0cf37be, 0x5aa1cae500000000}, + {0xb7abc627050305ad, 0xf14a3d9e40000000}, + {0xe596b7b0c643c719, 0x6d9ccd05d0000000}, + {0x8f7e32ce7bea5c6f, 0xe4820023a2000000}, + {0xb35dbf821ae4f38b, 0xdda2802c8a800000}, + {0xe0352f62a19e306e, 0xd50b2037ad200000}, + {0x8c213d9da502de45, 0x4526f422cc340000}, + {0xaf298d050e4395d6, 0x9670b12b7f410000}, + {0xdaf3f04651d47b4c, 0x3c0cdd765f114000}, + {0x88d8762bf324cd0f, 0xa5880a69fb6ac800}, + {0xab0e93b6efee0053, 0x8eea0d047a457a00}, + {0xd5d238a4abe98068, 0x72a4904598d6d880}, + {0x85a36366eb71f041, 0x47a6da2b7f864750}, + {0xa70c3c40a64e6c51, 0x999090b65f67d924}, + {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d}, + {0x82818f1281ed449f, 0xbff8f10e7a8921a5}, + {0xa321f2d7226895c7, 0xaff72d52192b6a0e}, + {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764491}, + {0xfee50b7025c36a08, 0x02f236d04753d5b5}, + {0x9f4f2726179a2245, 0x01d762422c946591}, + {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef6}, + {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb3}, + {0x9b934c3b330c8577, 0x63cc55f49f88eb30}, + {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fc}, + {0xf316271c7fc3908a, 0x8bef464e3945ef7b}, + {0x97edd871cfda3a56, 0x97758bf0e3cbb5ad}, + {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea318}, + {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bde}, + {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6b}, + {0xb975d6b6ee39e436, 0xb3e2fd538e122b45}, + {0xe7d34c64a9c85d44, 0x60dbbca87196b617}, + {0x90e40fbeea1d3a4a, 0xbc8955e946fe31ce}, + {0xb51d13aea4a488dd, 0x6babab6398bdbe42}, + {0xe264589a4dcdab14, 0xc696963c7eed2dd2}, + {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca3}, + {0xb0de65388cc8ada8, 0x3b25a55f43294bcc}, + {0xdd15fe86affad912, 0x49ef0eb713f39ebf}, + {0x8a2dbf142dfcc7ab, 0x6e3569326c784338}, + {0xacb92ed9397bf996, 0x49c2c37f07965405}, + {0xd7e77a8f87daf7fb, 0xdc33745ec97be907}, + {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a4}, + {0xa8acd7c0222311bc, 0xc40832ea0d68ce0d}, + {0xd2d80db02aabd62b, 0xf50a3fa490c30191}, + {0x83c7088e1aab65db, 0x792667c6da79e0fb}, + {0xa4b8cab1a1563f52, 0x577001b891185939}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, + {0x80b05e5ac60b6178, 0x544f8158315b05b5}, + {0xa0dc75f1778e39d6, 0x696361ae3db1c722}, + {0xc913936dd571c84c, 0x03bc3a19cd1e38ea}, + {0xfb5878494ace3a5f, 0x04ab48a04065c724}, + {0x9d174b2dcec0e47b, 0x62eb0d64283f9c77}, + {0xc45d1df942711d9a, 0x3ba5d0bd324f8395}, + {0xf5746577930d6500, 0xca8f44ec7ee3647a}, + {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecc}, + {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67f}, + {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101f}, + {0x95d04aee3b80ece5, 0xbba1f1d158724a13}, + {0xbb445da9ca61281f, 0x2a8a6e45ae8edc98}, + {0xea1575143cf97226, 0xf52d09d71a3293be}, + {0x924d692ca61be758, 0x593c2626705f9c57}, + {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836d}, + {0xe498f455c38b997a, 0x0b6dfb9c0f956448}, + {0x8edf98b59a373fec, 0x4724bd4189bd5ead}, + {0xb2977ee300c50fe7, 0x58edec91ec2cb658}, + {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ee}, + {0x8b865b215899f46c, 0xbd79e0d20082ee75}, + {0xae67f1e9aec07187, 0xecd8590680a3aa12}, + {0xda01ee641a708de9, 0xe80e6f4820cc9496}, + {0x884134fe908658b2, 0x3109058d147fdcde}, + {0xaa51823e34a7eede, 0xbd4b46f0599fd416}, + {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91b}, + {0x850fadc09923329e, 0x03e2cf6bc604ddb1}, + {0xa6539930bf6bff45, 0x84db8346b786151d}, + {0xcfe87f7cef46ff16, 0xe612641865679a64}, + {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07f}, + {0xa26da3999aef7749, 0xe3be5e330f38f09e}, + {0xcb090c8001ab551c, 0x5cadf5bfd3072cc6}, + {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f7}, + {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afb}, + {0xc646d63501a1511d, 0xb281e1fd541501b9}, + {0xf7d88bc24209a565, 0x1f225a7ca91a4227}, + {0x9ae757596946075f, 0x3375788de9b06959}, + {0xc1a12d2fc3978937, 0x0052d6b1641c83af}, + {0xf209787bb47d6b84, 0xc0678c5dbd23a49b}, + {0x9745eb4d50ce6332, 0xf840b7ba963646e1}, + {0xbd176620a501fbff, 0xb650e5a93bc3d899}, + {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebf}, + {0x93ba47c980e98cdf, 0xc66f336c36b10138}, + {0xb8a8d9bbe123f017, 0xb80b0047445d4185}, + {0xe6d3102ad96cec1d, 0xa60dc059157491e6}, + {0x9043ea1ac7e41392, 0x87c89837ad68db30}, + {0xb454e4a179dd1877, 0x29babe4598c311fc}, + {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67b}, + {0x8ce2529e2734bb1d, 0x1899e4a65f58660d}, + {0xb01ae745b101e9e4, 0x5ec05dcff72e7f90}, + {0xdc21a1171d42645d, 0x76707543f4fa1f74}, + {0x899504ae72497eba, 0x6a06494a791c53a9}, + {0xabfa45da0edbde69, 0x0487db9d17636893}, + {0xd6f8d7509292d603, 0x45a9d2845d3c42b7}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, + {0xa7f26836f282b732, 0x8e6cac7768d7141f}, + {0xd1ef0244af2364ff, 0x3207d795430cd927}, + {0x8335616aed761f1f, 0x7f44e6bd49e807b9}, + {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a7}, + {0xcd036837130890a1, 0x36dba887c37a8c10}, + {0x802221226be55a64, 0xc2494954da2c978a}, + {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6d}, + {0xc83553c5c8965d3d, 0x6f92829494e5acc8}, + {0xfa42a8b73abbf48c, 0xcb772339ba1f17fa}, + {0x9c69a97284b578d7, 0xff2a760414536efc}, + {0xc38413cf25e2d70d, 0xfef5138519684abb}, + {0xf46518c2ef5b8cd1, 0x7eb258665fc25d6a}, + {0x98bf2f79d5993802, 0xef2f773ffbd97a62}, + {0xbeeefb584aff8603, 0xaafb550ffacfd8fb}, + {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf39}, + {0x952ab45cfa97a0b2, 0xdd945a747bf26184}, + {0xba756174393d88df, 0x94f971119aeef9e5}, + {0xe912b9d1478ceb17, 0x7a37cd5601aab85e}, + {0x91abb422ccb812ee, 0xac62e055c10ab33b}, + {0xb616a12b7fe617aa, 0x577b986b314d600a}, + {0xe39c49765fdf9d94, 0xed5a7e85fda0b80c}, + {0x8e41ade9fbebc27d, 0x14588f13be847308}, + {0xb1d219647ae6b31c, 0x596eb2d8ae258fc9}, + {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bc}, + {0x8aec23d680043bee, 0x25de7bb9480d5855}, + {0xada72ccc20054ae9, 0xaf561aa79a10ae6b}, + {0xd910f7ff28069da4, 0x1b2ba1518094da05}, + {0x87aa9aff79042286, 0x90fb44d2f05d0843}, + {0xa99541bf57452b28, 0x353a1607ac744a54}, + {0xd3fa922f2d1675f2, 0x42889b8997915ce9}, + {0x847c9b5d7c2e09b7, 0x69956135febada12}, + {0xa59bc234db398c25, 0x43fab9837e699096}, + {0xcf02b2c21207ef2e, 0x94f967e45e03f4bc}, + {0x8161afb94b44f57d, 0x1d1be0eebac278f6}, + {0xa1ba1ba79e1632dc, 0x6462d92a69731733}, + {0xca28a291859bbf93, 0x7d7b8f7503cfdcff}, + {0xfcb2cb35e702af78, 0x5cda735244c3d43f}, + {0x9defbf01b061adab, 0x3a0888136afa64a8}, + {0xc56baec21c7a1916, 0x088aaa1845b8fdd1}, + {0xf6c69a72a3989f5b, 0x8aad549e57273d46}, + {0x9a3c2087a63f6399, 0x36ac54e2f678864c}, + {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7de}, + {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d6}, + {0x969eb7c47859e743, 0x9f644ae5a4b1b326}, + {0xbc4665b596706114, 0x873d5d9f0dde1fef}, + {0xeb57ff22fc0c7959, 0xa90cb506d155a7eb}, + {0x9316ff75dd87cbd8, 0x09a7f12442d588f3}, + {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30}, + {0xe5d3ef282a242e81, 0x8f1668c8a86da5fb}, + {0x8fa475791a569d10, 0xf96e017d694487bd}, + {0xb38d92d760ec4455, 0x37c981dcc395a9ad}, + {0xe070f78d3927556a, 0x85bbe253f47b1418}, + {0x8c469ab843b89562, 0x93956d7478ccec8f}, + {0xaf58416654a6babb, 0x387ac8d1970027b3}, + {0xdb2e51bfe9d0696a, 0x06997b05fcc0319f}, + {0x88fcf317f22241e2, 0x441fece3bdf81f04}, + {0xab3c2fddeeaad25a, 0xd527e81cad7626c4}, + {0xd60b3bd56a5586f1, 0x8a71e223d8d3b075}, + {0x85c7056562757456, 0xf6872d5667844e4a}, + {0xa738c6bebb12d16c, 0xb428f8ac016561dc}, + {0xd106f86e69d785c7, 0xe13336d701beba53}, + {0x82a45b450226b39c, 0xecc0024661173474}, + {0xa34d721642b06084, 0x27f002d7f95d0191}, + {0xcc20ce9bd35c78a5, 0x31ec038df7b441f5}, + {0xff290242c83396ce, 0x7e67047175a15272}, + {0x9f79a169bd203e41, 0x0f0062c6e984d387}, + {0xc75809c42c684dd1, 0x52c07b78a3e60869}, + {0xf92e0c3537826145, 0xa7709a56ccdf8a83}, + {0x9bbcc7a142b17ccb, 0x88a66076400bb692}, + {0xc2abf989935ddbfe, 0x6acff893d00ea436}, + {0xf356f7ebf83552fe, 0x0583f6b8c4124d44}, + {0x98165af37b2153de, 0xc3727a337a8b704b}, + {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5d}, + {0xeda2ee1c7064130c, 0x1162def06f79df74}, + {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba9}, + {0xb9a74a0637ce2ee1, 0x6d953e2bd7173693}, + {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0438}, + {0x910ab1d4db9914a0, 0x1d9c9892400a22a3}, + {0xb54d5e4a127f59c8, 0x2503beb6d00cab4c}, + {0xe2a0b5dc971f303a, 0x2e44ae64840fd61e}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, + {0xb10d8e1456105dad, 0x7425a83e872c5f48}, + {0xdd50f1996b947518, 0xd12f124e28f7771a}, + {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa70}, + {0xace73cbfdc0bfb7b, 0x636cc64d1001550c}, + {0xd8210befd30efa5a, 0x3c47f7e05401aa4f}, + {0x8714a775e3e95c78, 0x65acfaec34810a72}, + {0xa8d9d1535ce3b396, 0x7f1839a741a14d0e}, + {0xd31045a8341ca07c, 0x1ede48111209a051}, + {0x83ea2b892091e44d, 0x934aed0aab460433}, + {0xa4e4b66b68b65d60, 0xf81da84d56178540}, + {0xce1de40642e3f4b9, 0x36251260ab9d668f}, + {0x80d2ae83e9ce78f3, 0xc1d72b7c6b42601a}, + {0xa1075a24e4421730, 0xb24cf65b8612f820}, + {0xc94930ae1d529cfc, 0xdee033f26797b628}, + {0xfb9b7cd9a4a7443c, 0x169840ef017da3b2}, + {0x9d412e0806e88aa5, 0x8e1f289560ee864f}, + {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e3}, + {0xf5b5d7ec8acb58a2, 0xae10af696774b1dc}, + {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef2a}, + {0xbff610b0cc6edd3f, 0x17fd090a58d32af4}, + {0xeff394dcff8a948e, 0xddfc4b4cef07f5b1}, + {0x95f83d0a1fb69cd9, 0x4abdaf101564f98f}, + {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f2}, + {0xea53df5fd18d5513, 0x84c86189216dc5ee}, + {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb5}, + {0xb7118682dbb66a77, 0x3fbc8c33221dc2a2}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, + {0x8f05b1163ba6832d, 0x29cb4d87f2a7400f}, + {0xb2c71d5bca9023f8, 0x743e20e9ef511013}, + {0xdf78e4b2bd342cf6, 0x914da9246b255417}, + {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f}, + {0xae9672aba3d0c320, 0xa184ac2473b529b2}, + {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741f}, + {0x8865899617fb1871, 0x7e2fa67c7a658893}, + {0xaa7eebfb9df9de8d, 0xddbb901b98feeab8}, + {0xd51ea6fa85785631, 0x552a74227f3ea566}, + {0x8533285c936b35de, 0xd53a88958f872760}, + {0xa67ff273b8460356, 0x8a892abaf368f138}, + {0xd01fef10a657842c, 0x2d2b7569b0432d86}, + {0x8213f56a67f6b29b, 0x9c3b29620e29fc74}, + {0xa298f2c501f45f42, 0x8349f3ba91b47b90}, + {0xcb3f2f7642717713, 0x241c70a936219a74}, + {0xfe0efb53d30dd4d7, 0xed238cd383aa0111}, + {0x9ec95d1463e8a506, 0xf4363804324a40ab}, + {0xc67bb4597ce2ce48, 0xb143c6053edcd0d6}, + {0xf81aa16fdc1b81da, 0xdd94b7868e94050b}, + {0x9b10a4e5e9913128, 0xca7cf2b4191c8327}, + {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f1}, + {0xf24a01a73cf2dccf, 0xbc633b39673c8ced}, + {0x976e41088617ca01, 0xd5be0503e085d814}, + {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e19}, + {0xec9c459d51852ba2, 0xddf8e7d60ed1219f}, + {0x93e1ab8252f33b45, 0xcabb90e5c942b504}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, + {0xe7109bfba19c0c9d, 0x0cc512670a783ad5}, + {0x906a617d450187e2, 0x27fb2b80668b24c6}, + {0xb484f9dc9641e9da, 0xb1f9f660802dedf7}, + {0xe1a63853bbd26451, 0x5e7873f8a0396974}, + {0x8d07e33455637eb2, 0xdb0b487b6423e1e9}, + {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda63}, + {0xdc5c5301c56b75f7, 0x7641a140cc7810fc}, + {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9e}, + {0xac2820d9623bf429, 0x546345fa9fbdcd45}, + {0xd732290fbacaf133, 0xa97c177947ad4096}, + {0x867f59a9d4bed6c0, 0x49ed8eabcccc485e}, + {0xa81f301449ee8c70, 0x5c68f256bfff5a75}, + {0xd226fc195c6a2f8c, 0x73832eec6fff3112}, + {0x83585d8fd9c25db7, 0xc831fd53c5ff7eac}, + {0xa42e74f3d032f525, 0xba3e7ca8b77f5e56}, + {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35ec}, + {0x80444b5e7aa7cf85, 0x7980d163cf5b81b4}, + {0xa0555e361951c366, 0xd7e105bcc3326220}, + {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa8}, + {0xfa856334878fc150, 0xb14f98f6f0feb952}, + {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d4}, + {0xc3b8358109e84f07, 0x0a862f80ec4700c9}, + {0xf4a642e14c6262c8, 0xcd27bb612758c0fb}, + {0x98e7e9cccfbd7dbd, 0x8038d51cb897789d}, + {0xbf21e44003acdd2c, 0xe0470a63e6bd56c4}, + {0xeeea5d5004981478, 0x1858ccfce06cac75}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xbaa718e68396cffd, 0xd30560258f54e6bb}, + {0xe950df20247c83fd, 0x47c6b82ef32a206a}, + {0x91d28b7416cdd27e, 0x4cdc331d57fa5442}, + {0xb6472e511c81471d, 0xe0133fe4adf8e953}, + {0xe3d8f9e563a198e5, 0x58180fddd97723a7}, + {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7649}, + {0xb201833b35d63f73, 0x2cd2cc6551e513db}, + {0xde81e40a034bcf4f, 0xf8077f7ea65e58d2}, + {0x8b112e86420f6191, 0xfb04afaf27faf783}, + {0xadd57a27d29339f6, 0x79c5db9af1f9b564}, + {0xd94ad8b1c7380874, 0x18375281ae7822bd}, + {0x87cec76f1c830548, 0x8f2293910d0b15b6}, + {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb23}, + {0xd433179d9c8cb841, 0x5fa60692a46151ec}, + {0x849feec281d7f328, 0xdbc7c41ba6bcd334}, + {0xa5c7ea73224deff3, 0x12b9b522906c0801}, + {0xcf39e50feae16bef, 0xd768226b34870a01}, + {0x81842f29f2cce375, 0xe6a1158300d46641}, + {0xa1e53af46f801c53, 0x60495ae3c1097fd1}, + {0xca5e89b18b602368, 0x385bb19cb14bdfc5}, + {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, + {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, + {0xc5a05277621be293, 0xc7098b7305241886}, + {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8}, + {0x9a65406d44a5c903, 0x737f74f1dc043329}, + {0xc0fe908895cf3b44, 0x505f522e53053ff3}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0}, + {0x96c6e0eab509e64d, 0x5eca783430dc19f6}, + {0xbc789925624c5fe0, 0xb67d16413d132073}, + {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890}, + {0x933e37a534cbaae7, 0x8e91b962f7b6f15a}, + {0xb80dc58e81fe95a1, 0x723627bbb5a4adb1}, + {0xe61136f2227e3b09, 0xcec3b1aaa30dd91d}, + {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2}, + {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e}, + {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, + {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, + {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, + {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2}, #else - {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, - {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, - {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, - {0x86a8d39ef77164bc, 0xae5dff9c02033198}, - {0xd98ddaee19068c76, 0x3badd624dd9b0958}, - {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, - {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, - {0xe55990879ddcaabd, 0xcc420a6a101d0516}, - {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, - {0x95a8637627989aad, 0xdde7001379a44aa9}, - {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, - {0xc350000000000000, 0x0000000000000000}, - {0x9dc5ada82b70b59d, 0xf020000000000000}, - {0xfee50b7025c36a08, 0x02f236d04753d5b5}, - {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, - {0xa6539930bf6bff45, 0x84db8346b786151d}, - {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, - {0xd910f7ff28069da4, 0x1b2ba1518094da05}, - {0xaf58416654a6babb, 0x387ac8d1970027b3}, - {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, - {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, - {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, - {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, - {0xf13e34aabb430a15, 0x647726b9e7c68ff0} + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0xc350000000000000, 0x0000000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xfee50b7025c36a08, 0x02f236d04753d5b5}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, + {0xa6539930bf6bff45, 0x84db8346b786151d}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, + {0xd910f7ff28069da4, 0x1b2ba1518094da05}, + {0xaf58416654a6babb, 0x387ac8d1970027b3}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0} #endif }; @@ -1058,7 +1075,7 @@ template <> struct cache_accessor { int offset = k - kb; // Get base cache. - uint128_fallback base_cache = pow10_significands[cache_index]; + uint128 base_cache = pow10_significands[cache_index]; if (offset == 0) return base_cache; // Compute the required amount of bit-shift. @@ -1067,17 +1084,16 @@ template <> struct cache_accessor { // Try to recover the real cache. uint64_t pow5 = powers_of_5_64[offset]; - uint128_fallback recovered_cache = umul128(base_cache.high(), pow5); - uint128_fallback middle_low = umul128(base_cache.low(), pow5); + uint128 recovered_cache = umul128(base_cache.high(), pow5); + uint128 middle_low = umul128(base_cache.low(), pow5); recovered_cache += middle_low.high(); uint64_t high_to_middle = recovered_cache.high() << (64 - alpha); uint64_t middle_to_low = recovered_cache.low() << (64 - alpha); - recovered_cache = - uint128_fallback{(recovered_cache.low() >> alpha) | high_to_middle, - ((middle_low.low() >> alpha) | middle_to_low)}; + recovered_cache = uint128{(recovered_cache.low() >> alpha) | high_to_middle, + ((middle_low.low() >> alpha) | middle_to_low)}; FMT_ASSERT(recovered_cache.low() + 1 != 0, ""); return {recovered_cache.high(), recovered_cache.low() + 1}; #endif @@ -1138,7 +1154,7 @@ template <> struct cache_accessor { } }; -FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback { +FMT_FUNC auto get_cached_power(int k) noexcept -> uint128 { return cache_accessor::get_cached_power(k); } @@ -1463,10 +1479,10 @@ template struct span { }; template auto flockfile(F* f) -> decltype(_lock_file(f)) { - _lock_file(f); + return _lock_file(f); } template auto funlockfile(F* f) -> decltype(_unlock_file(f)) { - _unlock_file(f); + return _unlock_file(f); } #ifndef getc_unlocked @@ -1475,12 +1491,16 @@ template auto getc_unlocked(F* f) -> decltype(_fgetc_nolock(f)) { } #endif +#ifndef FMT_USE_FLOCKFILE +# define FMT_USE_FLOCKFILE 1 +#endif + template struct has_flockfile : std::false_type {}; template struct has_flockfile()))>> - : std::true_type {}; + : bool_constant {}; // A FILE wrapper. F is FILE defined as a template parameter to make system API // detection work. @@ -1678,6 +1698,9 @@ class file_print_buffer::value>> public: explicit file_print_buffer(F* f) : buffer(grow, size_t()), file_(f) { flockfile(f); +#ifdef __SANITIZE_THREAD__ + __tsan_acquire(f); +#endif file_.init_buffer(); auto buf = file_.get_write_buffer(); set(buf.data, buf.size); @@ -1685,7 +1708,10 @@ class file_print_buffer::value>> ~file_print_buffer() { file_.advance_write_buffer(size()); bool flush = file_.needs_flush(); - F* f = file_; // Make funlockfile depend on the template parameter F + F* f = file_; // Make funlockfile depend on the template parameter F. +#ifdef __SANITIZE_THREAD__ + __tsan_release(f); +#endif funlockfile(f); // for the system API detection to work. if (flush) fflush(file_); } diff --git a/third_party/fmt/format.h b/third_party/fmt/format.h index 4a65300..5044bef 100644 --- a/third_party/fmt/format.h +++ b/third_party/fmt/format.h @@ -1,7 +1,7 @@ /* Formatting library for C++ - Copyright (c) 2012 - present, Victor Zverovich + Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -47,14 +47,12 @@ #endif #ifndef FMT_MODULE +# include // uint32_t # include // malloc, free +# include // memcpy -# include // std::signbit -# include // std::byte -# include // uint32_t -# include // std::memcpy -# include // std::numeric_limits -# include // std::bad_alloc +# include // std::signbit +# include // std::numeric_limits # if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI) // Workaround for pre gcc 5 libstdc++. # include // std::allocator_traits @@ -122,14 +120,6 @@ # define FMT_NOINLINE #endif -#ifdef FMT_DEPRECATED -// Use the provided definition. -#elif FMT_HAS_CPP14_ATTRIBUTE(deprecated) -# define FMT_DEPRECATED [[deprecated]] -#else -# define FMT_DEPRECATED /* deprecated */ -#endif - // Detect constexpr std::string. #if !FMT_USE_CONSTEVAL # define FMT_USE_CONSTEXPR_STRING 0 @@ -224,7 +214,6 @@ namespace detail { inline auto clz(uint32_t x) -> int { FMT_ASSERT(x != 0, ""); - FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. unsigned long r = 0; _BitScanReverse(&r, x); return 31 ^ static_cast(r); @@ -233,7 +222,6 @@ inline auto clz(uint32_t x) -> int { inline auto clzll(uint64_t x) -> int { FMT_ASSERT(x != 0, ""); - FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. unsigned long r = 0; # ifdef _WIN64 _BitScanReverse64(&r, x); @@ -283,7 +271,7 @@ FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { #endif auto to = To(); // The cast suppresses a bogus -Wclass-memaccess on GCC. - std::memcpy(static_cast(&to), &from, sizeof(to)); + memcpy(static_cast(&to), &from, sizeof(to)); return to; } @@ -302,13 +290,13 @@ inline auto is_big_endian() -> bool { #endif } -class uint128_fallback { +class uint128 { private: uint64_t lo_, hi_; public: - constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} - constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} + constexpr uint128(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} + constexpr uint128(uint64_t value = 0) : lo_(value), hi_(0) {} constexpr auto high() const noexcept -> uint64_t { return hi_; } constexpr auto low() const noexcept -> uint64_t { return lo_; } @@ -318,92 +306,84 @@ class uint128_fallback { return static_cast(lo_); } - friend constexpr auto operator==(const uint128_fallback& lhs, - const uint128_fallback& rhs) -> bool { + friend constexpr auto operator==(const uint128& lhs, const uint128& rhs) + -> bool { return lhs.hi_ == rhs.hi_ && lhs.lo_ == rhs.lo_; } - friend constexpr auto operator!=(const uint128_fallback& lhs, - const uint128_fallback& rhs) -> bool { + friend constexpr auto operator!=(const uint128& lhs, const uint128& rhs) + -> bool { return !(lhs == rhs); } - friend constexpr auto operator>(const uint128_fallback& lhs, - const uint128_fallback& rhs) -> bool { + friend constexpr auto operator>(const uint128& lhs, const uint128& rhs) + -> bool { return lhs.hi_ != rhs.hi_ ? lhs.hi_ > rhs.hi_ : lhs.lo_ > rhs.lo_; } - friend constexpr auto operator|(const uint128_fallback& lhs, - const uint128_fallback& rhs) - -> uint128_fallback { + friend constexpr auto operator|(const uint128& lhs, const uint128& rhs) + -> uint128 { return {lhs.hi_ | rhs.hi_, lhs.lo_ | rhs.lo_}; } - friend constexpr auto operator&(const uint128_fallback& lhs, - const uint128_fallback& rhs) - -> uint128_fallback { + friend constexpr auto operator&(const uint128& lhs, const uint128& rhs) + -> uint128 { return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; } - friend constexpr auto operator~(const uint128_fallback& n) - -> uint128_fallback { - return {~n.hi_, ~n.lo_}; - } - friend FMT_CONSTEXPR auto operator+(const uint128_fallback& lhs, - const uint128_fallback& rhs) - -> uint128_fallback { - auto result = uint128_fallback(lhs); + friend FMT_CONSTEXPR auto operator+(const uint128& lhs, const uint128& rhs) + -> uint128 { + auto result = uint128(lhs); result += rhs; return result; } - friend FMT_CONSTEXPR auto operator*(const uint128_fallback& lhs, uint32_t rhs) - -> uint128_fallback { + friend FMT_CONSTEXPR auto operator*(const uint128& lhs, uint32_t rhs) + -> uint128 { FMT_ASSERT(lhs.hi_ == 0, ""); uint64_t hi = (lhs.lo_ >> 32) * rhs; uint64_t lo = (lhs.lo_ & ~uint32_t()) * rhs; uint64_t new_lo = (hi << 32) + lo; return {(hi >> 32) + (new_lo < lo ? 1 : 0), new_lo}; } - friend constexpr auto operator-(const uint128_fallback& lhs, uint64_t rhs) - -> uint128_fallback { + friend constexpr auto operator-(const uint128& lhs, uint64_t rhs) -> uint128 { return {lhs.hi_ - (lhs.lo_ < rhs ? 1 : 0), lhs.lo_ - rhs}; } - FMT_CONSTEXPR auto operator>>(int shift) const -> uint128_fallback { + FMT_CONSTEXPR auto operator>>(int shift) const -> uint128 { if (shift == 64) return {0, hi_}; - if (shift > 64) return uint128_fallback(0, hi_) >> (shift - 64); + if (shift > 64) return uint128(0, hi_) >> (shift - 64); return {hi_ >> shift, (hi_ << (64 - shift)) | (lo_ >> shift)}; } - FMT_CONSTEXPR auto operator<<(int shift) const -> uint128_fallback { + FMT_CONSTEXPR auto operator<<(int shift) const -> uint128 { if (shift == 64) return {lo_, 0}; - if (shift > 64) return uint128_fallback(lo_, 0) << (shift - 64); + if (shift > 64) return uint128(lo_, 0) << (shift - 64); return {hi_ << shift | (lo_ >> (64 - shift)), (lo_ << shift)}; } - FMT_CONSTEXPR auto operator>>=(int shift) -> uint128_fallback& { + FMT_CONSTEXPR auto operator>>=(int shift) -> uint128& { return *this = *this >> shift; } - FMT_CONSTEXPR void operator+=(uint128_fallback n) { + FMT_CONSTEXPR void operator+=(uint128 n) { uint64_t new_lo = lo_ + n.lo_; uint64_t new_hi = hi_ + n.hi_ + (new_lo < lo_ ? 1 : 0); FMT_ASSERT(new_hi >= hi_, ""); lo_ = new_lo; hi_ = new_hi; } - FMT_CONSTEXPR void operator&=(uint128_fallback n) { + FMT_CONSTEXPR void operator&=(uint128 n) { lo_ &= n.lo_; hi_ &= n.hi_; } - FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128_fallback& { + FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128& { if (is_constant_evaluated()) { lo_ += n; hi_ += (lo_ < n ? 1 : 0); return *this; } #if FMT_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) - unsigned long long carry; + ullong carry; lo_ = __builtin_addcll(lo_, n, 0, &carry); hi_ += carry; #elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) && !defined(__ibmxl__) - unsigned long long result; + ullong result; auto carry = __builtin_ia32_addcarryx_u64(0, lo_, n, &result); lo_ = result; hi_ += carry; -#elif defined(_MSC_VER) && defined(_M_X64) +#elif defined(_MSC_VER) && defined(_M_AMD64) auto carry = _addcarry_u64(0, lo_, n, &lo_); _addcarry_u64(carry, hi_, 0, &hi_); #else @@ -414,7 +394,7 @@ class uint128_fallback { } }; -using uint128_t = conditional_t; +using uint128_t = conditional_t; #ifdef UINTPTR_MAX using uintptr_t = ::uintptr_t; @@ -431,12 +411,12 @@ template constexpr auto num_bits() -> int { return std::numeric_limits::digits; } // std::numeric_limits::digits may return 0 for 128-bit ints. -template <> constexpr auto num_bits() -> int { return 128; } -template <> constexpr auto num_bits() -> int { return 128; } -template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } // A heterogeneous bit_cast used for converting 96-bit long double to uint128_t -// and 128-bit pointers to uint128_fallback. +// and 128-bit pointers to uint128. template sizeof(From))> inline auto bit_cast(const From& from) -> To { constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned short)); @@ -444,7 +424,7 @@ inline auto bit_cast(const From& from) -> To { unsigned short value[static_cast(size)]; } data = bit_cast(from); auto result = To(); - if (const_check(is_big_endian())) { + if (is_big_endian()) { for (int i = 0; i < size; ++i) result = (result << num_bits()) | data.value[i]; } else { @@ -493,8 +473,8 @@ template = 307 && !FMT_ICC_VERSION __attribute__((no_sanitize("undefined"))) #endif -FMT_CONSTEXPR20 inline auto -reserve(OutputIt it, size_t n) -> typename OutputIt::value_type* { +FMT_CONSTEXPR20 inline auto reserve(OutputIt it, size_t n) -> + typename OutputIt::value_type* { auto& c = get_container(it); size_t size = c.size(); c.resize(size + n); @@ -564,10 +544,15 @@ FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { if (is_constant_evaluated()) return fill_n(out, count, value); static_assert(sizeof(T) == 1, "sizeof(T) must be 1 to use char for initialization"); - std::memset(out, value, to_unsigned(count)); + memset(out, value, to_unsigned(count)); return out + count; } +template +FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { + return copy(s.begin(), s.end(), out); +} + template FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end, OutputIt out) -> OutputIt { @@ -690,13 +675,13 @@ FMT_CONSTEXPR inline auto display_width_of(uint32_t cp) noexcept -> size_t { } template struct is_integral : std::is_integral {}; -template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; template <> struct is_integral : std::true_type {}; template using is_signed = std::integral_constant::is_signed || - std::is_same::value>; + std::is_same::value>; template using is_integer = @@ -736,21 +721,17 @@ using fast_float_t = conditional_t; template using is_double_double = bool_constant::digits == 106>; -#ifndef FMT_USE_FULL_CACHE_DRAGONBOX -# define FMT_USE_FULL_CACHE_DRAGONBOX 0 -#endif +FMT_API auto allocate(size_t size) -> void*; // An allocator that uses malloc/free to allow removing dependency on the C++ -// standard libary runtime. std::decay is used for back_inserter to be found by +// standard library runtime. std::decay is used for back_inserter to be found by // ADL when applied to memory_buffer. template struct allocator : private std::decay { using value_type = T; auto allocate(size_t n) -> T* { FMT_ASSERT(n <= max_value() / sizeof(T), ""); - T* p = static_cast(malloc(n * sizeof(T))); - if (!p) FMT_THROW(std::bad_alloc()); - return p; + return static_cast(detail::allocate(n * sizeof(T))); } void deallocate(T* p, size_t) { free(p); } @@ -984,7 +965,7 @@ FMT_API void print(FILE*, string_view); namespace detail { template struct fixed_string { - FMT_CONSTEXPR20 fixed_string(const Char (&s)[N]) { + FMT_CONSTEXPR fixed_string(const Char (&s)[N]) { detail::copy(static_cast(s), s + N, data); } @@ -1031,12 +1012,12 @@ using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; (factor) * 100000, (factor) * 1000000, (factor) * 10000000, \ (factor) * 100000000, (factor) * 1000000000 -// Converts value in the range [0, 100) to a string. -// GCC generates slightly better code when value is pointer-size. -inline auto digits2(size_t value) -> const char* { +// Converts value in the range [0, 100) to a string. GCC generates a bit better +// code when value is pointer-size (https://www.godbolt.org/z/5fEPMT1cc). +inline auto digits2(size_t value) noexcept -> const char* { // Align data since unaligned access may be slower when crossing a // hardware-specific boundary. - alignas(2) static const char data[] = + alignas(2) static constexpr char data[] = "0001020304050607080910111213141516171819" "2021222324252627282930313233343536373839" "4041424344454647484950515253545556575859" @@ -1045,9 +1026,25 @@ inline auto digits2(size_t value) -> const char* { return &data[value * 2]; } +// Given i in [0, 100), let x be the first 7 digits after +// the decimal point of i / 100 in base 2, the first 2 bytes +// after digits2_i(x) is the string representation of i. +inline auto digits2_i(size_t value) noexcept -> const char* { + alignas(2) static constexpr char data[] = + "00010203 0405060707080910 1112" + "131414151617 18192021 222324 " + "25262728 2930313232333435 3637" + "383939404142 43444546 474849 " + "50515253 5455565757585960 6162" + "636464656667 68697071 727374 " + "75767778 7980818282838485 8687" + "888989909192 93949596 979899 "; + return &data[value * 2]; +} + template constexpr auto getsign(sign s) -> Char { - return static_cast(((' ' << 24) | ('+' << 16) | ('-' << 8)) >> - (static_cast(s) * 8)); + return static_cast(static_cast( + ((' ' << 24) | ('+' << 16) | ('-' << 8)) >> (static_cast(s) * 8))); } template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { @@ -1065,7 +1062,7 @@ template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { } } #if FMT_USE_INT128 -FMT_CONSTEXPR inline auto count_digits(uint128_opt n) -> int { +FMT_CONSTEXPR inline auto count_digits(native_uint128 n) -> int { return count_digits_fallback(n); } #endif @@ -1153,7 +1150,9 @@ FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int { template constexpr auto digits10() noexcept -> int { return std::numeric_limits::digits10; } -template <> constexpr auto digits10() noexcept -> int { return 38; } +template <> constexpr auto digits10() noexcept -> int { + return 38; +} template <> constexpr auto digits10() noexcept -> int { return 38; } template struct thousands_sep_result { @@ -1166,7 +1165,7 @@ FMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result; template inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { auto result = thousands_sep_impl(loc); - return {result.grouping, Char(result.thousands_sep)}; + return {std::move(result.grouping), Char(result.thousands_sep)}; } template <> inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { @@ -1213,6 +1212,16 @@ FMT_CONSTEXPR20 FMT_INLINE void write2digits(Char* out, size_t value) { *out = static_cast('0' + value % 10); } +template +FMT_INLINE void write2digits_i(Char* out, size_t value) { + if (std::is_same::value && !FMT_OPTIMIZE_SIZE) { + memcpy(out, digits2_i(value), 2); + return; + } + *out++ = static_cast(digits2_i(value)[0]); + *out = static_cast(digits2_i(value)[1]); +} + // Formats a decimal unsigned integer value writing to out pointing to a buffer // of specified size. The caller must ensure that the buffer is large enough. template @@ -1221,12 +1230,19 @@ FMT_CONSTEXPR20 auto do_format_decimal(Char* out, UInt value, int size) FMT_ASSERT(size >= count_digits(value), "invalid digit count"); unsigned n = to_unsigned(size); while (value >= 100) { - // Integer division is slow so do it for a group of two digits instead - // of for every digit. The idea comes from the talk by Alexandrescu - // "Three Optimization Tips for C++". See speed-test for a comparison. n -= 2; - write2digits(out + n, static_cast(value % 100)); - value /= 100; + if (!is_constant_evaluated() && sizeof(UInt) == 4) { + auto p = value * static_cast((1ull << 39) / 100 + 1); + write2digits_i(out + n, p >> (39 - 7) & ((1 << 7) - 1)); + value = static_cast(p >> 39) + + (static_cast(value >= (100u << 25)) << 25); + } else { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + write2digits(out + n, static_cast(value % 100)); + value /= 100; + } } if (value >= 10) { n -= 2; @@ -1311,7 +1327,13 @@ class utf8_to_utf16 { inline auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; -enum class to_utf8_error_policy { abort, replace }; +enum class to_utf8_error_policy { abort, replace, wtf }; + +inline void to_utf8_3bytes(buffer& buf, uint32_t cp) { + buf.push_back(static_cast(0xe0 | (cp >> 12))); + buf.push_back(static_cast(0x80 | ((cp & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (cp & 0x3f))); +} // A converter from UTF-16/UTF-32 (host endian) to UTF-8. template class to_utf8 { @@ -1353,8 +1375,13 @@ template class to_utf8 { // Handle a surrogate pair. ++p; if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { - if (policy == to_utf8_error_policy::abort) return false; - buf.append(string_view("\xEF\xBF\xBD")); + switch (policy) { + case to_utf8_error_policy::abort: return false; + case to_utf8_error_policy::replace: + buf.append(string_view("\xEF\xBF\xBD")); + break; + case to_utf8_error_policy::wtf: to_utf8_3bytes(buf, c); break; + } --p; continue; } @@ -1366,9 +1393,7 @@ template class to_utf8 { buf.push_back(static_cast(0xc0 | (c >> 6))); buf.push_back(static_cast(0x80 | (c & 0x3f))); } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { - buf.push_back(static_cast(0xe0 | (c >> 12))); - buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - buf.push_back(static_cast(0x80 | (c & 0x3f))); + to_utf8_3bytes(buf, c); } else if (c >= 0x10000 && c <= 0x10ffff) { buf.push_back(static_cast(0xf0 | (c >> 18))); buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); @@ -1383,11 +1408,11 @@ template class to_utf8 { }; // Computes 128-bit result of multiplication of two 64-bit unsigned integers. -FMT_INLINE auto umul128(uint64_t x, uint64_t y) noexcept -> uint128_fallback { +FMT_INLINE auto umul128(uint64_t x, uint64_t y) noexcept -> uint128 { #if FMT_USE_INT128 - auto p = static_cast(x) * static_cast(y); + auto p = static_cast(x) * static_cast(y); return {static_cast(p >> 64), static_cast(p)}; -#elif defined(_MSC_VER) && defined(_M_X64) +#elif defined(_MSC_VER) && defined(_M_AMD64) auto hi = uint64_t(); auto lo = _umul128(x, y, &hi); return {hi, lo}; @@ -1428,9 +1453,9 @@ inline auto floor_log2_pow10(int e) noexcept -> int { // Computes upper 64 bits of multiplication of two 64-bit unsigned integers. inline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t { #if FMT_USE_INT128 - auto p = static_cast(x) * static_cast(y); + auto p = static_cast(x) * static_cast(y); return static_cast(p >> 64); -#elif defined(_MSC_VER) && defined(_M_X64) +#elif defined(_MSC_VER) && defined(_M_AMD64) return __umulh(x, y); #else return umul128(x, y).high(); @@ -1439,40 +1464,39 @@ inline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t { // Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. -inline auto umul192_upper128(uint64_t x, uint128_fallback y) noexcept - -> uint128_fallback { - uint128_fallback r = umul128(x, y.high()); +inline auto umul192_upper128(uint64_t x, uint128 y) noexcept -> uint128 { + uint128 r = umul128(x, y.high()); r += umul128_upper64(x, y.low()); return r; } -FMT_API auto get_cached_power(int k) noexcept -> uint128_fallback; +FMT_API auto get_cached_power(int k) noexcept -> uint128; // Type-specific information that Dragonbox uses. template struct float_info; template <> struct float_info { using carrier_uint = uint32_t; - static const int exponent_bits = 8; - static const int kappa = 1; - static const int big_divisor = 100; - static const int small_divisor = 10; - static const int min_k = -31; - static const int max_k = 46; - static const int shorter_interval_tie_lower_threshold = -35; - static const int shorter_interval_tie_upper_threshold = -35; + static constexpr int exponent_bits = 8; + static constexpr int kappa = 1; + static constexpr int big_divisor = 100; + static constexpr int small_divisor = 10; + static constexpr int min_k = -31; + enum { max_k = 46 }; + static constexpr int shorter_interval_tie_lower_threshold = -35; + static constexpr int shorter_interval_tie_upper_threshold = -35; }; template <> struct float_info { using carrier_uint = uint64_t; - static const int exponent_bits = 11; - static const int kappa = 2; - static const int big_divisor = 1000; - static const int small_divisor = 100; - static const int min_k = -292; - static const int max_k = 341; - static const int shorter_interval_tie_lower_threshold = -77; - static const int shorter_interval_tie_upper_threshold = -77; + static constexpr int exponent_bits = 11; + static constexpr int kappa = 2; + static constexpr int big_divisor = 1000; + static constexpr int small_divisor = 100; + static constexpr int min_k = -292; + enum { max_k = 341 }; + static constexpr int shorter_interval_tie_lower_threshold = -77; + static constexpr int shorter_interval_tie_upper_threshold = -77; }; // An 80- or 128-bit floating point number. @@ -1608,7 +1632,7 @@ template struct basic_fp { } }; -using fp = basic_fp; +using fp = basic_fp; // Normalizes the value converted from double and multiplied by (1 << SHIFT). template @@ -1739,7 +1763,7 @@ FMT_API auto is_printable(uint32_t cp) -> bool; inline auto needs_escape(uint32_t cp) -> bool { if (cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\') return true; - if (const_check(FMT_OPTIMIZE_SIZE > 1)) return false; + if FMT_CONSTEXPR20 (FMT_OPTIMIZE_SIZE > 1) return false; return !is_printable(cp); } @@ -1754,7 +1778,7 @@ auto find_escape(const Char* begin, const Char* end) -> find_escape_result { for (; begin != end; ++begin) { uint32_t cp = static_cast>(*begin); - if (const_check(sizeof(Char) == 1) && cp >= 0x80) continue; + if (sizeof(Char) == 1 && cp >= 0x80) continue; if (needs_escape(cp)) return {begin, begin + 1, cp}; } return {begin, nullptr, 0}; @@ -1762,7 +1786,7 @@ auto find_escape(const Char* begin, const Char* end) inline auto find_escape(const char* begin, const char* end) -> find_escape_result { - if (const_check(!use_utf8)) return find_escape(begin, end); + if FMT_CONSTEXPR20 (!use_utf8) return find_escape(begin, end); auto result = find_escape_result{end, nullptr, 0}; for_each_codepoint(string_view(begin, to_unsigned(end - begin)), [&](uint32_t cp, string_view sv) { @@ -1890,7 +1914,7 @@ template class digit_grouping { explicit digit_grouping(locale_ref loc, bool localized = true) { if (!localized) return; auto sep = thousands_sep(loc); - grouping_ = sep.grouping; + grouping_ = std::move(sep.grouping); if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); } digit_grouping(std::string grouping, std::basic_string sep) @@ -2335,9 +2359,8 @@ FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, ++begin; } break; - } else if (p == begin) { - break; } + if (p == begin) break; p = begin; } specs.set_align(alignment); @@ -2534,7 +2557,7 @@ FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f, auto grouping = Grouping(loc, specs.localized()); size += grouping.count_separators(exp); return write_padded( - out, specs, to_unsigned(size), [&](iterator it) { + out, specs, static_cast(size), [&](iterator it) { if (s != sign::none) *it++ = detail::getsign(s); it = write_significand(it, f.significand, significand_size, exp, decimal_point, grouping); @@ -2550,7 +2573,7 @@ FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f, bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt(); size += 1 + (pointy ? 1 : 0) + num_zeros; return write_padded( - out, specs, to_unsigned(size), [&](iterator it) { + out, specs, static_cast(size), [&](iterator it) { if (s != sign::none) *it++ = detail::getsign(s); *it++ = Char('0'); if (!pointy) return it; @@ -2594,7 +2617,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, *it++ = Char(exp_char); return write_exponent(exp, it); }; - auto usize = to_unsigned(size); + size_t usize = static_cast(size); return specs.width > 0 ? write_padded(out, specs, usize, write) : base_iterator(out, write(reserve(out, usize))); @@ -3138,8 +3161,9 @@ constexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t { // It is equal to ceil(2^31 + 2^32/10^(k + 1)). // These are stored in a string literal because we cannot have static arrays // in constexpr functions and non-static ones are poorly optimized. - return U"\x9999999a\x828f5c29\x80418938\x80068db9\x8000a7c6\x800010c7" - U"\x800001ae\x8000002b"[index]; + return uint32_t(u"\x9999\x828f\x8041\x8006\x8000\x8000\x8000\x8000"[index]) + << 16u | + uint32_t(u"\x999a\x5c29\x8938\x8db9\xa7c6\x10c7\x01ae\x002b"[index]); } template @@ -3544,8 +3568,8 @@ FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { memcpy(ptr, prefix, 2); ptr += 2; } else { - *ptr++ = prefix[0]; - *ptr++ = prefix[1]; + *ptr++ = static_cast(prefix[0]); + *ptr++ = static_cast(prefix[1]); } if (abs_exponent >= 100) { *ptr++ = static_cast('0' + abs_exponent / 100); @@ -3697,12 +3721,12 @@ template struct arg_formatter { struct dynamic_spec_getter { template ::value)> - FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { - return is_negative(value) ? ~0ull : static_cast(value); + FMT_CONSTEXPR auto operator()(T value) -> ullong { + return is_negative(value) ? ~0ull : static_cast(value); } template ::value)> - FMT_CONSTEXPR auto operator()(T) -> unsigned long long { + FMT_CONSTEXPR auto operator()(T) -> ullong { report_error("width/precision is not integer"); return 0; } @@ -3716,7 +3740,7 @@ FMT_CONSTEXPR void handle_dynamic_spec( auto arg = kind == arg_id_kind::index ? ctx.arg(ref.index) : ctx.arg(ref.name); if (!arg) report_error("argument not found"); - unsigned long long result = arg.visit(dynamic_spec_getter()); + ullong result = arg.visit(dynamic_spec_getter()); if (result > to_unsigned(max_value())) report_error("width/precision is out of range"); value = static_cast(result); @@ -3751,7 +3775,7 @@ struct udl_arg { template struct udl_arg { const Char* str; - template auto operator=(T&& value) const -> named_arg { + template auto operator=(T&& value) const -> named_arg { return {str, std::forward(value)}; } }; @@ -3864,7 +3888,7 @@ template class generic_context { constexpr auto out() const -> iterator { return out_; } - void advance_to(iterator it) { + FMT_CONSTEXPR void advance_to(iterator it) { if (!detail::is_back_insert_iterator()) out_ = it; } @@ -3906,8 +3930,8 @@ template class format_facet : public Locale::facet { explicit format_facet(string_view sep = "", std::string grouping = "\3", std::string decimal_point = ".") : separator_(sep.data(), sep.size()), - grouping_(grouping), - decimal_point_(decimal_point) {} + grouping_(std::move(grouping)), + decimal_point_(std::move(decimal_point)) {} auto put(appender out, loc_value val, const format_specs& specs) const -> bool { @@ -3943,12 +3967,6 @@ template class formatter, Char> : public formatter, Char> {}; -template -struct formatter, Char> : formatter {}; -template -struct formatter, Char> - : formatter {}; - template struct formatter : detail::native_formatter underlying_t { } } // namespace enums -#ifdef __cpp_lib_byte -template -struct formatter : formatter { - static auto format_as(std::byte b) -> unsigned char { - return static_cast(b); - } - template - auto format(std::byte b, Context& ctx) const -> decltype(ctx.out()) { - return formatter::format(format_as(b), ctx); - } -}; -#endif - struct bytes { string_view data; @@ -4142,6 +4147,14 @@ template struct nested_formatter { inline namespace literals { #if FMT_USE_NONTYPE_TEMPLATE_ARGS +/** + * User-defined literal equivalent of `fmt::arg`, but with compile-time checks. + * + * **Example**: + * + * using namespace fmt::literals; + * fmt::print("The answer is {answer}.", "answer"_a=42); + */ template constexpr auto operator""_a() { using char_t = remove_cvref_t; return detail::udl_arg(); @@ -4166,7 +4179,7 @@ class format_int { private: // Buffer should be large enough to hold all digits (digits10 + 1), // a sign and a null character. - enum { buffer_size = std::numeric_limits::digits10 + 3 }; + enum { buffer_size = std::numeric_limits::digits10 + 3 }; mutable char buffer_[buffer_size]; char* str_; @@ -4196,7 +4209,7 @@ class format_int { : str_(format_unsigned(value)) {} FMT_CONSTEXPR20 explicit format_int(unsigned long value) : str_(format_unsigned(value)) {} - FMT_CONSTEXPR20 explicit format_int(unsigned long long value) + FMT_CONSTEXPR20 explicit format_int(ullong value) : str_(format_unsigned(value)) {} /// Returns the number of characters written to the output buffer. @@ -4248,7 +4261,11 @@ class format_int { * // A compile-time error because 'd' is an invalid specifier for strings. * std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); */ -#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string) +#if FMT_USE_CONSTEVAL +# define FMT_STRING(s) s +#else +# define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string) +#endif // FMT_USE_CONSTEVAL FMT_API auto vsystem_error(int error_code, string_view fmt, format_args args) -> std::system_error; From 84ed641979147cabfa2222bc6faec67527a6fc44 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 19 Oct 2026 22:35:11 +0200 Subject: [PATCH 05/13] bench: refresh README / BENCHMARKS.md numbers; add per-backend matrix script Re-measured on the same release CPython 3.14.3 after the fmt 12.2.0 / fast_float v8.3.2 bump (bench_ns_per_op for the README table, bench_backend_matrix for BENCHMARKS.md). repr() is ~7% faster across corpora from fmt's Dragonbox full cache; f"{x:.4f}" and float(s) are unchanged within noise (an A/B against the v8.2.5 headers on this machine gave identical parse timings). bench/bench_backend_matrix.py is the script that produces the BENCHMARKS.md tables, so they are reproducible with `FLOATIUM_AUTOPATCH=0 python -m bench.bench_backend_matrix` on an all-backends build. Co-Authored-By: Claude Fable 5.1 --- BENCHMARKS.md | 38 +++++++++-------- README.md | 32 +++++++------- bench/bench_backend_matrix.py | 79 +++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 34 deletions(-) create mode 100644 bench/bench_backend_matrix.py diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 9fa8dee..be9e1e2 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -7,13 +7,15 @@ backend (`fast_float`, `wuffs`, `stock`). ## Methodology -- **floatium:** 0.14.2, release build, all backends compiled in. +- **floatium:** 0.14.5 + branch `py315-fixes-revendor` (fmt 12.2.0, + fast_float v8.3.2), release build, all backends compiled in. - **CPython:** 3.14.3. -- **Date:** 2026-05-16. +- **Date:** 2026-10-19. +- **Script:** `FLOATIUM_AUTOPATCH=0 python -m bench.bench_backend_matrix`. - **Corpora:** the five canonical corpora from `bench/corpora.py` — `random_uniform`, `random_bits`, `financial`, `scientific`, `integer_valued`; 2000 values each. -- **Timing:** mirrors `bench/bench_ns_per_op.py` — 9 outer samples per +- **Timing:** same as `bench/bench_ns_per_op.py` — 9 outer samples per cell, each timing 5 inner loops over the whole corpus; the reported figure is the median of the fastest third (3 of 9), robust to background jitter without picking the cold-cache outlier. @@ -32,16 +34,16 @@ values above 1.00x are faster than stock. | Corpus | Operation | Stock (ns) | fmt_opt | fmt | ryu_opt | fmt_opt speedup | fmt speedup | ryu_opt speedup | |---|---|---:|---:|---:|---:|---:|---:|---:| -| random_uniform | `repr(x)` | 285.3 | 97.9 | 98.3 | 96.4 | 2.92x | 2.90x | 2.96x | -| random_uniform | `f"{x:.4f}"` | 119.2 | 103.4 | 103.5 | 104.8 | 1.15x | 1.15x | 1.14x | -| random_bits | `repr(x)` | 819.6 | 135.5 | 135.2 | 146.1 | 6.05x | 6.06x | 5.61x | -| random_bits | `f"{x:.4f}"` | 1,956.3 | 193.9 | 5,549.3 | 212.2 | 10.09x | 0.35x | 9.22x | -| financial | `repr(x)` | 172.0 | 80.7 | 79.6 | 101.6 | 2.13x | 2.16x | 1.69x | -| financial | `f"{x:.4f}"` | 144.5 | 100.5 | 100.1 | 115.1 | 1.44x | 1.44x | 1.25x | -| scientific | `repr(x)` | 639.8 | 133.6 | 132.7 | 141.0 | 4.79x | 4.82x | 4.54x | -| scientific | `f"{x:.4f}"` | 1,078.6 | 158.1 | 2,972.4 | 170.7 | 6.82x | 0.36x | 6.32x | -| integer_valued | `repr(x)` | 143.1 | 88.7 | 88.2 | 80.7 | 1.61x | 1.62x | 1.77x | -| integer_valued | `f"{x:.4f}"` | 168.0 | 104.8 | 104.5 | 107.6 | 1.60x | 1.61x | 1.56x | +| random_uniform | `repr(x)` | 284.7 | 92.2 | 91.6 | 95.4 | 3.09x | 3.11x | 2.98x | +| random_uniform | `f"{x:.4f}"` | 119.9 | 99.9 | 100.7 | 105.6 | 1.20x | 1.19x | 1.14x | +| random_bits | `repr(x)` | 821.6 | 130.1 | 129.9 | 142.4 | 6.31x | 6.32x | 5.77x | +| random_bits | `f"{x:.4f}"` | 1,933.9 | 196.8 | 5,532.8 | 215.1 | 9.83x | 0.35x | 8.99x | +| financial | `repr(x)` | 171.0 | 75.0 | 73.8 | 101.2 | 2.28x | 2.32x | 1.69x | +| financial | `f"{x:.4f}"` | 144.7 | 96.5 | 98.1 | 114.4 | 1.50x | 1.47x | 1.27x | +| scientific | `repr(x)` | 638.4 | 127.2 | 127.3 | 138.5 | 5.02x | 5.02x | 4.61x | +| scientific | `f"{x:.4f}"` | 1,078.7 | 161.4 | 2,969.0 | 173.4 | 6.68x | 0.36x | 6.22x | +| integer_valued | `repr(x)` | 142.8 | 84.1 | 83.3 | 78.8 | 1.70x | 1.71x | 1.81x | +| integer_valued | `f"{x:.4f}"` | 167.9 | 102.2 | 101.7 | 107.4 | 1.64x | 1.65x | 1.56x | ## Parse backend @@ -50,8 +52,8 @@ values above 1.00x are faster than stock. | Corpus | Operation | Stock (ns) | fast_float | wuffs | stock | fast_float speedup | wuffs speedup | stock speedup | |---|---|---:|---:|---:|---:|---:|---:|---:| -| random_uniform | `float(s)` | 121.4 | 43.9 | 55.6 | 106.3 | 2.76x | 2.18x | 1.14x | -| random_bits | `float(s)` | 277.6 | 59.1 | 293.4 | 208.7 | 4.70x | 0.95x | 1.33x | -| financial | `float(s)` | 36.8 | 37.0 | 39.8 | 78.0 | 0.99x | 0.92x | 0.47x | -| scientific | `float(s)` | 213.7 | 57.4 | 66.5 | 183.3 | 3.72x | 3.21x | 1.17x | -| integer_valued | `float(s)` | 42.8 | 42.0 | 46.6 | 64.1 | 1.02x | 0.92x | 0.67x | +| random_uniform | `float(s)` | 125.6 | 46.0 | 57.0 | 108.5 | 2.73x | 2.20x | 1.16x | +| random_bits | `float(s)` | 276.3 | 66.8 | 300.9 | 210.8 | 4.14x | 0.92x | 1.31x | +| financial | `float(s)` | 36.8 | 37.6 | 41.9 | 78.5 | 0.98x | 0.88x | 0.47x | +| scientific | `float(s)` | 213.6 | 63.8 | 74.1 | 185.4 | 3.35x | 2.88x | 1.15x | +| integer_valued | `float(s)` | 43.2 | 44.0 | 47.5 | 69.1 | 0.98x | 0.91x | 0.63x | diff --git a/README.md b/README.md index 3342e36..ac20f89 100644 --- a/README.md +++ b/README.md @@ -56,25 +56,25 @@ lower is better) using floatium's default `fmt_opt` format backend + | Corpus | Operation | Stock (ns) | floatium (ns) | Speedup | |-----------------|-----------------|-----------:|--------------:|--------:| -| random_uniform | `repr(x)` | 284 | 96 | 2.95× | -| random_uniform | `f"{x:.4f}"` | 119 | 103 | 1.16× | -| random_uniform | `float(s)` | 121 | 44 | 2.79× | -| random_bits | `repr(x)` | 820 | 134 | 6.11× | -| random_bits | `f"{x:.4f}"` | 1,933 | 196 | 9.86× | -| random_bits | `float(s)` | 275 | 61 | 4.52× | -| financial | `repr(x)` | 171 | 80 | 2.14× | -| financial | `f"{x:.4f}"` | 145 | 101 | 1.43× | -| financial | `float(s)` | 37 | 36 | 1.01× | -| scientific | `repr(x)` | 640 | 135 | 4.74× | -| scientific | `f"{x:.4f}"` | 1,081 | 161 | 6.71× | -| scientific | `float(s)` | 212 | 58 | 3.64× | -| integer_valued | `repr(x)` | 143 | 88 | 1.62× | -| integer_valued | `f"{x:.4f}"` | 169 | 106 | 1.60× | -| integer_valued | `float(s)` | 43 | 42 | 1.02× | +| random_uniform | `repr(x)` | 285 | 89 | 3.19× | +| random_uniform | `f"{x:.4f}"` | 119 | 99 | 1.20× | +| random_uniform | `float(s)` | 121 | 47 | 2.61× | +| random_bits | `repr(x)` | 818 | 129 | 6.33× | +| random_bits | `f"{x:.4f}"` | 1,935 | 193 | 10.01× | +| random_bits | `float(s)` | 276 | 67 | 4.12× | +| financial | `repr(x)` | 170 | 73 | 2.33× | +| financial | `f"{x:.4f}"` | 143 | 97 | 1.48× | +| financial | `float(s)` | 37 | 38 | 0.97× | +| scientific | `repr(x)` | 636 | 127 | 5.01× | +| scientific | `f"{x:.4f}"` | 1,079 | 157 | 6.87× | +| scientific | `float(s)` | 211 | 63 | 3.36× | +| integer_valued | `repr(x)` | 143 | 83 | 1.73× | +| integer_valued | `f"{x:.4f}"` | 167 | 103 | 1.63× | +| integer_valued | `float(s)` | 43 | 43 | 0.99× | **Wins are largest on hard inputs.** `random_bits` and `scientific` corpora — values whose decimal expansion stresses dtoa's big-integer -path — see 6–10× on `repr` / `f"{x:.4f}"` and 3.6–4.5× on `float(s)`. +path — see 6–10× on `repr` / `f"{x:.4f}"` and 3.4–4.1× on `float(s)`. On `financial` and `integer_valued`, parse stays near 1× because the inputs are short, integer-valued strings where dtoa's fast path and fast_float's Eisel–Lemire path both finish in tens of nanoseconds — diff --git a/bench/bench_backend_matrix.py b/bench/bench_backend_matrix.py new file mode 100644 index 0000000..87ee6ad --- /dev/null +++ b/bench/bench_backend_matrix.py @@ -0,0 +1,79 @@ +"""Per-backend ns/op matrix — the tables in BENCHMARKS.md. + +Same kernels, corpora and timing method as bench_ns_per_op.py, but every +compiled-in format backend (repr / f"{x:.4f}") and parse backend +(float(s)) is measured in turn against the unpatched interpreter. +Needs a build with all backends compiled in: + + pip install . -C cmake.define.FLOATIUM_FORMAT_BACKEND=all \\ + -C cmake.define.FLOATIUM_PARSE_BACKEND=all + FLOATIUM_AUTOPATCH=0 python -m bench.bench_backend_matrix +""" + +from __future__ import annotations + +import argparse + +import floatium +from bench import corpora +from bench.bench_ns_per_op import FORMAT_OPERATIONS, PARSE_OPERATIONS, _time_ns_per_op + + +def _table(rows: dict, names: list[str]) -> None: + print("| Corpus | Operation | Stock (ns) | " + " | ".join(names) + " | " + + " | ".join(f"{n} speedup" for n in names) + " |") + print("|---|---|---:|" + "---:|" * (2 * len(names))) + for (corpus, op), r in rows.items(): + stock = r["Stock"] + print(f"| {corpus} | `{op}` | {stock:,.1f} | " + + " | ".join(f"{r[n]:,.1f}" for n in names) + " | " + + " | ".join(f"{stock / r[n]:.2f}x" for n in names) + " |") + + +def main() -> None: + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("--corpus-size", type=int, default=2000) + p.add_argument("--inner-loops", type=int, default=5) + p.add_argument("--outer-runs", type=int, default=9) + args = p.parse_args() + + def measure(kernel, values): + kernel(values) # warmup + return _time_ns_per_op(kernel, values, args.inner_loops, args.outer_runs) + + data = corpora.all_corpora(n=args.corpus_size) + floatium.uninstall() + str_data = {name: [repr(x) for x in values] for name, values in data.items()} + info = floatium.info() + format_backends = info["available_format_backends"].split(",") + parse_backends = info["available_parse_backends"].split(",") + + fmt_rows: dict = {} + parse_rows: dict = {} + for corpus, values in data.items(): + for op, kernel in FORMAT_OPERATIONS.items(): + fmt_rows[(corpus, op)] = {"Stock": measure(kernel, values)} + for op, kernel in PARSE_OPERATIONS.items(): + parse_rows[(corpus, op)] = {"Stock": measure(kernel, str_data[corpus])} + for backend in format_backends: + floatium.uninstall() + floatium.install(format_backend=backend) + for corpus, values in data.items(): + for op, kernel in FORMAT_OPERATIONS.items(): + fmt_rows[(corpus, op)][backend] = measure(kernel, values) + for backend in parse_backends: + floatium.uninstall() + floatium.install(parse_backend=backend) + for corpus, _values in data.items(): + for op, kernel in PARSE_OPERATIONS.items(): + parse_rows[(corpus, op)][backend] = measure(kernel, str_data[corpus]) + floatium.uninstall() + + print("## Format backends\n") + _table(fmt_rows, format_backends) + print("\n## Parse backend\n") + _table(parse_rows, parse_backends) + + +if __name__ == "__main__": + main() From 8ccaff65bfea77fba629b224ea6b5ca0735c3409 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 19 Oct 2026 22:43:55 +0200 Subject: [PATCH 06/13] =?UTF-8?q?floatium=20v0.15.0=20=E2=80=94=20CPython?= =?UTF-8?q?=203.15=20wheels,=20parity=20fixes,=20fmt=2012.2.0=20/=20fast?= =?UTF-8?q?=5Ffloat=20v8.3.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop cp313t wheels: cibuildwheel 4.x no longer builds 3.13 free-threaded CPython, and carrying a pinned 3.x step for one identifier is not worth it. 3.13 (GIL) wheels, 3.14t and 3.15t stay; 3.13t users build from the sdist. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/wheels.yml | 14 ++------------ BENCHMARKS.md | 4 ++-- floatium/__init__.py | 2 +- pyproject.toml | 9 ++++----- 4 files changed, 9 insertions(+), 20 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index c11eefe..d4d7012 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -47,18 +47,8 @@ jobs: # cibuildwheel 4.x: cp315/cp315t are in the default catalog # (3.15.0 final since 4.3.1) and free-threaded 3.14+ builds # need no `enable` token — the old cpython-freethreading enable - # was removed in 4.0.0 along with the 3.13t builds. - CIBW_BUILD_VERBOSITY: 1 - - - name: Build wheels (cp313t) - # cibuildwheel 4.x can no longer build 3.13 free-threaded wheels. - # Keep the last 3.x release around for that single identifier - # until 3.13t wheels are dropped; it still needs the (deprecated - # in 3.x) cpython-freethreading enable token. - uses: pypa/cibuildwheel@v3.4.1 - env: - CIBW_BUILD: "cp313t-*" - CIBW_ENABLE: cpython-freethreading + # was removed in 4.0.0 along with the 3.13t builds (which is + # why there are no cp313t wheels from 0.15.0 on). CIBW_BUILD_VERBOSITY: 1 - uses: actions/upload-artifact@v5 diff --git a/BENCHMARKS.md b/BENCHMARKS.md index be9e1e2..d2b6fca 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -7,8 +7,8 @@ backend (`fast_float`, `wuffs`, `stock`). ## Methodology -- **floatium:** 0.14.5 + branch `py315-fixes-revendor` (fmt 12.2.0, - fast_float v8.3.2), release build, all backends compiled in. +- **floatium:** 0.15.0 (fmt 12.2.0, fast_float v8.3.2), release build, + all backends compiled in. - **CPython:** 3.14.3. - **Date:** 2026-10-19. - **Script:** `FLOATIUM_AUTOPATCH=0 python -m bench.bench_backend_matrix`. diff --git a/floatium/__init__.py b/floatium/__init__.py index 879c668..7375082 100644 --- a/floatium/__init__.py +++ b/floatium/__init__.py @@ -34,7 +34,7 @@ "__version__", ] -__version__ = "0.14.5" +__version__ = "0.15.0" def install( diff --git a/pyproject.toml b/pyproject.toml index 2d055ef..8977c2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build" [project] name = "floatium" -version = "0.14.5" +version = "0.15.0" description = "Drop-in replacement for CPython float formatting/parsing, backed by {fmt} and fast_float. Demonstrator for a forthcoming CPython PEP." readme = "README.md" license = { text = "MIT" } @@ -51,10 +51,9 @@ FLOATIUM_PARSE_BACKEND = "fast_float" [tool.cibuildwheel] # CPython 3.13–3.15, incl. free-threaded 3.14t / 3.15t. cibuildwheel 4.x # builds cp315 / cp315t (3.15.0 final) by default and needs no `enable` -# flag for free-threaded 3.14+. cp313t is NOT in this list: cibuildwheel -# 4.0 dropped the experimental 3.13 free-threaded builds, so wheels.yml -# runs a second, pinned cibuildwheel 3.4.1 step with CIBW_BUILD=cp313t-* -# to keep shipping 3.13t wheels. +# flag for free-threaded 3.14+. No cp313t: cibuildwheel 4.0 dropped the +# experimental 3.13 free-threaded builds (floatium <= 0.14.5 shipped +# them via cibuildwheel 3.x); 3.13t users install from the sdist. build = "cp313-* cp314-* cp314t-* cp315-* cp315t-*" skip = "*-musllinux_i686 pp*" test-extras = ["test"] From 58894f5eee8f928bab355f77b6f89142b9eb00f7 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 19 Oct 2026 23:45:17 +0200 Subject: [PATCH 07/13] add zmij format backend: pure-C, one upstream library, the CPython candidate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vendors the C port of Żmij (vitaut/zmij a9d6363, MIT or BSL-1.0; zmij.c + zmij-c.h only) and adds a "zmij" format backend that serves every _Py_dg_dtoa mode from it: shortest via Żmij's verified algorithm, %e/%f precision via its correctly rounded scientific/fixed writers (bigint-backed above 18 digits). Unlike fmt_opt and ryu_opt this is one unmodified library plus one adapter, with no C++ anywhere — the shape a CPython proposal needs. The adapter (src/cpython_adapter/zmij_dtoa.c) is plain C11 against the exact _Py_dg_dtoa contract, using nothing from CPython but PyMem_*: it re-reads Żmij's printf-style text into digits + decpt and implements mode 3 with negative ndigits (round(x, -k)) with ties-to-even digit arithmetic on the exact integer part. Verified with 182k direct calls through ctypes against repr/format/round, plus tests/test_backends.py across 2800 values x 32 specs; suite passes on 3.14 and 3.15t debug. Also: CMAKE_C_STANDARD 11 (zmij.c uses static_assert), the all-backends CI job now runs on ubuntu/macos/windows so zmij.c is built with MSVC and Apple clang too, INTERNALS.md documents the zmij + wuffs pair as the CPython candidate, and README.vendor lists the three upstream requests for Żmij (asm -> __asm__ for -std=c11, a to-decimal C entry point, extern "C" guards). Co-Authored-By: Claude Fable 5.1 --- .github/workflows/ci.yml | 17 +- CMakeLists.txt | 18 +- INTERNALS.md | 42 +- README.md | 12 +- floatium/__init__.py | 2 +- src/backends/zmij_format.cc | 33 + src/cpython_adapter/zmij_dtoa.c | 297 +++ third_party/zmij/LICENSE | 21 + third_party/zmij/README.vendor | 50 + third_party/zmij/zmij-c.h | 219 +++ third_party/zmij/zmij.c | 3065 +++++++++++++++++++++++++++++++ tools/sync_zmij.sh | 97 + 12 files changed, 3854 insertions(+), 19 deletions(-) create mode 100644 src/backends/zmij_format.cc create mode 100644 src/cpython_adapter/zmij_dtoa.c create mode 100644 third_party/zmij/LICENSE create mode 100644 third_party/zmij/README.vendor create mode 100644 third_party/zmij/zmij-c.h create mode 100644 third_party/zmij/zmij.c create mode 100755 tools/sync_zmij.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76610d8..373794b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,12 +48,17 @@ jobs: shell: bash test-all-backends: - # Every optional backend (fmt, ryu_opt, wuffs, stock) compiled in, - # so tests/test_backends.py can check each one against stock. The - # wheels only ship the defaults; this job is what keeps the others - # honest. - name: test / all backends / py3.14 / ubuntu - runs-on: ubuntu-latest + # Every optional backend (fmt, ryu_opt, zmij, wuffs, stock) compiled + # in, so tests/test_backends.py can check each one against stock. + # The wheels only ship the defaults; this job is what keeps the + # others honest — and, on all three OSes, what proves the pure-C + # zmij.c / zmij_dtoa.c build with MSVC and Apple clang as well. + name: test / all backends / py3.14 / ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-14, windows-latest] steps: - uses: actions/checkout@v5 diff --git a/CMakeLists.txt b/CMakeLists.txt index e2fb411..190d823 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.20) project(floatium LANGUAGES C CXX) +set(CMAKE_C_STANDARD 11) # zmij.c: static_assert, stdbool +set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) @@ -11,6 +13,8 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # fmt (plain fmt for everything, for A/B measurement), # ryu_opt (pure-C: Ryu d2s for repr, d2exp for %e/%g, d2fixed # for %f — zero C++ in the format path), +# zmij (pure-C: Żmij for every mode — one unmodified +# upstream library; the CPython-proposal backend), # all. # Parse backend: fast_float (default), wuffs (pure-C Eisel-Lemire + HPD), # stock (libc strtod via floatium's wrapper), all. @@ -23,9 +27,9 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # # With "all", every backend is compiled in and the active one is chosen at # runtime via env vars FLOATIUM_FORMAT_BACKEND / FLOATIUM_PARSE_BACKEND. -set(FLOATIUM_FORMAT_BACKEND "fmt_opt" CACHE STRING "Format backend: fmt_opt, fmt, ryu_opt, all") +set(FLOATIUM_FORMAT_BACKEND "fmt_opt" CACHE STRING "Format backend: fmt_opt, fmt, ryu_opt, zmij, all") set(FLOATIUM_PARSE_BACKEND "fast_float" CACHE STRING "Parse backend: fast_float, wuffs, stock, all") -set_property(CACHE FLOATIUM_FORMAT_BACKEND PROPERTY STRINGS fmt_opt fmt ryu_opt all) +set_property(CACHE FLOATIUM_FORMAT_BACKEND PROPERTY STRINGS fmt_opt fmt ryu_opt zmij all) set_property(CACHE FLOATIUM_PARSE_BACKEND PROPERTY STRINGS fast_float wuffs stock all) # --- Python ----------------------------------------------------------------- @@ -66,6 +70,14 @@ if(FLOATIUM_FORMAT_BACKEND STREQUAL "ryu_opt" OR FLOATIUM_FORMAT_BACKEND STREQUA set(FLOATIUM_HAS_RYU_OPT_FORMAT 1) set(FLOATIUM_NEEDS_RYU_D2FIXED 1) endif() +if(FLOATIUM_FORMAT_BACKEND STREQUAL "zmij" OR FLOATIUM_FORMAT_BACKEND STREQUAL "all") + list(APPEND FLOATIUM_SRCS + src/backends/zmij_format.cc + src/cpython_adapter/zmij_dtoa.c + third_party/zmij/zmij.c + ) + set(FLOATIUM_HAS_ZMIJ_FORMAT 1) +endif() # Ryu d2fixed.c is shared between fmt_opt and ryu_opt — compile it once. if(FLOATIUM_NEEDS_RYU_D2FIXED) list(APPEND FLOATIUM_SRCS third_party/ryu/d2fixed.c) @@ -98,6 +110,7 @@ target_include_directories(_ext PRIVATE third_party/fast_float third_party/ryu third_party/wuffs + third_party/zmij ) # Match the build flags the CPython fmt-fastfloat branch uses for the @@ -139,6 +152,7 @@ target_compile_definitions(_ext PRIVATE $<$:FLOATIUM_HAS_FMT_FORMAT=1> $<$:FLOATIUM_HAS_FMT_OPT_FORMAT=1> $<$:FLOATIUM_HAS_RYU_OPT_FORMAT=1> + $<$:FLOATIUM_HAS_ZMIJ_FORMAT=1> $<$:FLOATIUM_HAS_FAST_FLOAT_PARSE=1> $<$:FLOATIUM_HAS_WUFFS_PARSE=1> $<$:FLOATIUM_HAS_STOCK_PARSE=1> diff --git a/INTERNALS.md b/INTERNALS.md index 1a0f997..c618ce3 100644 --- a/INTERNALS.md +++ b/INTERNALS.md @@ -11,7 +11,7 @@ see the [README](README.md) first. {'patched': True, 'format_backend': 'fmt_opt', 'parse_backend': 'fast_float', - 'available_format_backends': 'fmt,fmt_opt,ryu_opt', + 'available_format_backends': 'fmt,fmt_opt,ryu_opt,zmij', 'available_parse_backends': 'fast_float,wuffs,stock', 'default_format_backend': 'fmt_opt', 'default_parse_backend': 'fast_float'} @@ -37,6 +37,7 @@ time, or build all of them and switch at install time: | `fmt_opt` (default) | `{fmt}` Dragonbox (modes 0/2) + Ryu `d2fixed` (mode 3) | yes | | `fmt` | `{fmt}` Dragonbox + `fmt::detail::format_float` | yes | | `ryu_opt` | Ryu `d2s` (mode 0) + `d2exp` (mode 2) + `d2fixed` (mode 3) | **no** | +| `zmij` | Żmij C port for every mode (shortest + fixed/scientific, bigint above 18 digits) | **no** | | Parse backend | Algorithm | C++? | |----------------------|------------------------------------------------------------|------| @@ -51,14 +52,45 @@ formatter produces a packaged string directly. The parse side does have a `stock` backend (libc `strtod`) because libc happens to match the contract. -`ryu_opt` paired with `wuffs` gives fully pure-C operation — zero C++ -on either the format or parse path: +`zmij` or `ryu_opt` paired with `wuffs` gives fully pure-C operation — +zero C++ on either the format or parse path: ```python import floatium -floatium.install(format_backend="ryu_opt", parse_backend="wuffs") +floatium.install(format_backend="zmij", parse_backend="wuffs") ``` +## The CPython candidate: `zmij` + `wuffs` + +`zmij` is the configuration floatium proposes for CPython itself, and +it is built to be lifted out as-is: + +- One unmodified upstream library, [Żmij](https://github.com/vitaut/zmij) + (MIT or BSL-1.0), vendored as its C port (`zmij.c`, `zmij-c.h`). It + covers every `_Py_dg_dtoa` mode natively: shortest round-trip for + `repr`, and correctly rounded fixed / scientific output at any + precision for `%f` / `%e` / `%g` (a bigint path takes over above 18 + digits). The shortest algorithm is formally verified upstream. +- One adapter, `src/cpython_adapter/zmij_dtoa.c`: plain C11 with the + exact `_Py_dg_dtoa` signature and output contract, using nothing from + CPython but `PyMem_Malloc`. It re-reads Żmij's printf-style text into + dtoa's digits + `decpt` shape and implements the one thing printf has + no notion of — `round(x, -k)`, i.e. mode 3 with negative `ndigits` — + in ~90 lines of digit arithmetic. Copying it into `Python/` next to + `pystrtod.c` is the intended upgrade path. + +Two things would make the adapter smaller and are worth proposing to +Żmij upstream (see `third_party/zmij/README.vendor`): a to-decimal +entry point in the C API (significand + exponent, the shape Żmij +already computes internally) so mode 0 needs no text round-trip, and +`__asm__` instead of `asm` so `zmij.c` compiles under CPython's +`-std=c11`. + +The parse side is independent of this: `_Py_dg_strtod` and +`_Py_dg_dtoa` only share dtoa.c's bignum helpers. A format-only change +can land first, but `dtoa.c` can only be deleted once string-to-float +is replaced too; `wuffs` is the pure-C candidate for that half. + The `ryu_opt` adapter (mode dispatch, FP fast path for `%e`/`%g`, banker's rounding for `round(x, k)` with negative `k`) is ported from the [`rye_float`](https://github.com/eendebakpt/cpython/tree/rye_float) @@ -75,7 +107,7 @@ combinations: ```bash # Build only the pure-C path (smaller wheel, no C++ in float I/O): pip install -e . \ - -C cmake.define.FLOATIUM_FORMAT_BACKEND=ryu_opt \ + -C cmake.define.FLOATIUM_FORMAT_BACKEND=zmij \ -C cmake.define.FLOATIUM_PARSE_BACKEND=wuffs # Build every backend, switch at install time: diff --git a/README.md b/README.md index ac20f89..d0d1f65 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,8 @@ cd floatium pip install -e '.[test]' ``` -No system libraries are required; `{fmt}`, `fast_float`, Ryu, and -Wuffs are all vendored. See [INTERNALS.md](INTERNALS.md) for the build +No system libraries are required; `{fmt}`, `fast_float`, Ryu, Żmij, +and Wuffs are all vendored. See [INTERNALS.md](INTERNALS.md) for the build options and backend matrix. ## Why this package exists @@ -81,9 +81,11 @@ fast_float's Eisel–Lemire path both finish in tens of nanoseconds — there's not enough work to amortize either parser's setup cost. The table above is the default configuration. For the full -per-backend matrix — `fmt_opt` / `fmt` / `ryu_opt` formatting and -`fast_float` / `wuffs` / `stock` parsing — see -[BENCHMARKS.md](BENCHMARKS.md). +per-backend matrix — `fmt_opt` / `fmt` / `ryu_opt` / `zmij` formatting +and `fast_float` / `wuffs` / `stock` parsing — see +[BENCHMARKS.md](BENCHMARKS.md). The `zmij` + `wuffs` pair is the pure-C +configuration floatium proposes for CPython itself; see +[INTERNALS.md](INTERNALS.md#the-cpython-candidate-zmij--wuffs). Run locally with: diff --git a/floatium/__init__.py b/floatium/__init__.py index 7375082..48a2af5 100644 --- a/floatium/__init__.py +++ b/floatium/__init__.py @@ -1,7 +1,7 @@ """floatium — drop-in replacement for CPython float formatting/parsing. Backs float repr/str/__format__/__new__ with the {fmt} and fast_float C++ -libraries (or a pure-C combination of Ryu + Wuffs) to demonstrate the +libraries (or a pure-C combination of Żmij or Ryu + Wuffs) to demonstrate the performance and correctness case for a forthcoming CPython PEP. Basic usage: diff --git a/src/backends/zmij_format.cc b/src/backends/zmij_format.cc new file mode 100644 index 0000000..9682871 --- /dev/null +++ b/src/backends/zmij_format.cc @@ -0,0 +1,33 @@ +// zmij: pure-C format backend backed by the Żmij C library (shortest via +// Żmij's Schubfach-family algorithm; %e / %f precision via its own +// correctly rounded fixed / scientific writers, bigint-backed above 18 +// digits). This is the backend floatium proposes for CPython itself: one +// unmodified upstream C library plus the plain-C adapter in +// src/cpython_adapter/zmij_dtoa.c. Pair with parse_backend="wuffs" for a +// fully pure-C float path. +#include "common/backend.h" + +#include + +extern "C" { +// Provided by src/cpython_adapter/zmij_dtoa.c (compiled as C). +char *_Py_zmij_dtoa(double d, int mode, int ndigits, + int *decpt, int *sign, char **rve); +void _Py_zmij_dtoa_free(char *s); +} + +namespace { + +struct Register { + Register() { + static const FloatiumFormatBackend b = { + /*name=*/"zmij", + /*dtoa=*/&_Py_zmij_dtoa, + /*dtoa_free=*/&_Py_zmij_dtoa_free, + }; + floatium::register_format_backend(b); + } +}; +Register _reg; + +} // namespace diff --git a/src/cpython_adapter/zmij_dtoa.c b/src/cpython_adapter/zmij_dtoa.c new file mode 100644 index 0000000..4ed447c --- /dev/null +++ b/src/cpython_adapter/zmij_dtoa.c @@ -0,0 +1,297 @@ +/* zmij_dtoa.c - _Py_dg_dtoa drop-in backed by the Żmij C library. + * + * This is the backend floatium proposes for CPython itself, so it is + * written as plain C11 against the exact _Py_dg_dtoa contract and uses + * nothing from CPython beyond PyMem_Malloc / PyMem_Free. Moving it into + * Python/ next to pystrtod.c should be a copy, not a port. + * + * char *_Py_zmij_dtoa(double d, int mode, int ndigits, + * int *decpt, int *sign, char **rve); + * + * mode 0 - shortest round-trip digits (repr / str) + * mode 2 - max(1, ndigits) significant digits (%e / %g) + * mode 3 - ndigits digits past the decimal point (%f); ndigits may be + * negative (float.__round__ with a negative argument), in which + * case the value is rounded to a multiple of 10**-ndigits with + * ties to even against the exact binary value. + * + * Output contract (David Gay's): + * - returns a PyMem_Malloc'd NUL-terminated string of decimal digits only: + * no sign, no decimal point, no exponent, no leading zeros, and no + * trailing zeros; value ~= 0. * 10**(*decpt). + * - a result that rounds to zero in mode 3 is the empty string with + * *decpt == -ndigits (Gay's "no digits" convention). + * - *sign is 1 for negative input (including -0.0), else 0. + * - +-inf / nan return "Infinity" / "NaN" with *decpt == 9999; + * format_float_short recognises them by the first character. + * - *rve points at the terminating NUL. NULL means allocation failure. + * + * Żmij writes printf-style text, so the adapter re-reads the few + * characters it produced into the digits/decpt shape. A to-decimal + * entry point in Żmij's C API (significand + exponent, the shape its + * internal to_decimal already has) would remove that step; see + * README.vendor for the upstream request. + */ + +#include + +#include +#include +#include +#include + +#include "zmij-c.h" + +/* Worst-case widths of the Żmij text we re-read: + * fixed : 309 integer digits + '.' + precision (no sign) + * scientific : 1 + '.' + precision + "e+308" (no sign) + * The public zmij_write_* wrappers truncate to the buffer size we pass, + * so these bounds are also what makes the re-read safe. */ +#define ZMIJ_DTOA_FIXED_EXTRA 311 +#define ZMIJ_DTOA_SCI_EXTRA 8 + +static char * +dup_digits(const char *src, size_t n, char **rve) +{ + char *out = (char *)PyMem_Malloc(n + 1); + if (out == NULL) { + return NULL; + } + memcpy(out, src, n); + out[n] = '\0'; + if (rve) { + *rve = out + n; + } + return out; +} + +/* Re-read Żmij's "[d...][.d...][e[+-]dd]" text (no sign) into Gay's + * digits/decpt shape: strips the decimal point, leading zeros and trailing + * zeros, and folds the exponent into *decpt. Handles the fixed shape + * ("123.45", "0.00123", "1500"), the scientific shape ("1.5e+20", + * "5e-324") and the all-zero shape ("0.000" -> "" with decpt + * -len(fraction)). Returns a PyMem_Malloc'd digit string or NULL. */ +static char * +from_text(const char *s, size_t len, int *decpt, char **rve) +{ + const char *e = (const char *)memchr(s, 'e', len); + size_t mant_len = e ? (size_t)(e - s) : len; + + int exp10 = 0; + if (e != NULL) { + const char *p = e + 1; + const char *end = s + len; + int neg = 0; + if (p < end && (*p == '+' || *p == '-')) { + neg = (*p == '-'); + p++; + } + while (p < end) { + exp10 = exp10 * 10 + (*p - '0'); + p++; + } + if (neg) { + exp10 = -exp10; + } + } + + const char *dot = (const char *)memchr(s, '.', mant_len); + int int_len = dot ? (int)(dot - s) : (int)mant_len; + + char *out = (char *)PyMem_Malloc(mant_len + 1); + if (out == NULL) { + return NULL; + } + size_t n = 0; + for (size_t i = 0; i < mant_len; i++) { + if (s[i] != '.') { + out[n++] = s[i]; + } + } + size_t lead = 0; + while (lead < n && out[lead] == '0') { + lead++; + } + n -= lead; + memmove(out, out + lead, n); + while (n > 0 && out[n - 1] == '0') { + n--; + } + out[n] = '\0'; + + /* int_len counts the digits before the point including stripped + * leading zeros, so int_len - lead is where the point sits relative + * to the first significant digit. For the all-zero case (n == 0) + * this yields -(fraction length) == -ndigits, Gay's convention. */ + *decpt = int_len - (int)lead + exp10; + if (rve) { + *rve = out + n; + } + return out; +} + +/* Mode 3 with ndigits = -k (k >= 1): round |d| to a multiple of 10**k, + * ties to even against the exact value. Every finite double of magnitude + * >= 2**52 is an integer, so floor() is exact and the exact decimal digits + * of the integer part come from a precision-0 fixed conversion; the only + * other information needed is whether a fractional part exists at all. */ +static char * +mode3_negative(double abs_d, int k, int *decpt, char **rve) +{ + double ix = floor(abs_d); + int frac_nonzero = (abs_d != ix); + + char intbuf[zmij_double_fixed_buffer_size]; + char *end = zmij_write_fixed(intbuf, sizeof intbuf, ix, 0); + int intlen = (int)(end - intbuf); + + /* Fewer than k integer digits: |d| < 10**(k-1) < 10**k / 2 -> 0. */ + if (intlen < k) { + *decpt = k; + return dup_digits("", 0, rve); + } + + int q_len = intlen - k; /* digits of the quotient Q */ + const char *r = intbuf + q_len; /* the low k digits, remainder */ + + int cmp; /* R <=> 10**k / 2 */ + if (r[0] < '5') { + cmp = -1; + } + else if (r[0] > '5') { + cmp = 1; + } + else { + cmp = 0; + for (int i = 1; i < k; i++) { + if (r[i] != '0') { + cmp = 1; + break; + } + } + } + + int round_up; + if (cmp != 0) { + round_up = cmp > 0; + } + else if (frac_nonzero) { + round_up = 1; + } + else { + char q_last = q_len > 0 ? intbuf[q_len - 1] : '0'; + round_up = ((q_last - '0') & 1) != 0; + } + + /* Q (+1), with room for a carry-out digit. */ + char *q = (char *)PyMem_Malloc((size_t)q_len + 2); + if (q == NULL) { + return NULL; + } + memcpy(q, intbuf, (size_t)q_len); + int qlen = q_len; + if (round_up) { + int i = qlen - 1; + while (i >= 0 && q[i] == '9') { + q[i--] = '0'; + } + if (i >= 0) { + q[i]++; + } + else { + memmove(q + 1, q, (size_t)qlen); + q[0] = '1'; + qlen++; + } + } + if (qlen == 0) { /* Q == 0 and no round-up */ + PyMem_Free(q); + *decpt = k; + return dup_digits("", 0, rve); + } + + *decpt = k + qlen; /* value == Q * 10**k */ + while (qlen > 0 && q[qlen - 1] == '0') { + qlen--; + } + q[qlen] = '\0'; + if (rve) { + *rve = q + qlen; + } + return q; +} + +char * +_Py_zmij_dtoa(double d, int mode, int ndigits, int *decpt, int *sign, + char **rve) +{ + *sign = signbit(d) ? 1 : 0; + double abs_d = fabs(d); + + if (isinf(abs_d)) { + *decpt = 9999; + return dup_digits("Infinity", 8, rve); + } + if (isnan(abs_d)) { + *decpt = 9999; + return dup_digits("NaN", 3, rve); + } + if (abs_d == 0.0) { + *decpt = 1; + return dup_digits("0", 1, rve); + } + + switch (mode) { + case 0: { + char buf[zmij_double_buffer_size]; + char *end = zmij_write(buf, sizeof buf, abs_d); + return from_text(buf, (size_t)(end - buf), decpt, rve); + } + case 2: { + int precision = (ndigits < 1 ? 1 : ndigits) - 1; /* digits after '.' */ + char stack[zmij_double_scientific_buffer_size]; + char *buf = stack; + size_t n = sizeof stack; + if (precision >= 18) { + n = (size_t)precision + ZMIJ_DTOA_SCI_EXTRA; + buf = (char *)PyMem_Malloc(n); + if (buf == NULL) { + return NULL; + } + } + char *end = zmij_write_scientific(buf, n, abs_d, precision); + char *out = from_text(buf, (size_t)(end - buf), decpt, rve); + if (buf != stack) { + PyMem_Free(buf); + } + return out; + } + default: { /* mode 3, like Gay's fallthrough */ + if (ndigits < 0) { + return mode3_negative(abs_d, -ndigits, decpt, rve); + } + char stack[zmij_double_fixed_buffer_size]; + char *buf = stack; + size_t n = sizeof stack; + if (ndigits > 18) { + n = (size_t)ndigits + ZMIJ_DTOA_FIXED_EXTRA; + buf = (char *)PyMem_Malloc(n); + if (buf == NULL) { + return NULL; + } + } + char *end = zmij_write_fixed(buf, n, abs_d, ndigits); + char *out = from_text(buf, (size_t)(end - buf), decpt, rve); + if (buf != stack) { + PyMem_Free(buf); + } + return out; + } + } +} + +void +_Py_zmij_dtoa_free(char *s) +{ + PyMem_Free(s); +} diff --git a/third_party/zmij/LICENSE b/third_party/zmij/LICENSE new file mode 100644 index 0000000..0af4df9 --- /dev/null +++ b/third_party/zmij/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Victor Zverovich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/zmij/README.vendor b/third_party/zmij/README.vendor new file mode 100644 index 0000000..254e40f --- /dev/null +++ b/third_party/zmij/README.vendor @@ -0,0 +1,50 @@ +Żmij — fast floating-point-to-string conversion (C port) +========================================================= + +Upstream: https://github.com/vitaut/zmij +Version: v1.2-19-ga9d6363 +Commit: a9d6363 (2026-10-18) +License: MIT OR BSL-1.0 (see LICENSE) + +Files vendored +-------------- +zmij.c — the complete C implementation: shortest round-trip + (Schubfach-family algorithm, formally verified in Lean + upstream), plus fixed / scientific / general precision + writers with a bigint path for precisions above 18. +zmij-c.h — public C API (zmij_write, zmij_write_fixed, + zmij_write_scientific, ...). +LICENSE — upstream license text. + +The C++ headers (zmij.h, zmij.cc, zmij-to-chars.h) are deliberately not +vendored: the point of this backend is a pure-C float-formatting path +that could be proposed for CPython itself. + +Used by which backends +---------------------- +zmij — all three dtoa modes (0 shortest / 2 significant / 3 fixed, + including negative ndigits) via src/cpython_adapter/zmij_dtoa.c, + which is written as plain C against the _Py_dg_dtoa contract so + it can move into Python/ next to pystrtod.c unchanged. + +Build configuration +------------------- + $(CC) -std=c11 (static_assert, stdbool; __int128 or _umul128) + +Local modifications +------------------- +None. The vendored files are byte-identical to upstream. + +Upstream requests (not yet filed) +--------------------------------- +1. `asm` -> `__asm__` in the ZMIJ_ASM macro (zmij.c). With `-std=c11`, + which is what CPython compiles with, `asm` is not a keyword and the + three ZMIJ_ASM sites fail to compile. floatium builds with gnu11, so + it is unaffected; CPython would not be. +2. A to-decimal entry point in the C API: `{sig, exp}` for the shortest + representation (the shape of zmij.c's internal to_decimal_result), + so a dtoa-style caller can skip writing and re-reading the text. + Optionally the same for fixed / scientific precision. +3. `extern "C"` guards in zmij-c.h for inclusion from C++ TUs. + +Resync: tools/sync_zmij.sh diff --git a/third_party/zmij/zmij-c.h b/third_party/zmij/zmij-c.h new file mode 100644 index 0000000..f0fc43f --- /dev/null +++ b/third_party/zmij/zmij-c.h @@ -0,0 +1,219 @@ +// A double-to-string conversion library: https://github.com/vitaut/zmij/ +// +// Copyright (c) 2025 - present, Victor Zverovich +// Distributed under the MIT license (see LICENSE) or alternatively +// the Boost Software License, Version 1.0. + +#ifndef ZMIJ_C_H_ +#define ZMIJ_C_H_ + +#include // size_t +#include // memcpy + +// Implementation details, use the zmij_write functions instead. +char* zmij_detail_write_f(float value, char* buffer); +char* zmij_detail_write(double value, char* buffer); +char* zmij_detail_write_scientific_f(float value, char* buffer, int num_digits); +char* zmij_detail_write_scientific(double value, char* buffer, int num_digits); +size_t zmij_detail_write_scientific_big(char* out, size_t n, double value, + int precision); +char* zmij_detail_write_general_f(float value, char* buffer, int precision); +char* zmij_detail_write_general(double value, char* buffer, int precision); +size_t zmij_detail_write_general_big(char* out, size_t n, double value, + int precision); +char* zmij_detail_write_fixed_f(float value, char* buffer, int precision); +char* zmij_detail_write_fixed(double value, char* buffer, int precision); +size_t zmij_detail_write_fixed_big(char* out, size_t n, double value, + int precision); +char* zmij_detail_write_hex(double value, char* buffer); + +enum { + // Minimum buffer sizes for zmij_write and zmij_write_f. + zmij_float_buffer_size = 17, + zmij_double_buffer_size = 34, + // Buffer sizes that always suffice for the zmij_write_scientific functions + // with precision up to 17 and the zmij_write_general ones with precision up + // to 18; higher precision needs `precision + 8` bytes. + zmij_float_scientific_buffer_size = 24, + zmij_double_scientific_buffer_size = 25, + // Buffer sizes that always suffice for the zmij_write_fixed functions with + // precision up to 18. + zmij_float_fixed_buffer_size = 59, + zmij_double_fixed_buffer_size = 329, + // Buffer sizes that always suffice for the zmij_write_hex functions. + zmij_float_hex_buffer_size = 16, + zmij_double_hex_buffer_size = 24, +}; + +/// Writes the shortest correctly rounded decimal representation of `value` to +/// `out` without a null terminator. Returns a pointer past the last character +/// written; if the representation exceeds `n` characters, only the first `n` +/// are written. +static inline char* zmij_write(char* out, size_t n, double value) { + if (n >= zmij_double_buffer_size) return zmij_detail_write(value, out); + char buffer[zmij_double_buffer_size]; + size_t size = zmij_detail_write(value, buffer) - buffer; + if (size > n) size = n; + memcpy(out, buffer, size); + return out + size; +} + +static inline char* zmij_write_f(char* out, size_t n, float value) { + if (n >= zmij_float_buffer_size) return zmij_detail_write_f(value, out); + char buffer[zmij_float_buffer_size]; + size_t size = zmij_detail_write_f(value, buffer) - buffer; + if (size > n) size = n; + memcpy(out, buffer, size); + return out + size; +} + +/// Writes `value` in scientific format with `precision` digits after the +/// decimal point (e.g. 1.234e+05) to `out`, without a null terminator, like +/// printf's %e. A negative `precision` defaults to 6. +/// +/// Returns a pointer past the last character written; if the representation +/// exceeds `n` characters, only the first `n` are written. +static inline char* zmij_write_scientific(char* out, size_t n, double value, + int precision) { + if (precision < 0) precision = 6; + if (precision >= 18) { + size_t size = zmij_detail_write_scientific_big(out, n, value, precision); + return out + (size < n ? size : n); + } + if (n >= zmij_double_scientific_buffer_size) + return zmij_detail_write_scientific(value, out, precision + 1); + char buffer[zmij_double_scientific_buffer_size]; + size_t size = + zmij_detail_write_scientific(value, buffer, precision + 1) - buffer; + if (size > n) size = n; + memcpy(out, buffer, size); + return out + size; +} + +static inline char* zmij_write_scientific_f(char* out, size_t n, float value, + int precision) { + if (precision < 0) precision = 6; + if (precision >= 18) { + // A float is exact as a double, so both produce the same digits. + size_t size = + zmij_detail_write_scientific_big(out, n, (double)value, precision); + return out + (size < n ? size : n); + } + if (n >= zmij_float_scientific_buffer_size) + return zmij_detail_write_scientific_f(value, out, precision + 1); + char buffer[zmij_float_scientific_buffer_size]; + size_t size = + zmij_detail_write_scientific_f(value, buffer, precision + 1) - buffer; + if (size > n) size = n; + memcpy(out, buffer, size); + return out + size; +} + +/// Writes `value` in general format with up to `precision` significant digits +/// and no trailing zeros (e.g. 1.5 or 1.5e+20) to `out`, without a null +/// terminator. Fixed notation is used when `value`'s decimal exponent is in +/// [-4, precision), and scientific otherwise. A negative `precision` defaults +/// to 6 and zero is treated as 1, matching printf. +/// +/// Returns a pointer past the last character written; if the representation +/// exceeds `n` characters, only the first `n` are written. +static inline char* zmij_write_general(char* out, size_t n, double value, + int precision) { + if (precision <= 1) precision = precision < 0 ? 6 : 1; + if (precision > 18) { + size_t size = zmij_detail_write_general_big(out, n, value, precision); + return out + (size < n ? size : n); + } + if (n >= zmij_double_scientific_buffer_size) + return zmij_detail_write_general(value, out, precision); + char buffer[zmij_double_scientific_buffer_size]; + size_t size = zmij_detail_write_general(value, buffer, precision) - buffer; + if (size > n) size = n; + memcpy(out, buffer, size); + return out + size; +} + +static inline char* zmij_write_general_f(char* out, size_t n, float value, + int precision) { + if (precision <= 1) precision = precision < 0 ? 6 : 1; + if (precision > 18) { + // A float is exact as a double, so both produce the same digits. + size_t size = + zmij_detail_write_general_big(out, n, (double)value, precision); + return out + (size < n ? size : n); + } + if (n >= zmij_float_scientific_buffer_size) + return zmij_detail_write_general_f(value, out, precision); + char buffer[zmij_float_scientific_buffer_size]; + size_t size = zmij_detail_write_general_f(value, buffer, precision) - buffer; + if (size > n) size = n; + memcpy(out, buffer, size); + return out + size; +} + +/// Writes `value` in fixed notation with exactly `precision` digits after the +/// decimal point (e.g. 1.500) to `out`, without a null terminator. The result +/// is the exact value correctly rounded (ties to even), matching printf's %f. +/// A negative `precision` defaults to 6. +/// +/// Returns a pointer past the last character written; if the representation +/// exceeds `n` characters, only the first `n` are written. +static inline char* zmij_write_fixed(char* out, size_t n, double value, + int precision) { + if (precision < 0) precision = 6; + if (precision > 18) { + size_t size = zmij_detail_write_fixed_big(out, n, value, precision); + return out + (size < n ? size : n); + } + if (n >= zmij_double_fixed_buffer_size) + return zmij_detail_write_fixed(value, out, precision); + char buffer[zmij_double_fixed_buffer_size]; + size_t size = zmij_detail_write_fixed(value, buffer, precision) - buffer; + if (size > n) size = n; + memcpy(out, buffer, size); + return out + size; +} + +static inline char* zmij_write_fixed_f(char* out, size_t n, float value, + int precision) { + if (precision < 0) precision = 6; + if (precision > 18) { + // A float is exact as a double, so both produce the same digits. + size_t size = zmij_detail_write_fixed_big(out, n, (double)value, precision); + return out + (size < n ? size : n); + } + if (n >= zmij_float_fixed_buffer_size) + return zmij_detail_write_fixed_f(value, out, precision); + char buffer[zmij_float_fixed_buffer_size]; + size_t size = zmij_detail_write_fixed_f(value, buffer, precision) - buffer; + if (size > n) size = n; + memcpy(out, buffer, size); + return out + size; +} + +/// Writes `value` in hexadecimal floating-point notation (like printf's %a) in +/// its shortest form (e.g. -0x1.8p+1) to `out`, without a null terminator. +/// +/// Returns a pointer past the last character written; if the representation +/// exceeds `n` characters, only the first `n` are written. +static inline char* zmij_write_hex(char* out, size_t n, double value) { + if (n >= zmij_double_hex_buffer_size) + return zmij_detail_write_hex(value, out); + char buffer[zmij_double_hex_buffer_size]; + size_t size = zmij_detail_write_hex(value, buffer) - buffer; + if (size > n) size = n; + memcpy(out, buffer, size); + return out + size; +} + +static inline char* zmij_write_hex_f(char* out, size_t n, float value) { + if (n >= zmij_float_hex_buffer_size) + return zmij_detail_write_hex((double)value, out); + char buffer[zmij_float_hex_buffer_size]; + size_t size = zmij_detail_write_hex((double)value, buffer) - buffer; + if (size > n) size = n; + memcpy(out, buffer, size); + return out + size; +} + +#endif // ZMIJ_C_H_ diff --git a/third_party/zmij/zmij.c b/third_party/zmij/zmij.c new file mode 100644 index 0000000..23073ec --- /dev/null +++ b/third_party/zmij/zmij.c @@ -0,0 +1,3065 @@ +// A double-to-string conversion library: https://github.com/vitaut/zmij/ +// +// Copyright (c) 2025 - present, Victor Zverovich +// Distributed under the MIT license (see LICENSE) or alternatively +// the Boost Software License, Version 1.0. + +#if __has_include("zmij-c.h") +# include "zmij-c.h" +#endif + +#include // assert +#include // DBL_MANT_DIG +#include // bool +#include // size_t +#include // uint64_t +#include // memcpy + +#ifdef ZMIJ_USE_SIMD +// Use the provided definition. +#elif defined(_MSC_VER) +# define ZMIJ_USE_SIMD 0 +#else +# define ZMIJ_USE_SIMD 1 +#endif + +#ifdef ZMIJ_USE_NEON +// Use the provided definition. +#elif defined(__ARM_NEON) || defined(_M_ARM64) +# define ZMIJ_USE_NEON ZMIJ_USE_SIMD +#else +# define ZMIJ_USE_NEON 0 +#endif +#if ZMIJ_USE_NEON +# include +#endif + +#ifdef ZMIJ_USE_SSE +// Use the provided definition. +#elif defined(__SSE2__) +# define ZMIJ_USE_SSE ZMIJ_USE_SIMD +#elif defined(_M_AMD64) || (defined(_M_IX86_FP) && _M_IX86_FP == 2) +# define ZMIJ_USE_SSE ZMIJ_USE_SIMD +#else +# define ZMIJ_USE_SSE 0 +#endif +#if ZMIJ_USE_SSE +# include +#endif + +#ifdef ZMIJ_USE_SSE4_1 +// Use the provided definition. +static_assert(!ZMIJ_USE_SSE4_1 || ZMIJ_USE_SSE, + "ZMIJ_USE_SSE should be enabled if ZMIJ_USE_SSE4_1 is enabled"); +#elif defined(__SSE4_1__) || defined(__AVX__) +// On MSVC there's no way to check for SSE4.1 specifically so check __AVX__. +# define ZMIJ_USE_SSE4_1 ZMIJ_USE_SSE +#else +# define ZMIJ_USE_SSE4_1 0 +#endif + +#ifdef __aarch64__ +# define ZMIJ_AARCH64 1 +#else +# define ZMIJ_AARCH64 0 +#endif + +#ifdef __x86_64__ +# define ZMIJ_X86_64 1 +#else +# define ZMIJ_X86_64 0 +#endif + +#ifdef __clang__ +# define ZMIJ_CLANG 1 +#else +# define ZMIJ_CLANG 0 +#endif + +#ifdef _MSC_VER +# define ZMIJ_MSC_VER _MSC_VER +# include // __lzcnt64/_umul128/__umulh +#else +# define ZMIJ_MSC_VER 0 +#endif + +#if defined(__has_builtin) && !defined(ZMIJ_NO_BUILTINS) +# define ZMIJ_HAS_BUILTIN(x) __has_builtin(x) +#else +# define ZMIJ_HAS_BUILTIN(x) 0 +#endif +#ifdef __has_attribute +# define ZMIJ_HAS_ATTRIBUTE(x) __has_attribute(x) +#else +# define ZMIJ_HAS_ATTRIBUTE(x) 0 +#endif + +#if ZMIJ_HAS_BUILTIN(__builtin_expect) +# define ZMIJ_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +# define ZMIJ_UNLIKELY(x) (x) +#endif + +#ifdef ZMIJ_OPTIMIZE_SIZE +// Use the provided definition. +#elif defined(__OPTIMIZE_SIZE__) +# define ZMIJ_OPTIMIZE_SIZE 1 +#else +# define ZMIJ_OPTIMIZE_SIZE 0 +#endif +#ifndef ZMIJ_USE_EXP_STRING_TABLE +# define ZMIJ_USE_EXP_STRING_TABLE (ZMIJ_OPTIMIZE_SIZE == 0) +#endif + +#if ZMIJ_HAS_ATTRIBUTE(always_inline) && !ZMIJ_OPTIMIZE_SIZE +# define ZMIJ_INLINE __attribute__((always_inline)) inline +#elif ZMIJ_MSC_VER +# define ZMIJ_INLINE __forceinline +#else +# define ZMIJ_INLINE inline +#endif + +#ifdef __GNUC__ +# define ZMIJ_ASM(x) asm x +#else +# define ZMIJ_ASM(x) ((void)0) +#endif + +#if ZMIJ_HAS_ATTRIBUTE(unused) +# define ZMIJ_MAYBE_UNUSED __attribute__((unused)) +#else +# define ZMIJ_MAYBE_UNUSED +#endif + +#if defined(ZMIJ_ALIGNAS) +// Use the provided definition. +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +# define ZMIJ_ALIGNAS(x) _Alignas(x) +#elif ZMIJ_MSC_VER +# define ZMIJ_ALIGNAS(x) __declspec(align(x)) +#elif __GNUC__ +# define ZMIJ_ALIGNAS(x) __attribute__((aligned(x))) +#else +# define ZMIJ_ALIGNAS(x) +#endif + +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +static const bool is_big_endian = true; +#else +static const bool is_big_endian = false; +#endif + +static inline uint64_t bswap64(uint64_t x) { +#if ZMIJ_HAS_BUILTIN(__builtin_bswap64) + return __builtin_bswap64(x); +#elif ZMIJ_MSC_VER + return _byteswap_uint64(x); +#else + return ((x & 0xff00000000000000) >> 56) | ((x & 0x00ff000000000000) >> 40) | + ((x & 0x0000ff0000000000) >> 24) | ((x & 0x000000ff00000000) >> +8) | + ((x & 0x00000000ff000000) << +8) | ((x & 0x0000000000ff0000) << 24) | + ((x & 0x000000000000ff00) << 40) | ((x & 0x00000000000000ff) << 56); +#endif +} + +static inline int clz(uint64_t x) { + assert(x != 0); +#if ZMIJ_HAS_BUILTIN(__builtin_clzll) + return __builtin_clzll(x); +#elif defined(_M_AMD64) && defined(__AVX2__) + // Use lzcnt only on AVX2-capable CPUs that have this BMI instruction. + return __lzcnt64(x); +#elif defined(_M_AMD64) || defined(_M_ARM64) + unsigned long idx; + _BitScanReverse64(&idx, x); // Fallback to the BSR instruction. + return 63 - idx; +#elif ZMIJ_MSC_VER + // Fallback to the 32-bit BSR instruction. + unsigned long idx; + if (_BitScanReverse(&idx, (uint32_t)(x >> 32))) return 31 - idx; + _BitScanReverse(&idx, (uint32_t)x); + return 63 - idx; +#else + int n = 64; + for (; x > 0; x >>= 1) --n; + return n; +#endif +} + +typedef struct { + uint64_t hi; + uint64_t lo; +} uint128; + +#ifdef ZMIJ_USE_INT128 +// Use the provided definition. +#elif defined(__SIZEOF_INT128__) +# define ZMIJ_USE_INT128 1 +#else +# define ZMIJ_USE_INT128 0 +#endif + +#if ZMIJ_USE_INT128 +typedef unsigned __int128 uint128_t; +#else +typedef uint128 uint128_t; +#endif // ZMIJ_USE_INT128 + +static inline uint128_t uint128_rshift(uint128_t u, int shift) { +#if ZMIJ_USE_INT128 + return u >> shift; +#else + if (shift == 32) { + uint64_t hilo = (uint32_t)u.hi; + uint128 result = {u.hi >> 32, (hilo << 32) | (u.lo >> 32)}; + return result; + } + assert(shift >= 64 && shift < 128); + uint128 result = {0, u.hi >> (shift - 64)}; + return result; +#endif +} +#if ZMIJ_USE_INT128 && defined(__APPLE__) +static const bool use_umul128_hi64 = true; // Use umul128_hi64 for division. +#else +static const bool use_umul128_hi64 = false; +#endif + +// Computes 128-bit result of multiplication of two 64-bit unsigned integers. +static uint128_t umul128(uint64_t x, uint64_t y) { +#if ZMIJ_USE_INT128 + return (uint128_t)x * y; +#else +# if defined(_M_AMD64) + { + uint64_t hi = 0; + uint64_t lo = _umul128(x, y, &hi); + uint128 result = {hi, lo}; + return result; + } +# elif defined(_M_ARM64) + { + uint128 result = {__umulh(x, y), x * y}; + return result; + } +# endif + uint64_t a = x >> 32; + uint64_t b = (uint32_t)x; + uint64_t c = y >> 32; + uint64_t d = (uint32_t)y; + + uint64_t ac = a * c; + uint64_t bc = b * c; + uint64_t ad = a * d; + uint64_t bd = b * d; + + uint64_t cs = (bd >> 32) + (uint32_t)ad + (uint32_t)bc; // cross sum + uint128 result = {ac + (ad >> 32) + (bc >> 32) + (cs >> 32), + (cs << 32) + (uint32_t)bd}; + return result; +#endif // ZMIJ_USE_INT128 +} + +static uint64_t umul128_hi64(uint64_t x, uint64_t y) { +#if ZMIJ_USE_INT128 + return (uint64_t)(umul128(x, y) >> 64); +#else + return umul128(x, y).hi; +#endif +} + +static uint64_t lo64(uint128_t v) { +#if ZMIJ_USE_INT128 + return (uint64_t)v; +#else + return v.lo; +#endif +} +static uint64_t hi64(uint128_t v) { +#if ZMIJ_USE_INT128 + return v >> 64; +#else + return v.hi; +#endif +} + +static inline uint128 umul192_hi128(uint64_t x_hi, uint64_t x_lo, uint64_t y) { + uint128_t p = umul128(x_hi, y); + uint64_t lo = lo64(p) + umul128_hi64(x_lo, y); + uint128 result = {hi64(p) + (lo < lo64(p)), lo}; + return result; +} + +// Returns (x * y + c) >> 64. +static inline uint64_t umul128_add_hi64(uint64_t x, uint64_t y, uint64_t c) { +#if ZMIJ_USE_INT128 + return (uint64_t)(((uint128_t)x * y + c) >> 64); +#else + uint128 p = umul128(x, y); + return p.hi + (p.lo + c < p.lo); +#endif +} + +// Returns x / 10 for x <= 2**62. +static ZMIJ_INLINE uint64_t div10(uint64_t x) { + assert(x <= (1ull << 62)); + // ceil(2**64 / 10) computed as (1 << 63) / 5 + 1 to avoid int128. + const uint64_t div10_sig64 = (1ull << 63) / 5 + 1; + return ZMIJ_USE_INT128 ? umul128_hi64(x, div10_sig64) : x / 10; +} + +// Powers of ten that fit in 64 bits, indexed by exponent. +static const uint64_t pow10s[] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, +}; + +enum { + double_num_bits = 64, + double_num_sig_bits = DBL_MANT_DIG - 1, + double_num_exp_bits = double_num_bits - double_num_sig_bits - 1, + double_exp_mask = (1 << double_num_exp_bits) - 1, + double_exp_bias = (1 << (double_num_exp_bits - 1)) - 1, + double_exp_offset = double_exp_bias + double_num_sig_bits, + + // Bounds for the exact big-integer path (write_big): the significand times + // 5**k (k up to exp_offset + num_sig_bits - 1) fits in big_bits bits, hence + // big_limbs base-2**32 limbs and big_digits decimal digits. 2322/1000 and + // 30103/100000 over-approximate log2(5) and log10(2); the +2 rounds the + // digit count up and reserves one carry digit. A float is exact as a double, + // so float precision output goes through these bounds too. + double_big_bits = double_num_sig_bits + 2 + + (double_exp_offset + double_num_sig_bits - 1) * 2322 / 1000, + double_big_limbs = (double_big_bits + 31) / 32, + double_big_digits = double_big_bits * 30103 / 100000 + 2, +}; + +typedef uint64_t double_sig_type; +static const double_sig_type double_implicit_bit = (double_sig_type)1 + << double_num_sig_bits; + +static inline double_sig_type double_to_bits(double value) { + uint64_t bits; + memcpy(&bits, &value, sizeof(value)); + return bits; +} + +static inline bool double_is_negative(double_sig_type bits) { + return bits >> (double_num_bits - 1); +} +static inline double_sig_type double_get_sig(double_sig_type bits) { + return bits & (double_implicit_bit - 1); +} +static int64_t double_get_exp(double_sig_type bits) { + return (int64_t)((bits << 1) >> (double_num_sig_bits + 1)); +} + +enum { + float_num_bits = 32, + float_num_sig_bits = FLT_MANT_DIG - 1, + float_num_exp_bits = float_num_bits - float_num_sig_bits - 1, + float_exp_mask = (1 << float_num_exp_bits) - 1, + float_exp_bias = (1 << (float_num_exp_bits - 1)) - 1, + float_exp_offset = float_exp_bias + float_num_sig_bits, +}; + +typedef uint32_t float_sig_type; +static const float_sig_type float_implicit_bit = (float_sig_type)1 + << float_num_sig_bits; + +static inline float_sig_type float_to_bits(float value) { + uint32_t bits; + memcpy(&bits, &value, sizeof(value)); + return bits; +} + +static inline bool float_is_negative(float_sig_type bits) { + return bits >> (float_num_bits - 1); +} +static inline float_sig_type float_get_sig(float_sig_type bits) { + return bits & (float_implicit_bit - 1); +} +static int64_t float_get_exp(float_sig_type bits) { + return (int64_t)((bits << 1) >> (float_num_sig_bits + 1)); +} + +// Computes the decimal exponent as floor(log10(2**bin_exp)) if regular or +// floor(log10(3/4 * 2**bin_exp)) otherwise, without branching. +static int compute_dec_exp(int bin_exp, bool regular) { + assert(bin_exp >= -1334 && bin_exp <= 2620); + // log10_3_over_4_sig = -log10(3/4) * 2**log10_2_exp rounded to a power of 2 + const int log10_3_over_4_sig = 131072; + // log10_2_sig = round(log10(2) * 2**log10_2_exp) + const int log10_2_sig = 315653; + const int log10_2_exp = 20; + return (bin_exp * log10_2_sig - !regular * log10_3_over_4_sig) >> log10_2_exp; +} + +// Computes a shift so that, after scaling by a power of 10, the intermediate +// result always has a fixed 128-bit fractional part (for double). +// +// Different binary exponents can map to the same decimal exponent, but place +// the decimal point at different bit positions. The shift compensates for this. +// +// For example, both 3 * 2**59 and 3 * 2**60 have dec_exp = 2, but dividing by +// 10^dec_exp puts the decimal point in different bit positions: +// 3 * 2**59 / 100 = 1.72...e+16 (needs shift = 1 + 1) +// 3 * 2**60 / 100 = 3.45...e+16 (needs shift = 2 + 1) +static ZMIJ_INLINE signed char compute_exp_shift(int bin_exp, int dec_exp) { + assert(dec_exp >= -350 && dec_exp <= 350); + // log2_pow10_sig = round(log2(10) * 2**log2_pow10_exp) + 1 + const int log2_pow10_sig = 217707, log2_pow10_exp = 16; + // pow10_bin_exp = floor(log2(10**-dec_exp)) + int pow10_bin_exp = -dec_exp * log2_pow10_sig >> log2_pow10_exp; + // pow10 = ((pow10_hi << 64) | pow10_lo) * 2**(pow10_bin_exp - 127) + return (signed char)(bin_exp + pow10_bin_exp + 1); +} + +static inline int count_trailing_nonzeros(uint64_t x) { + // We count the number of bytes until there are only zeros left. + // The code is equivalent to + // return 8 - clz(x) / 8 + // but if the BSR instruction is emitted (as gcc on x64 does with + // default settings), subtracting the constant before dividing allows + // the compiler to combine it with the subtraction which it inserts + // due to BSR counting in the opposite direction. + // + // Additionally, the BSR instruction requires a zero check. Since the + // high bit is unused we can avoid the zero check by shifting the + // datum left by one and inserting a sentinel bit at the end. This can + // be faster than the automatically inserted range check. + if (is_big_endian) x = bswap64(x); + return ((size_t)70 - clz((x << 1) | 1)) / 8; // size_t for native arithmetic +} + +// Converts value in the range [0, 100) to a string. GCC generates a bit better +// code when value is pointer-size (https://www.godbolt.org/z/5fEPMT1cc). +static ZMIJ_MAYBE_UNUSED inline const char* digits2(size_t value) { + // Align data since unaligned access may be slower when crossing a + // hardware-specific boundary. + ZMIJ_ALIGNAS(2) + static const char data[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + return &data[value * 2]; +} + +// Enum constants: static_data's initializer needs constant expressions, which +// a static const object is not in C. gcc 5 and 6 enforce that. +enum { + div10k_exp = 40, + div10k_sig = (uint32_t)((1ull << div10k_exp) / 10000 + 1), + + div100_exp = 19, + div100_sig = (1 << div100_exp) / 100 + 1, + neg100 = (1 << 16) - 100, + + div10_exp = 10, + div10_sig = (1 << div10_exp) / 10 + 1, + neg10 = (1 << 8) - 10, +}; + +// These two don't fit in an int, so they cannot be enum constants before C23. +// static_data repeats their expressions rather than naming them. +static const uint32_t neg10k = (uint32_t)((1ull << 32) - 10000); +static const uint64_t zeros = 0x0101010101010101u * '0'; + +static inline void write8(char* buffer, uint64_t value) { + memcpy(buffer, &value, 8); +} + +static ZMIJ_INLINE char* write2(char* out, char a, char b) { + uint16_t v = (uint16_t)((uint8_t)a | (uint8_t)b << 8); + if (is_big_endian) v = (uint16_t)(v << 8 | v >> 8); + memcpy(out, &v, 2); + return out + 2; +} + +#if ZMIJ_USE_SSE && !ZMIJ_MSC_VER +typedef __m128i m128i; +#else +typedef struct { + long long data[2]; +} m128i; +#endif + +#define ZMIJ_SPLAT64(x) {(long long)(x), (long long)(x)} +#define ZMIJ_SPLAT32(x) ZMIJ_SPLAT64((uint64_t)(x) << 32 | (x)) +#define ZMIJ_SPLAT16(x) ZMIJ_SPLAT32((uint32_t)(x) << 16 | (x)) +#define ZMIJ_PACK8(a, b, c, d, e, f, g, h) \ + ((uint64_t)(h) << 56 | (uint64_t)(g) << 48 | (uint64_t)(f) << 40 | \ + (uint64_t)(e) << 32 | (uint64_t)(d) << 24 | (uint64_t)(c) << 16 | \ + (uint64_t)(b) << 8 | (uint64_t)(a)) + +// File-scope SIMD constants used by to_bcd_4x4, to_unshuffled_digits, and +// write_digits. The non-SIMD scalar paths don't need these. +#if ZMIJ_USE_NEON +# if ZMIJ_MSC_VER +typedef int32_t int32x4_storage[4]; +typedef int16_t int16x8_storage[8]; +# else +typedef int32x4_t int32x4_storage; +typedef int16x8_t int16x8_storage; +# endif +#endif + +// Per-decimal-exponent buffer layout for branchless fixed-notation output. +// The SSE4.1 shuffle data lives in the same entry, so both are one aligned +// lookup (matches the C++ fixed_layout_table::entry). +#if ZMIJ_USE_SSE4_1 +# define ZMIJ_FIXED_ENTRY_ALIGN 64 // Align each entry to a cache line. +#elif ZMIJ_AARCH64 && !ZMIJ_OPTIMIZE_SIZE +// Align entry to 32 bytes so indexing uses `lsl #5` not `umaddl`. +# define ZMIJ_FIXED_ENTRY_ALIGN 32 +#else +# define ZMIJ_FIXED_ENTRY_ALIGN 1 +#endif + +typedef struct { +#if ZMIJ_USE_SSE4_1 + // pshufb table mapping BCD bytes to their output slots; the decimal-point + // slot (if any) holds a zero-marker (high bit set). Indexed by extra_digit. + // Read via aligned load (_mm_load_si128); the entry alignment keeps it + // 16-byte aligned. + ZMIJ_ALIGNAS(ZMIJ_FIXED_ENTRY_ALIGN) unsigned char shuffle[2][16]; +#else + ZMIJ_ALIGNAS(ZMIJ_FIXED_ENTRY_ALIGN) +#endif + // Byte offset past leading "0.00..." before first significant digit. + unsigned char start_pos; + unsigned char point_pos; + // Start position for shifting digits right by one to insert the point. + unsigned char shift_pos; +#if ZMIJ_USE_SSE4_1 + // Buffer-relative position of the last_digit byte, indexed by + // has_extra_digit. Only used for bcd_size == 16 (doubles). + unsigned char last_digit_pos[2]; +#endif + // Offset past end of fixed-notation output, indexed by sig length - 1. + unsigned char end_pos[17]; +} fixed_layout_entry; + +#if ZMIJ_USE_EXP_STRING_TABLE +enum { + exp_string_offset = 324, +}; +#endif // ZMIJ_USE_EXP_STRING_TABLE + +enum { + exp_shift_extra_shift = 6, +}; + +typedef struct { + uint64_t threshold; + // +6 is needed for boundary cases found by verify.py. + uint64_t biased_half; +#if ZMIJ_USE_NEON + uint64_t mul_const; + uint64_t hundred_million; + int32x4_storage multipliers32; + int16x8_storage multipliers16; +#elif ZMIJ_USE_SSE + // Ordered so the values used to format floats fit in a single cache line. + m128i div100; + m128i div10; +# if ZMIJ_USE_SSE4_1 + m128i neg100; + m128i neg10; + m128i bswap; +# else + m128i hundred; + m128i moddiv10; +# endif // ZMIJ_USE_SSE4_1 + m128i div10k; + m128i neg10k; + m128i zeros_v; +#endif // ZMIJ_USE_SSE + // A table of precomputed shifts for the new direct-scaling algorithm. + // `data[raw_exp] = compute_exp_shift(bin_exp, dec_exp + 1) + extra_shift` + // where extra_shift = 6 and bin_exp = max(raw_exp, 1) - double_exp_offset. + // Shared between float and double via double_exp_offset. + unsigned char exp_shifts[2048]; +#if ZMIJ_USE_EXP_STRING_TABLE + // A table of precomputed exponent strings for scientific notation. + // Each entry packs "e+dd" or "e+ddd" into a uint64_t with the length in + // byte 7. Indexed by `dec_exp + exp_string_offset` for dec_exp in [-324, + // 308]. + uint64_t exp_strings[633]; +#endif + // 128-bit significands of powers of 10 rounded down, for 10**-307 to + // 10**341. The shortest path only reaches 10**-293 to 10**324; the + // precision paths scale by up to 17 extra powers of ten at either end. + ZMIJ_ALIGNAS(64) uint128 pow10_significands[649]; + fixed_layout_entry fixed_layouts[20]; + // Shuffle indices for SIMD digit shift. Offset 0 = identity, offset 1 = + // shift left by 1 (drops the leading '0' of a 16-digit significand). + unsigned char shift_shuffle[17]; +} zmij_data; + +// Expand to the SSE4.1-only fixed_layout_entry fields (plus a trailing comma +// separating them from the following initializers), or to nothing. +#if ZMIJ_USE_SSE4_1 +# define ZMIJ_FIXED_SSE(...) __VA_ARGS__, +#else +# define ZMIJ_FIXED_SSE(...) +#endif + +ZMIJ_ALIGNAS(64) +static const zmij_data static_data = { + (uint64_t)1e15, + ((uint64_t)1 << 63) + 6, +#if ZMIJ_USE_NEON + 0xabcc77118461cefd, + 100000000, + {div10k_sig, (int32_t)(0x10000 - 10000), div100_sig << 12, neg100}, + {0xce0, neg10, 0, 0, 0, 0, 0, 0}, +#elif ZMIJ_USE_SSE + ZMIJ_SPLAT32(div100_sig), + ZMIJ_SPLAT16((1 << 16) / 10 + 1), +# if ZMIJ_USE_SSE4_1 + ZMIJ_SPLAT32(neg100), + ZMIJ_SPLAT16((1 << 8) - 10), + {ZMIJ_PACK8(15, 14, 13, 12, 11, 10, 9, 8), + ZMIJ_PACK8(7, 6, 5, 4, 3, 2, 1, 0)}, +# else + ZMIJ_SPLAT32(100), + ZMIJ_SPLAT16(10 * (1 << 8) - 1), +# endif // ZMIJ_USE_SSE4_1 + ZMIJ_SPLAT64(div10k_sig), + // neg10k and zeros, which cannot be named in a static initializer. + ZMIJ_SPLAT64((uint32_t)((1ull << 32) - 10000)), + ZMIJ_SPLAT64((uint64_t)(0x0101010101010101u * '0')), +#endif // ZMIJ_USE_SSE + // .exp_shifts = + { + 5, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, + 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, + 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, + 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, + 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, + 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, + 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, + 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, + 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, + 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, + 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, + 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, + 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, + 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, + 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, + 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, + 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, + 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, + 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, + 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, + 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, + 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, + 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, + 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, + 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, + 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, + 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, + 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, + 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, + 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, + 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, + 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, + 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, + 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, + 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, + 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, + 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, + 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, + 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, + 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, + 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, + 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, + 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, + 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, + 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, + 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, + 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, + 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, + 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, + 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, + 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, + 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, + 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, + 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, + 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, + 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, + 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, + 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, + 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, + 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, + 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, + 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, + 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, + 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, + 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, + 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, + 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, + 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, + 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, + 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, + 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, + 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, + 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, + 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, + 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, + 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, + 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, + 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, + 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, + 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, + 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, + 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, + 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, + 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, + 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 3, 4, 5, 6, 4, 5, 6, 4, + 5, 6, 3, 4, 5, 6, 4, 5, + }, +#if ZMIJ_USE_EXP_STRING_TABLE + // .exp_strings = + { + 0x05003432332d65, 0x05003332332d65, 0x05003232332d65, 0x05003132332d65, + 0x05003032332d65, 0x05003931332d65, 0x05003831332d65, 0x05003731332d65, + 0x05003631332d65, 0x05003531332d65, 0x05003431332d65, 0x05003331332d65, + 0x05003231332d65, 0x05003131332d65, 0x05003031332d65, 0x05003930332d65, + 0x05003830332d65, 0x05003730332d65, 0x05003630332d65, 0x05003530332d65, + 0x05003430332d65, 0x05003330332d65, 0x05003230332d65, 0x05003130332d65, + 0x05003030332d65, 0x05003939322d65, 0x05003839322d65, 0x05003739322d65, + 0x05003639322d65, 0x05003539322d65, 0x05003439322d65, 0x05003339322d65, + 0x05003239322d65, 0x05003139322d65, 0x05003039322d65, 0x05003938322d65, + 0x05003838322d65, 0x05003738322d65, 0x05003638322d65, 0x05003538322d65, + 0x05003438322d65, 0x05003338322d65, 0x05003238322d65, 0x05003138322d65, + 0x05003038322d65, 0x05003937322d65, 0x05003837322d65, 0x05003737322d65, + 0x05003637322d65, 0x05003537322d65, 0x05003437322d65, 0x05003337322d65, + 0x05003237322d65, 0x05003137322d65, 0x05003037322d65, 0x05003936322d65, + 0x05003836322d65, 0x05003736322d65, 0x05003636322d65, 0x05003536322d65, + 0x05003436322d65, 0x05003336322d65, 0x05003236322d65, 0x05003136322d65, + 0x05003036322d65, 0x05003935322d65, 0x05003835322d65, 0x05003735322d65, + 0x05003635322d65, 0x05003535322d65, 0x05003435322d65, 0x05003335322d65, + 0x05003235322d65, 0x05003135322d65, 0x05003035322d65, 0x05003934322d65, + 0x05003834322d65, 0x05003734322d65, 0x05003634322d65, 0x05003534322d65, + 0x05003434322d65, 0x05003334322d65, 0x05003234322d65, 0x05003134322d65, + 0x05003034322d65, 0x05003933322d65, 0x05003833322d65, 0x05003733322d65, + 0x05003633322d65, 0x05003533322d65, 0x05003433322d65, 0x05003333322d65, + 0x05003233322d65, 0x05003133322d65, 0x05003033322d65, 0x05003932322d65, + 0x05003832322d65, 0x05003732322d65, 0x05003632322d65, 0x05003532322d65, + 0x05003432322d65, 0x05003332322d65, 0x05003232322d65, 0x05003132322d65, + 0x05003032322d65, 0x05003931322d65, 0x05003831322d65, 0x05003731322d65, + 0x05003631322d65, 0x05003531322d65, 0x05003431322d65, 0x05003331322d65, + 0x05003231322d65, 0x05003131322d65, 0x05003031322d65, 0x05003930322d65, + 0x05003830322d65, 0x05003730322d65, 0x05003630322d65, 0x05003530322d65, + 0x05003430322d65, 0x05003330322d65, 0x05003230322d65, 0x05003130322d65, + 0x05003030322d65, 0x05003939312d65, 0x05003839312d65, 0x05003739312d65, + 0x05003639312d65, 0x05003539312d65, 0x05003439312d65, 0x05003339312d65, + 0x05003239312d65, 0x05003139312d65, 0x05003039312d65, 0x05003938312d65, + 0x05003838312d65, 0x05003738312d65, 0x05003638312d65, 0x05003538312d65, + 0x05003438312d65, 0x05003338312d65, 0x05003238312d65, 0x05003138312d65, + 0x05003038312d65, 0x05003937312d65, 0x05003837312d65, 0x05003737312d65, + 0x05003637312d65, 0x05003537312d65, 0x05003437312d65, 0x05003337312d65, + 0x05003237312d65, 0x05003137312d65, 0x05003037312d65, 0x05003936312d65, + 0x05003836312d65, 0x05003736312d65, 0x05003636312d65, 0x05003536312d65, + 0x05003436312d65, 0x05003336312d65, 0x05003236312d65, 0x05003136312d65, + 0x05003036312d65, 0x05003935312d65, 0x05003835312d65, 0x05003735312d65, + 0x05003635312d65, 0x05003535312d65, 0x05003435312d65, 0x05003335312d65, + 0x05003235312d65, 0x05003135312d65, 0x05003035312d65, 0x05003934312d65, + 0x05003834312d65, 0x05003734312d65, 0x05003634312d65, 0x05003534312d65, + 0x05003434312d65, 0x05003334312d65, 0x05003234312d65, 0x05003134312d65, + 0x05003034312d65, 0x05003933312d65, 0x05003833312d65, 0x05003733312d65, + 0x05003633312d65, 0x05003533312d65, 0x05003433312d65, 0x05003333312d65, + 0x05003233312d65, 0x05003133312d65, 0x05003033312d65, 0x05003932312d65, + 0x05003832312d65, 0x05003732312d65, 0x05003632312d65, 0x05003532312d65, + 0x05003432312d65, 0x05003332312d65, 0x05003232312d65, 0x05003132312d65, + 0x05003032312d65, 0x05003931312d65, 0x05003831312d65, 0x05003731312d65, + 0x05003631312d65, 0x05003531312d65, 0x05003431312d65, 0x05003331312d65, + 0x05003231312d65, 0x05003131312d65, 0x05003031312d65, 0x05003930312d65, + 0x05003830312d65, 0x05003730312d65, 0x05003630312d65, 0x05003530312d65, + 0x05003430312d65, 0x05003330312d65, 0x05003230312d65, 0x05003130312d65, + 0x05003030312d65, 0x04000039392d65, 0x04000038392d65, 0x04000037392d65, + 0x04000036392d65, 0x04000035392d65, 0x04000034392d65, 0x04000033392d65, + 0x04000032392d65, 0x04000031392d65, 0x04000030392d65, 0x04000039382d65, + 0x04000038382d65, 0x04000037382d65, 0x04000036382d65, 0x04000035382d65, + 0x04000034382d65, 0x04000033382d65, 0x04000032382d65, 0x04000031382d65, + 0x04000030382d65, 0x04000039372d65, 0x04000038372d65, 0x04000037372d65, + 0x04000036372d65, 0x04000035372d65, 0x04000034372d65, 0x04000033372d65, + 0x04000032372d65, 0x04000031372d65, 0x04000030372d65, 0x04000039362d65, + 0x04000038362d65, 0x04000037362d65, 0x04000036362d65, 0x04000035362d65, + 0x04000034362d65, 0x04000033362d65, 0x04000032362d65, 0x04000031362d65, + 0x04000030362d65, 0x04000039352d65, 0x04000038352d65, 0x04000037352d65, + 0x04000036352d65, 0x04000035352d65, 0x04000034352d65, 0x04000033352d65, + 0x04000032352d65, 0x04000031352d65, 0x04000030352d65, 0x04000039342d65, + 0x04000038342d65, 0x04000037342d65, 0x04000036342d65, 0x04000035342d65, + 0x04000034342d65, 0x04000033342d65, 0x04000032342d65, 0x04000031342d65, + 0x04000030342d65, 0x04000039332d65, 0x04000038332d65, 0x04000037332d65, + 0x04000036332d65, 0x04000035332d65, 0x04000034332d65, 0x04000033332d65, + 0x04000032332d65, 0x04000031332d65, 0x04000030332d65, 0x04000039322d65, + 0x04000038322d65, 0x04000037322d65, 0x04000036322d65, 0x04000035322d65, + 0x04000034322d65, 0x04000033322d65, 0x04000032322d65, 0x04000031322d65, + 0x04000030322d65, 0x04000039312d65, 0x04000038312d65, 0x04000037312d65, + 0x04000036312d65, 0x04000035312d65, 0x04000034312d65, 0x04000033312d65, + 0x04000032312d65, 0x04000031312d65, 0x04000030312d65, 0x04000039302d65, + 0x04000038302d65, 0x04000037302d65, 0x04000036302d65, 0x04000035302d65, + 0x04000034302d65, 0x04000033302d65, 0x04000032302d65, 0x04000031302d65, + 0x04000030302b65, 0x04000031302b65, 0x04000032302b65, 0x04000033302b65, + 0x04000034302b65, 0x04000035302b65, 0x04000036302b65, 0x04000037302b65, + 0x04000038302b65, 0x04000039302b65, 0x04000030312b65, 0x04000031312b65, + 0x04000032312b65, 0x04000033312b65, 0x04000034312b65, 0x04000035312b65, + 0x04000036312b65, 0x04000037312b65, 0x04000038312b65, 0x04000039312b65, + 0x04000030322b65, 0x04000031322b65, 0x04000032322b65, 0x04000033322b65, + 0x04000034322b65, 0x04000035322b65, 0x04000036322b65, 0x04000037322b65, + 0x04000038322b65, 0x04000039322b65, 0x04000030332b65, 0x04000031332b65, + 0x04000032332b65, 0x04000033332b65, 0x04000034332b65, 0x04000035332b65, + 0x04000036332b65, 0x04000037332b65, 0x04000038332b65, 0x04000039332b65, + 0x04000030342b65, 0x04000031342b65, 0x04000032342b65, 0x04000033342b65, + 0x04000034342b65, 0x04000035342b65, 0x04000036342b65, 0x04000037342b65, + 0x04000038342b65, 0x04000039342b65, 0x04000030352b65, 0x04000031352b65, + 0x04000032352b65, 0x04000033352b65, 0x04000034352b65, 0x04000035352b65, + 0x04000036352b65, 0x04000037352b65, 0x04000038352b65, 0x04000039352b65, + 0x04000030362b65, 0x04000031362b65, 0x04000032362b65, 0x04000033362b65, + 0x04000034362b65, 0x04000035362b65, 0x04000036362b65, 0x04000037362b65, + 0x04000038362b65, 0x04000039362b65, 0x04000030372b65, 0x04000031372b65, + 0x04000032372b65, 0x04000033372b65, 0x04000034372b65, 0x04000035372b65, + 0x04000036372b65, 0x04000037372b65, 0x04000038372b65, 0x04000039372b65, + 0x04000030382b65, 0x04000031382b65, 0x04000032382b65, 0x04000033382b65, + 0x04000034382b65, 0x04000035382b65, 0x04000036382b65, 0x04000037382b65, + 0x04000038382b65, 0x04000039382b65, 0x04000030392b65, 0x04000031392b65, + 0x04000032392b65, 0x04000033392b65, 0x04000034392b65, 0x04000035392b65, + 0x04000036392b65, 0x04000037392b65, 0x04000038392b65, 0x04000039392b65, + 0x05003030312b65, 0x05003130312b65, 0x05003230312b65, 0x05003330312b65, + 0x05003430312b65, 0x05003530312b65, 0x05003630312b65, 0x05003730312b65, + 0x05003830312b65, 0x05003930312b65, 0x05003031312b65, 0x05003131312b65, + 0x05003231312b65, 0x05003331312b65, 0x05003431312b65, 0x05003531312b65, + 0x05003631312b65, 0x05003731312b65, 0x05003831312b65, 0x05003931312b65, + 0x05003032312b65, 0x05003132312b65, 0x05003232312b65, 0x05003332312b65, + 0x05003432312b65, 0x05003532312b65, 0x05003632312b65, 0x05003732312b65, + 0x05003832312b65, 0x05003932312b65, 0x05003033312b65, 0x05003133312b65, + 0x05003233312b65, 0x05003333312b65, 0x05003433312b65, 0x05003533312b65, + 0x05003633312b65, 0x05003733312b65, 0x05003833312b65, 0x05003933312b65, + 0x05003034312b65, 0x05003134312b65, 0x05003234312b65, 0x05003334312b65, + 0x05003434312b65, 0x05003534312b65, 0x05003634312b65, 0x05003734312b65, + 0x05003834312b65, 0x05003934312b65, 0x05003035312b65, 0x05003135312b65, + 0x05003235312b65, 0x05003335312b65, 0x05003435312b65, 0x05003535312b65, + 0x05003635312b65, 0x05003735312b65, 0x05003835312b65, 0x05003935312b65, + 0x05003036312b65, 0x05003136312b65, 0x05003236312b65, 0x05003336312b65, + 0x05003436312b65, 0x05003536312b65, 0x05003636312b65, 0x05003736312b65, + 0x05003836312b65, 0x05003936312b65, 0x05003037312b65, 0x05003137312b65, + 0x05003237312b65, 0x05003337312b65, 0x05003437312b65, 0x05003537312b65, + 0x05003637312b65, 0x05003737312b65, 0x05003837312b65, 0x05003937312b65, + 0x05003038312b65, 0x05003138312b65, 0x05003238312b65, 0x05003338312b65, + 0x05003438312b65, 0x05003538312b65, 0x05003638312b65, 0x05003738312b65, + 0x05003838312b65, 0x05003938312b65, 0x05003039312b65, 0x05003139312b65, + 0x05003239312b65, 0x05003339312b65, 0x05003439312b65, 0x05003539312b65, + 0x05003639312b65, 0x05003739312b65, 0x05003839312b65, 0x05003939312b65, + 0x05003030322b65, 0x05003130322b65, 0x05003230322b65, 0x05003330322b65, + 0x05003430322b65, 0x05003530322b65, 0x05003630322b65, 0x05003730322b65, + 0x05003830322b65, 0x05003930322b65, 0x05003031322b65, 0x05003131322b65, + 0x05003231322b65, 0x05003331322b65, 0x05003431322b65, 0x05003531322b65, + 0x05003631322b65, 0x05003731322b65, 0x05003831322b65, 0x05003931322b65, + 0x05003032322b65, 0x05003132322b65, 0x05003232322b65, 0x05003332322b65, + 0x05003432322b65, 0x05003532322b65, 0x05003632322b65, 0x05003732322b65, + 0x05003832322b65, 0x05003932322b65, 0x05003033322b65, 0x05003133322b65, + 0x05003233322b65, 0x05003333322b65, 0x05003433322b65, 0x05003533322b65, + 0x05003633322b65, 0x05003733322b65, 0x05003833322b65, 0x05003933322b65, + 0x05003034322b65, 0x05003134322b65, 0x05003234322b65, 0x05003334322b65, + 0x05003434322b65, 0x05003534322b65, 0x05003634322b65, 0x05003734322b65, + 0x05003834322b65, 0x05003934322b65, 0x05003035322b65, 0x05003135322b65, + 0x05003235322b65, 0x05003335322b65, 0x05003435322b65, 0x05003535322b65, + 0x05003635322b65, 0x05003735322b65, 0x05003835322b65, 0x05003935322b65, + 0x05003036322b65, 0x05003136322b65, 0x05003236322b65, 0x05003336322b65, + 0x05003436322b65, 0x05003536322b65, 0x05003636322b65, 0x05003736322b65, + 0x05003836322b65, 0x05003936322b65, 0x05003037322b65, 0x05003137322b65, + 0x05003237322b65, 0x05003337322b65, 0x05003437322b65, 0x05003537322b65, + 0x05003637322b65, 0x05003737322b65, 0x05003837322b65, 0x05003937322b65, + 0x05003038322b65, 0x05003138322b65, 0x05003238322b65, 0x05003338322b65, + 0x05003438322b65, 0x05003538322b65, 0x05003638322b65, 0x05003738322b65, + 0x05003838322b65, 0x05003938322b65, 0x05003039322b65, 0x05003139322b65, + 0x05003239322b65, 0x05003339322b65, 0x05003439322b65, 0x05003539322b65, + 0x05003639322b65, 0x05003739322b65, 0x05003839322b65, 0x05003939322b65, + 0x05003030332b65, 0x05003130332b65, 0x05003230332b65, 0x05003330332b65, + 0x05003430332b65, 0x05003530332b65, 0x05003630332b65, 0x05003730332b65, + 0x05003830332b65, + }, +#endif // ZMIJ_USE_EXP_STRING_TABLE + // .pow10_significands = + { + {0x8fd0c16206306bab, 0xa5d3b6d479f8e056}, // -307 + {0xb3c4f1ba87bc8696, 0x8f48a4899877186c}, // -306 + {0xe0b62e2929aba83c, 0x331acdabfe94de87}, // -305 + {0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14}, // -304 + {0xaf8e5410288e1b6f, 0x07ecf0ae5ee44dd9}, // -303 + {0xdb71e91432b1a24a, 0xc9e82cd9f69d6150}, // -302 + {0x892731ac9faf056e, 0xbe311c083a225cd2}, // -301 + {0xab70fe17c79ac6ca, 0x6dbd630a48aaf406}, // -300 + {0xd64d3d9db981787d, 0x092cbbccdad5b108}, // -299 + {0x85f0468293f0eb4e, 0x25bbf56008c58ea5}, // -298 + {0xa76c582338ed2621, 0xaf2af2b80af6f24e}, // -297 + {0xd1476e2c07286faa, 0x1af5af660db4aee1}, // -296 + {0x82cca4db847945ca, 0x50d98d9fc890ed4d}, // -295 + {0xa37fce126597973c, 0xe50ff107bab528a0}, // -294 + {0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8}, // -293 + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a}, // -292 + {0x9faacf3df73609b1, 0x77b191618c54e9ac}, // -291 + {0xc795830d75038c1d, 0xd59df5b9ef6a2417}, // -290 + {0xf97ae3d0d2446f25, 0x4b0573286b44ad1d}, // -289 + {0x9becce62836ac577, 0x4ee367f9430aec32}, // -288 + {0xc2e801fb244576d5, 0x229c41f793cda73f}, // -287 + {0xf3a20279ed56d48a, 0x6b43527578c1110f}, // -286 + {0x9845418c345644d6, 0x830a13896b78aaa9}, // -285 + {0xbe5691ef416bd60c, 0x23cc986bc656d553}, // -284 + {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8}, // -283 + {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9}, // -282 + {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53}, // -281 + {0xe858ad248f5c22c9, 0xd1b3400f8f9cff68}, // -280 + {0x91376c36d99995be, 0x23100809b9c21fa1}, // -279 + {0xb58547448ffffb2d, 0xabd40a0c2832a78a}, // -278 + {0xe2e69915b3fff9f9, 0x16c90c8f323f516c}, // -277 + {0x8dd01fad907ffc3b, 0xae3da7d97f6792e3}, // -276 + {0xb1442798f49ffb4a, 0x99cd11cfdf41779c}, // -275 + {0xdd95317f31c7fa1d, 0x40405643d711d583}, // -274 + {0x8a7d3eef7f1cfc52, 0x482835ea666b2572}, // -273 + {0xad1c8eab5ee43b66, 0xda3243650005eecf}, // -272 + {0xd863b256369d4a40, 0x90bed43e40076a82}, // -271 + {0x873e4f75e2224e68, 0x5a7744a6e804a291}, // -270 + {0xa90de3535aaae202, 0x711515d0a205cb36}, // -269 + {0xd3515c2831559a83, 0x0d5a5b44ca873e03}, // -268 + {0x8412d9991ed58091, 0xe858790afe9486c2}, // -267 + {0xa5178fff668ae0b6, 0x626e974dbe39a872}, // -266 + {0xce5d73ff402d98e3, 0xfb0a3d212dc8128f}, // -265 + {0x80fa687f881c7f8e, 0x7ce66634bc9d0b99}, // -264 + {0xa139029f6a239f72, 0x1c1fffc1ebc44e80}, // -263 + {0xc987434744ac874e, 0xa327ffb266b56220}, // -262 + {0xfbe9141915d7a922, 0x4bf1ff9f0062baa8}, // -261 + {0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9}, // -260 + {0xc4ce17b399107c22, 0xcb550fb4384d21d3}, // -259 + {0xf6019da07f549b2b, 0x7e2a53a146606a48}, // -258 + {0x99c102844f94e0fb, 0x2eda7444cbfc426d}, // -257 + {0xc0314325637a1939, 0xfa911155fefb5308}, // -256 + {0xf03d93eebc589f88, 0x793555ab7eba27ca}, // -255 + {0x96267c7535b763b5, 0x4bc1558b2f3458de}, // -254 + {0xbbb01b9283253ca2, 0x9eb1aaedfb016f16}, // -253 + {0xea9c227723ee8bcb, 0x465e15a979c1cadc}, // -252 + {0x92a1958a7675175f, 0x0bfacd89ec191ec9}, // -251 + {0xb749faed14125d36, 0xcef980ec671f667b}, // -250 + {0xe51c79a85916f484, 0x82b7e12780e7401a}, // -249 + {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810}, // -248 + {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15}, // -247 + {0xdfbdcece67006ac9, 0x67a791e093e1d49a}, // -246 + {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0}, // -245 + {0xaecc49914078536d, 0x58fae9f773886e18}, // -244 + {0xda7f5bf590966848, 0xaf39a475506a899e}, // -243 + {0x888f99797a5e012d, 0x6d8406c952429603}, // -242 + {0xaab37fd7d8f58178, 0xc8e5087ba6d33b83}, // -241 + {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64}, // -240 + {0x855c3be0a17fcd26, 0x5cf2eea09a55067f}, // -239 + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e}, // -238 + {0xd0601d8efc57b08b, 0xf13b94daf124da26}, // -237 + {0x823c12795db6ce57, 0x76c53d08d6b70858}, // -236 + {0xa2cb1717b52481ed, 0x54768c4b0c64ca6e}, // -235 + {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09}, // -234 + {0xfe5d54150b090b02, 0xd3f93b35435d7c4c}, // -233 + {0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf}, // -232 + {0xc6b8e9b0709f109a, 0x359ab6419ca1091b}, // -231 + {0xf867241c8cc6d4c0, 0xc30163d203c94b62}, // -230 + {0x9b407691d7fc44f8, 0x79e0de63425dcf1d}, // -229 + {0xc21094364dfb5636, 0x985915fc12f542e4}, // -228 + {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d}, // -227 + {0x979cf3ca6cec5b5a, 0xa705992ceecf9c42}, // -226 + {0xbd8430bd08277231, 0x50c6ff782a838353}, // -225 + {0xece53cec4a314ebd, 0xa4f8bf5635246428}, // -224 + {0x940f4613ae5ed136, 0x871b7795e136be99}, // -223 + {0xb913179899f68584, 0x28e2557b59846e3f}, // -222 + {0xe757dd7ec07426e5, 0x331aeada2fe589cf}, // -221 + {0x9096ea6f3848984f, 0x3ff0d2c85def7621}, // -220 + {0xb4bca50b065abe63, 0x0fed077a756b53a9}, // -219 + {0xe1ebce4dc7f16dfb, 0xd3e8495912c62894}, // -218 + {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c}, // -217 + {0xb080392cc4349dec, 0xbd8d794d96aacfb3}, // -216 + {0xdca04777f541c567, 0xecf0d7a0fc5583a0}, // -215 + {0x89e42caaf9491b60, 0xf41686c49db57244}, // -214 + {0xac5d37d5b79b6239, 0x311c2875c522ced5}, // -213 + {0xd77485cb25823ac7, 0x7d633293366b828b}, // -212 + {0x86a8d39ef77164bc, 0xae5dff9c02033197}, // -211 + {0xa8530886b54dbdeb, 0xd9f57f830283fdfc}, // -210 + {0xd267caa862a12d66, 0xd072df63c324fd7b}, // -209 + {0x8380dea93da4bc60, 0x4247cb9e59f71e6d}, // -208 + {0xa46116538d0deb78, 0x52d9be85f074e608}, // -207 + {0xcd795be870516656, 0x67902e276c921f8b}, // -206 + {0x806bd9714632dff6, 0x00ba1cd8a3db53b6}, // -205 + {0xa086cfcd97bf97f3, 0x80e8a40eccd228a4}, // -204 + {0xc8a883c0fdaf7df0, 0x6122cd128006b2cd}, // -203 + {0xfad2a4b13d1b5d6c, 0x796b805720085f81}, // -202 + {0x9cc3a6eec6311a63, 0xcbe3303674053bb0}, // -201 + {0xc3f490aa77bd60fc, 0xbedbfc4411068a9c}, // -200 + {0xf4f1b4d515acb93b, 0xee92fb5515482d44}, // -199 + {0x991711052d8bf3c5, 0x751bdd152d4d1c4a}, // -198 + {0xbf5cd54678eef0b6, 0xd262d45a78a0635d}, // -197 + {0xef340a98172aace4, 0x86fb897116c87c34}, // -196 + {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0}, // -195 + {0xbae0a846d2195712, 0x8974836059cca109}, // -194 + {0xe998d258869facd7, 0x2bd1a438703fc94b}, // -193 + {0x91ff83775423cc06, 0x7b6306a34627ddcf}, // -192 + {0xb67f6455292cbf08, 0x1a3bc84c17b1d542}, // -191 + {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93}, // -190 + {0x8e938662882af53e, 0x547eb47b7282ee9c}, // -189 + {0xb23867fb2a35b28d, 0xe99e619a4f23aa43}, // -188 + {0xdec681f9f4c31f31, 0x6405fa00e2ec94d4}, // -187 + {0x8b3c113c38f9f37e, 0xde83bc408dd3dd04}, // -186 + {0xae0b158b4738705e, 0x9624ab50b148d445}, // -185 + {0xd98ddaee19068c76, 0x3badd624dd9b0957}, // -184 + {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6}, // -183 + {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c}, // -182 + {0xd47487cc8470652b, 0x7647c3200069671f}, // -181 + {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073}, // -180 + {0xa5fb0a17c777cf09, 0xf468107100525890}, // -179 + {0xcf79cc9db955c2cc, 0x7182148d4066eeb4}, // -178 + {0x81ac1fe293d599bf, 0xc6f14cd848405530}, // -177 + {0xa21727db38cb002f, 0xb8ada00e5a506a7c}, // -176 + {0xca9cf1d206fdc03b, 0xa6d90811f0e4851c}, // -175 + {0xfd442e4688bd304a, 0x908f4a166d1da663}, // -174 + {0x9e4a9cec15763e2e, 0x9a598e4e043287fe}, // -173 + {0xc5dd44271ad3cdba, 0x40eff1e1853f29fd}, // -172 + {0xf7549530e188c128, 0xd12bee59e68ef47c}, // -171 + {0x9a94dd3e8cf578b9, 0x82bb74f8301958ce}, // -170 + {0xc13a148e3032d6e7, 0xe36a52363c1faf01}, // -169 + {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1}, // -168 + {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9}, // -167 + {0xbcb2b812db11a5de, 0x7415d448f6b6f0e7}, // -166 + {0xebdf661791d60f56, 0x111b495b3464ad21}, // -165 + {0x936b9fcebb25c995, 0xcab10dd900beec34}, // -164 + {0xb84687c269ef3bfb, 0x3d5d514f40eea742}, // -163 + {0xe65829b3046b0afa, 0x0cb4a5a3112a5112}, // -162 + {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab}, // -161 + {0xb3f4e093db73a093, 0x59ed216765690f56}, // -160 + {0xe0f218b8d25088b8, 0x306869c13ec3532c}, // -159 + {0x8c974f7383725573, 0x1e414218c73a13fb}, // -158 + {0xafbd2350644eeacf, 0xe5d1929ef90898fa}, // -157 + {0xdbac6c247d62a583, 0xdf45f746b74abf39}, // -156 + {0x894bc396ce5da772, 0x6b8bba8c328eb783}, // -155 + {0xab9eb47c81f5114f, 0x066ea92f3f326564}, // -154 + {0xd686619ba27255a2, 0xc80a537b0efefebd}, // -153 + {0x8613fd0145877585, 0xbd06742ce95f5f36}, // -152 + {0xa798fc4196e952e7, 0x2c48113823b73704}, // -151 + {0xd17f3b51fca3a7a0, 0xf75a15862ca504c5}, // -150 + {0x82ef85133de648c4, 0x9a984d73dbe722fb}, // -149 + {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba}, // -148 + {0xcc963fee10b7d1b3, 0x318df905079926a8}, // -147 + {0xffbbcfe994e5c61f, 0xfdf17746497f7052}, // -146 + {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633}, // -145 + {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0}, // -144 + {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0}, // -143 + {0x9c1661a651213e2d, 0x06bea10ca65c084e}, // -142 + {0xc31bfa0fe5698db8, 0x486e494fcff30a62}, // -141 + {0xf3e2f893dec3f126, 0x5a89dba3c3efccfa}, // -140 + {0x986ddb5c6b3a76b7, 0xf89629465a75e01c}, // -139 + {0xbe89523386091465, 0xf6bbb397f1135823}, // -138 + {0xee2ba6c0678b597f, 0x746aa07ded582e2c}, // -137 + {0x94db483840b717ef, 0xa8c2a44eb4571cdc}, // -136 + {0xba121a4650e4ddeb, 0x92f34d62616ce413}, // -135 + {0xe896a0d7e51e1566, 0x77b020baf9c81d17}, // -134 + {0x915e2486ef32cd60, 0x0ace1474dc1d122e}, // -133 + {0xb5b5ada8aaff80b8, 0x0d819992132456ba}, // -132 + {0xe3231912d5bf60e6, 0x10e1fff697ed6c69}, // -131 + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1}, // -130 + {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2}, // -129 + {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde}, // -128 + {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b}, // -127 + {0xad4ab7112eb3929d, 0x86c16c98d2c953c6}, // -126 + {0xd89d64d57a607744, 0xe871c7bf077ba8b7}, // -125 + {0x87625f056c7c4a8b, 0x11471cd764ad4972}, // -124 + {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf}, // -123 + {0xd389b47879823479, 0x4aff1d108d4ec2c3}, // -122 + {0x843610cb4bf160cb, 0xcedf722a585139ba}, // -121 + {0xa54394fe1eedb8fe, 0xc2974eb4ee658828}, // -120 + {0xce947a3da6a9273e, 0x733d226229feea32}, // -119 + {0x811ccc668829b887, 0x0806357d5a3f525f}, // -118 + {0xa163ff802a3426a8, 0xca07c2dcb0cf26f7}, // -117 + {0xc9bcff6034c13052, 0xfc89b393dd02f0b5}, // -116 + {0xfc2c3f3841f17c67, 0xbbac2078d443ace2}, // -115 + {0x9d9ba7832936edc0, 0xd54b944b84aa4c0d}, // -114 + {0xc5029163f384a931, 0x0a9e795e65d4df11}, // -113 + {0xf64335bcf065d37d, 0x4d4617b5ff4a16d5}, // -112 + {0x99ea0196163fa42e, 0x504bced1bf8e4e45}, // -111 + {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6}, // -110 + {0xf07da27a82c37088, 0x5d767327bb4e5a4c}, // -109 + {0x964e858c91ba2655, 0x3a6a07f8d510f86f}, // -108 + {0xbbe226efb628afea, 0x890489f70a55368b}, // -107 + {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e}, // -106 + {0x92c8ae6b464fc96f, 0x3b0b8bc90012929d}, // -105 + {0xb77ada0617e3bbcb, 0x09ce6ebb40173744}, // -104 + {0xe55990879ddcaabd, 0xcc420a6a101d0515}, // -103 + {0x8f57fa54c2a9eab6, 0x9fa946824a12232d}, // -102 + {0xb32df8e9f3546564, 0x47939822dc96abf9}, // -101 + {0xdff9772470297ebd, 0x59787e2b93bc56f7}, // -100 + {0x8bfbea76c619ef36, 0x57eb4edb3c55b65a}, // -99 + {0xaefae51477a06b03, 0xede622920b6b23f1}, // -98 + {0xdab99e59958885c4, 0xe95fab368e45eced}, // -97 + {0x88b402f7fd75539b, 0x11dbcb0218ebb414}, // -96 + {0xaae103b5fcd2a881, 0xd652bdc29f26a119}, // -95 + {0xd59944a37c0752a2, 0x4be76d3346f0495f}, // -94 + {0x857fcae62d8493a5, 0x6f70a4400c562ddb}, // -93 + {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952}, // -92 + {0xd097ad07a71f26b2, 0x7e2000a41346a7a7}, // -91 + {0x825ecc24c873782f, 0x8ed400668c0c28c8}, // -90 + {0xa2f67f2dfa90563b, 0x728900802f0f32fa}, // -89 + {0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9}, // -88 + {0xfea126b7d78186bc, 0xe2f610c84987bfa8}, // -87 + {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7c9}, // -86 + {0xc6ede63fa05d3143, 0x91503d1c79720dbb}, // -85 + {0xf8a95fcf88747d94, 0x75a44c6397ce912a}, // -84 + {0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba}, // -83 + {0xc24452da229b021b, 0xfbe85badce996168}, // -82 + {0xf2d56790ab41c2a2, 0xfae27299423fb9c3}, // -81 + {0x97c560ba6b0919a5, 0xdccd879fc967d41a}, // -80 + {0xbdb6b8e905cb600f, 0x5400e987bbc1c920}, // -79 + {0xed246723473e3813, 0x290123e9aab23b68}, // -78 + {0x9436c0760c86e30b, 0xf9a0b6720aaf6521}, // -77 + {0xb94470938fa89bce, 0xf808e40e8d5b3e69}, // -76 + {0xe7958cb87392c2c2, 0xb60b1d1230b20e04}, // -75 + {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2}, // -74 + {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3}, // -73 + {0xe2280b6c20dd5232, 0x25c6da63c38de1b0}, // -72 + {0x8d590723948a535f, 0x579c487e5a38ad0e}, // -71 + {0xb0af48ec79ace837, 0x2d835a9df0c6d851}, // -70 + {0xdcdb1b2798182244, 0xf8e431456cf88e65}, // -69 + {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff}, // -68 + {0xac8b2d36eed2dac5, 0xe272467e3d222f3f}, // -67 + {0xd7adf884aa879177, 0x5b0ed81dcc6abb0f}, // -66 + {0x86ccbb52ea94baea, 0x98e947129fc2b4e9}, // -65 + {0xa87fea27a539e9a5, 0x3f2398d747b36224}, // -64 + {0xd29fe4b18e88640e, 0x8eec7f0d19a03aad}, // -63 + {0x83a3eeeef9153e89, 0x1953cf68300424ac}, // -62 + {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7}, // -61 + {0xcdb02555653131b6, 0x3792f412cb06794d}, // -60 + {0x808e17555f3ebf11, 0xe2bbd88bbee40bd0}, // -59 + {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4}, // -58 + {0xc8de047564d20a8b, 0xf245825a5a445275}, // -57 + {0xfb158592be068d2e, 0xeed6e2f0f0d56712}, // -56 + {0x9ced737bb6c4183d, 0x55464dd69685606b}, // -55 + {0xc428d05aa4751e4c, 0xaa97e14c3c26b886}, // -54 + {0xf53304714d9265df, 0xd53dd99f4b3066a8}, // -53 + {0x993fe2c6d07b7fab, 0xe546a8038efe4029}, // -52 + {0xbf8fdb78849a5f96, 0xde98520472bdd033}, // -51 + {0xef73d256a5c0f77c, 0x963e66858f6d4440}, // -50 + {0x95a8637627989aad, 0xdde7001379a44aa8}, // -49 + {0xbb127c53b17ec159, 0x5560c018580d5d52}, // -48 + {0xe9d71b689dde71af, 0xaab8f01e6e10b4a6}, // -47 + {0x9226712162ab070d, 0xcab3961304ca70e8}, // -46 + {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22}, // -45 + {0xe45c10c42a2b3b05, 0x8cb89a7db77c506a}, // -44 + {0x8eb98a7a9a5b04e3, 0x77f3608e92adb242}, // -43 + {0xb267ed1940f1c61c, 0x55f038b237591ed3}, // -42 + {0xdf01e85f912e37a3, 0x6b6c46dec52f6688}, // -41 + {0x8b61313bbabce2c6, 0x2323ac4b3b3da015}, // -40 + {0xae397d8aa96c1b77, 0xabec975e0a0d081a}, // -39 + {0xd9c7dced53c72255, 0x96e7bd358c904a21}, // -38 + {0x881cea14545c7575, 0x7e50d64177da2e54}, // -37 + {0xaa242499697392d2, 0xdde50bd1d5d0b9e9}, // -36 + {0xd4ad2dbfc3d07787, 0x955e4ec64b44e864}, // -35 + {0x84ec3c97da624ab4, 0xbd5af13bef0b113e}, // -34 + {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e}, // -33 + {0xcfb11ead453994ba, 0x67de18eda5814af2}, // -32 + {0x81ceb32c4b43fcf4, 0x80eacf948770ced7}, // -31 + {0xa2425ff75e14fc31, 0xa1258379a94d028d}, // -30 + {0xcad2f7f5359a3b3e, 0x096ee45813a04330}, // -29 + {0xfd87b5f28300ca0d, 0x8bca9d6e188853fc}, // -28 + {0x9e74d1b791e07e48, 0x775ea264cf55347d}, // -27 + {0xc612062576589dda, 0x95364afe032a819d}, // -26 + {0xf79687aed3eec551, 0x3a83ddbd83f52204}, // -25 + {0x9abe14cd44753b52, 0xc4926a9672793542}, // -24 + {0xc16d9a0095928a27, 0x75b7053c0f178293}, // -23 + {0xf1c90080baf72cb1, 0x5324c68b12dd6338}, // -22 + {0x971da05074da7bee, 0xd3f6fc16ebca5e03}, // -21 + {0xbce5086492111aea, 0x88f4bb1ca6bcf584}, // -20 + {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e5}, // -19 + {0x9392ee8e921d5d07, 0x3aff322e62439fcf}, // -18 + {0xb877aa3236a4b449, 0x09befeb9fad487c2}, // -17 + {0xe69594bec44de15b, 0x4c2ebe687989a9b3}, // -16 + {0x901d7cf73ab0acd9, 0x0f9d37014bf60a10}, // -15 + {0xb424dc35095cd80f, 0x538484c19ef38c94}, // -14 + {0xe12e13424bb40e13, 0x2865a5f206b06fb9}, // -13 + {0x8cbccc096f5088cb, 0xf93f87b7442e45d3}, // -12 + {0xafebff0bcb24aafe, 0xf78f69a51539d748}, // -11 + {0xdbe6fecebdedd5be, 0xb573440e5a884d1b}, // -10 + {0x89705f4136b4a597, 0x31680a88f8953030}, // -9 + {0xabcc77118461cefc, 0xfdc20d2b36ba7c3d}, // -8 + {0xd6bf94d5e57a42bc, 0x3d32907604691b4c}, // -7 + {0x8637bd05af6c69b5, 0xa63f9a49c2c1b10f}, // -6 + {0xa7c5ac471b478423, 0x0fcf80dc33721d53}, // -5 + {0xd1b71758e219652b, 0xd3c36113404ea4a8}, // -4 + {0x83126e978d4fdf3b, 0x645a1cac083126e9}, // -3 + {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a3}, // -2 + {0xcccccccccccccccc, 0xcccccccccccccccc}, // -1 + {0x8000000000000000, 0x0000000000000000}, // 0 + {0xa000000000000000, 0x0000000000000000}, // 1 + {0xc800000000000000, 0x0000000000000000}, // 2 + {0xfa00000000000000, 0x0000000000000000}, // 3 + {0x9c40000000000000, 0x0000000000000000}, // 4 + {0xc350000000000000, 0x0000000000000000}, // 5 + {0xf424000000000000, 0x0000000000000000}, // 6 + {0x9896800000000000, 0x0000000000000000}, // 7 + {0xbebc200000000000, 0x0000000000000000}, // 8 + {0xee6b280000000000, 0x0000000000000000}, // 9 + {0x9502f90000000000, 0x0000000000000000}, // 10 + {0xba43b74000000000, 0x0000000000000000}, // 11 + {0xe8d4a51000000000, 0x0000000000000000}, // 12 + {0x9184e72a00000000, 0x0000000000000000}, // 13 + {0xb5e620f480000000, 0x0000000000000000}, // 14 + {0xe35fa931a0000000, 0x0000000000000000}, // 15 + {0x8e1bc9bf04000000, 0x0000000000000000}, // 16 + {0xb1a2bc2ec5000000, 0x0000000000000000}, // 17 + {0xde0b6b3a76400000, 0x0000000000000000}, // 18 + {0x8ac7230489e80000, 0x0000000000000000}, // 19 + {0xad78ebc5ac620000, 0x0000000000000000}, // 20 + {0xd8d726b7177a8000, 0x0000000000000000}, // 21 + {0x878678326eac9000, 0x0000000000000000}, // 22 + {0xa968163f0a57b400, 0x0000000000000000}, // 23 + {0xd3c21bcecceda100, 0x0000000000000000}, // 24 + {0x84595161401484a0, 0x0000000000000000}, // 25 + {0xa56fa5b99019a5c8, 0x0000000000000000}, // 26 + {0xcecb8f27f4200f3a, 0x0000000000000000}, // 27 + {0x813f3978f8940984, 0x4000000000000000}, // 28 + {0xa18f07d736b90be5, 0x5000000000000000}, // 29 + {0xc9f2c9cd04674ede, 0xa400000000000000}, // 30 + {0xfc6f7c4045812296, 0x4d00000000000000}, // 31 + {0x9dc5ada82b70b59d, 0xf020000000000000}, // 32 + {0xc5371912364ce305, 0x6c28000000000000}, // 33 + {0xf684df56c3e01bc6, 0xc732000000000000}, // 34 + {0x9a130b963a6c115c, 0x3c7f400000000000}, // 35 + {0xc097ce7bc90715b3, 0x4b9f100000000000}, // 36 + {0xf0bdc21abb48db20, 0x1e86d40000000000}, // 37 + {0x96769950b50d88f4, 0x1314448000000000}, // 38 + {0xbc143fa4e250eb31, 0x17d955a000000000}, // 39 + {0xeb194f8e1ae525fd, 0x5dcfab0800000000}, // 40 + {0x92efd1b8d0cf37be, 0x5aa1cae500000000}, // 41 + {0xb7abc627050305ad, 0xf14a3d9e40000000}, // 42 + {0xe596b7b0c643c719, 0x6d9ccd05d0000000}, // 43 + {0x8f7e32ce7bea5c6f, 0xe4820023a2000000}, // 44 + {0xb35dbf821ae4f38b, 0xdda2802c8a800000}, // 45 + {0xe0352f62a19e306e, 0xd50b2037ad200000}, // 46 + {0x8c213d9da502de45, 0x4526f422cc340000}, // 47 + {0xaf298d050e4395d6, 0x9670b12b7f410000}, // 48 + {0xdaf3f04651d47b4c, 0x3c0cdd765f114000}, // 49 + {0x88d8762bf324cd0f, 0xa5880a69fb6ac800}, // 50 + {0xab0e93b6efee0053, 0x8eea0d047a457a00}, // 51 + {0xd5d238a4abe98068, 0x72a4904598d6d880}, // 52 + {0x85a36366eb71f041, 0x47a6da2b7f864750}, // 53 + {0xa70c3c40a64e6c51, 0x999090b65f67d924}, // 54 + {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d}, // 55 + {0x82818f1281ed449f, 0xbff8f10e7a8921a4}, // 56 + {0xa321f2d7226895c7, 0xaff72d52192b6a0d}, // 57 + {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490}, // 58 + {0xfee50b7025c36a08, 0x02f236d04753d5b4}, // 59 + {0x9f4f2726179a2245, 0x01d762422c946590}, // 60 + {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5}, // 61 + {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2}, // 62 + {0x9b934c3b330c8577, 0x63cc55f49f88eb2f}, // 63 + {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb}, // 64 + {0xf316271c7fc3908a, 0x8bef464e3945ef7a}, // 65 + {0x97edd871cfda3a56, 0x97758bf0e3cbb5ac}, // 66 + {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317}, // 67 + {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd}, // 68 + {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a}, // 69 + {0xb975d6b6ee39e436, 0xb3e2fd538e122b44}, // 70 + {0xe7d34c64a9c85d44, 0x60dbbca87196b616}, // 71 + {0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd}, // 72 + {0xb51d13aea4a488dd, 0x6babab6398bdbe41}, // 73 + {0xe264589a4dcdab14, 0xc696963c7eed2dd1}, // 74 + {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2}, // 75 + {0xb0de65388cc8ada8, 0x3b25a55f43294bcb}, // 76 + {0xdd15fe86affad912, 0x49ef0eb713f39ebe}, // 77 + {0x8a2dbf142dfcc7ab, 0x6e3569326c784337}, // 78 + {0xacb92ed9397bf996, 0x49c2c37f07965404}, // 79 + {0xd7e77a8f87daf7fb, 0xdc33745ec97be906}, // 80 + {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3}, // 81 + {0xa8acd7c0222311bc, 0xc40832ea0d68ce0c}, // 82 + {0xd2d80db02aabd62b, 0xf50a3fa490c30190}, // 83 + {0x83c7088e1aab65db, 0x792667c6da79e0fa}, // 84 + {0xa4b8cab1a1563f52, 0x577001b891185938}, // 85 + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86}, // 86 + {0x80b05e5ac60b6178, 0x544f8158315b05b4}, // 87 + {0xa0dc75f1778e39d6, 0x696361ae3db1c721}, // 88 + {0xc913936dd571c84c, 0x03bc3a19cd1e38e9}, // 89 + {0xfb5878494ace3a5f, 0x04ab48a04065c723}, // 90 + {0x9d174b2dcec0e47b, 0x62eb0d64283f9c76}, // 91 + {0xc45d1df942711d9a, 0x3ba5d0bd324f8394}, // 92 + {0xf5746577930d6500, 0xca8f44ec7ee36479}, // 93 + {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb}, // 94 + {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e}, // 95 + {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e}, // 96 + {0x95d04aee3b80ece5, 0xbba1f1d158724a12}, // 97 + {0xbb445da9ca61281f, 0x2a8a6e45ae8edc97}, // 98 + {0xea1575143cf97226, 0xf52d09d71a3293bd}, // 99 + {0x924d692ca61be758, 0x593c2626705f9c56}, // 100 + {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c}, // 101 + {0xe498f455c38b997a, 0x0b6dfb9c0f956447}, // 102 + {0x8edf98b59a373fec, 0x4724bd4189bd5eac}, // 103 + {0xb2977ee300c50fe7, 0x58edec91ec2cb657}, // 104 + {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed}, // 105 + {0x8b865b215899f46c, 0xbd79e0d20082ee74}, // 106 + {0xae67f1e9aec07187, 0xecd8590680a3aa11}, // 107 + {0xda01ee641a708de9, 0xe80e6f4820cc9495}, // 108 + {0x884134fe908658b2, 0x3109058d147fdcdd}, // 109 + {0xaa51823e34a7eede, 0xbd4b46f0599fd415}, // 110 + {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a}, // 111 + {0x850fadc09923329e, 0x03e2cf6bc604ddb0}, // 112 + {0xa6539930bf6bff45, 0x84db8346b786151c}, // 113 + {0xcfe87f7cef46ff16, 0xe612641865679a63}, // 114 + {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e}, // 115 + {0xa26da3999aef7749, 0xe3be5e330f38f09d}, // 116 + {0xcb090c8001ab551c, 0x5cadf5bfd3072cc5}, // 117 + {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6}, // 118 + {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa}, // 119 + {0xc646d63501a1511d, 0xb281e1fd541501b8}, // 120 + {0xf7d88bc24209a565, 0x1f225a7ca91a4226}, // 121 + {0x9ae757596946075f, 0x3375788de9b06958}, // 122 + {0xc1a12d2fc3978937, 0x0052d6b1641c83ae}, // 123 + {0xf209787bb47d6b84, 0xc0678c5dbd23a49a}, // 124 + {0x9745eb4d50ce6332, 0xf840b7ba963646e0}, // 125 + {0xbd176620a501fbff, 0xb650e5a93bc3d898}, // 126 + {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe}, // 127 + {0x93ba47c980e98cdf, 0xc66f336c36b10137}, // 128 + {0xb8a8d9bbe123f017, 0xb80b0047445d4184}, // 129 + {0xe6d3102ad96cec1d, 0xa60dc059157491e5}, // 130 + {0x9043ea1ac7e41392, 0x87c89837ad68db2f}, // 131 + {0xb454e4a179dd1877, 0x29babe4598c311fb}, // 132 + {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a}, // 133 + {0x8ce2529e2734bb1d, 0x1899e4a65f58660c}, // 134 + {0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f}, // 135 + {0xdc21a1171d42645d, 0x76707543f4fa1f73}, // 136 + {0x899504ae72497eba, 0x6a06494a791c53a8}, // 137 + {0xabfa45da0edbde69, 0x0487db9d17636892}, // 138 + {0xd6f8d7509292d603, 0x45a9d2845d3c42b6}, // 139 + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2}, // 140 + {0xa7f26836f282b732, 0x8e6cac7768d7141e}, // 141 + {0xd1ef0244af2364ff, 0x3207d795430cd926}, // 142 + {0x8335616aed761f1f, 0x7f44e6bd49e807b8}, // 143 + {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6}, // 144 + {0xcd036837130890a1, 0x36dba887c37a8c0f}, // 145 + {0x802221226be55a64, 0xc2494954da2c9789}, // 146 + {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c}, // 147 + {0xc83553c5c8965d3d, 0x6f92829494e5acc7}, // 148 + {0xfa42a8b73abbf48c, 0xcb772339ba1f17f9}, // 149 + {0x9c69a97284b578d7, 0xff2a760414536efb}, // 150 + {0xc38413cf25e2d70d, 0xfef5138519684aba}, // 151 + {0xf46518c2ef5b8cd1, 0x7eb258665fc25d69}, // 152 + {0x98bf2f79d5993802, 0xef2f773ffbd97a61}, // 153 + {0xbeeefb584aff8603, 0xaafb550ffacfd8fa}, // 154 + {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38}, // 155 + {0x952ab45cfa97a0b2, 0xdd945a747bf26183}, // 156 + {0xba756174393d88df, 0x94f971119aeef9e4}, // 157 + {0xe912b9d1478ceb17, 0x7a37cd5601aab85d}, // 158 + {0x91abb422ccb812ee, 0xac62e055c10ab33a}, // 159 + {0xb616a12b7fe617aa, 0x577b986b314d6009}, // 160 + {0xe39c49765fdf9d94, 0xed5a7e85fda0b80b}, // 161 + {0x8e41ade9fbebc27d, 0x14588f13be847307}, // 162 + {0xb1d219647ae6b31c, 0x596eb2d8ae258fc8}, // 163 + {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb}, // 164 + {0x8aec23d680043bee, 0x25de7bb9480d5854}, // 165 + {0xada72ccc20054ae9, 0xaf561aa79a10ae6a}, // 166 + {0xd910f7ff28069da4, 0x1b2ba1518094da04}, // 167 + {0x87aa9aff79042286, 0x90fb44d2f05d0842}, // 168 + {0xa99541bf57452b28, 0x353a1607ac744a53}, // 169 + {0xd3fa922f2d1675f2, 0x42889b8997915ce8}, // 170 + {0x847c9b5d7c2e09b7, 0x69956135febada11}, // 171 + {0xa59bc234db398c25, 0x43fab9837e699095}, // 172 + {0xcf02b2c21207ef2e, 0x94f967e45e03f4bb}, // 173 + {0x8161afb94b44f57d, 0x1d1be0eebac278f5}, // 174 + {0xa1ba1ba79e1632dc, 0x6462d92a69731732}, // 175 + {0xca28a291859bbf93, 0x7d7b8f7503cfdcfe}, // 176 + {0xfcb2cb35e702af78, 0x5cda735244c3d43e}, // 177 + {0x9defbf01b061adab, 0x3a0888136afa64a7}, // 178 + {0xc56baec21c7a1916, 0x088aaa1845b8fdd0}, // 179 + {0xf6c69a72a3989f5b, 0x8aad549e57273d45}, // 180 + {0x9a3c2087a63f6399, 0x36ac54e2f678864b}, // 181 + {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd}, // 182 + {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5}, // 183 + {0x969eb7c47859e743, 0x9f644ae5a4b1b325}, // 184 + {0xbc4665b596706114, 0x873d5d9f0dde1fee}, // 185 + {0xeb57ff22fc0c7959, 0xa90cb506d155a7ea}, // 186 + {0x9316ff75dd87cbd8, 0x09a7f12442d588f2}, // 187 + {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb2f}, // 188 + {0xe5d3ef282a242e81, 0x8f1668c8a86da5fa}, // 189 + {0x8fa475791a569d10, 0xf96e017d694487bc}, // 190 + {0xb38d92d760ec4455, 0x37c981dcc395a9ac}, // 191 + {0xe070f78d3927556a, 0x85bbe253f47b1417}, // 192 + {0x8c469ab843b89562, 0x93956d7478ccec8e}, // 193 + {0xaf58416654a6babb, 0x387ac8d1970027b2}, // 194 + {0xdb2e51bfe9d0696a, 0x06997b05fcc0319e}, // 195 + {0x88fcf317f22241e2, 0x441fece3bdf81f03}, // 196 + {0xab3c2fddeeaad25a, 0xd527e81cad7626c3}, // 197 + {0xd60b3bd56a5586f1, 0x8a71e223d8d3b074}, // 198 + {0x85c7056562757456, 0xf6872d5667844e49}, // 199 + {0xa738c6bebb12d16c, 0xb428f8ac016561db}, // 200 + {0xd106f86e69d785c7, 0xe13336d701beba52}, // 201 + {0x82a45b450226b39c, 0xecc0024661173473}, // 202 + {0xa34d721642b06084, 0x27f002d7f95d0190}, // 203 + {0xcc20ce9bd35c78a5, 0x31ec038df7b441f4}, // 204 + {0xff290242c83396ce, 0x7e67047175a15271}, // 205 + {0x9f79a169bd203e41, 0x0f0062c6e984d386}, // 206 + {0xc75809c42c684dd1, 0x52c07b78a3e60868}, // 207 + {0xf92e0c3537826145, 0xa7709a56ccdf8a82}, // 208 + {0x9bbcc7a142b17ccb, 0x88a66076400bb691}, // 209 + {0xc2abf989935ddbfe, 0x6acff893d00ea435}, // 210 + {0xf356f7ebf83552fe, 0x0583f6b8c4124d43}, // 211 + {0x98165af37b2153de, 0xc3727a337a8b704a}, // 212 + {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c}, // 213 + {0xeda2ee1c7064130c, 0x1162def06f79df73}, // 214 + {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8}, // 215 + {0xb9a74a0637ce2ee1, 0x6d953e2bd7173692}, // 216 + {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437}, // 217 + {0x910ab1d4db9914a0, 0x1d9c9892400a22a2}, // 218 + {0xb54d5e4a127f59c8, 0x2503beb6d00cab4b}, // 219 + {0xe2a0b5dc971f303a, 0x2e44ae64840fd61d}, // 220 + {0x8da471a9de737e24, 0x5ceaecfed289e5d2}, // 221 + {0xb10d8e1456105dad, 0x7425a83e872c5f47}, // 222 + {0xdd50f1996b947518, 0xd12f124e28f77719}, // 223 + {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f}, // 224 + {0xace73cbfdc0bfb7b, 0x636cc64d1001550b}, // 225 + {0xd8210befd30efa5a, 0x3c47f7e05401aa4e}, // 226 + {0x8714a775e3e95c78, 0x65acfaec34810a71}, // 227 + {0xa8d9d1535ce3b396, 0x7f1839a741a14d0d}, // 228 + {0xd31045a8341ca07c, 0x1ede48111209a050}, // 229 + {0x83ea2b892091e44d, 0x934aed0aab460432}, // 230 + {0xa4e4b66b68b65d60, 0xf81da84d5617853f}, // 231 + {0xce1de40642e3f4b9, 0x36251260ab9d668e}, // 232 + {0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019}, // 233 + {0xa1075a24e4421730, 0xb24cf65b8612f81f}, // 234 + {0xc94930ae1d529cfc, 0xdee033f26797b627}, // 235 + {0xfb9b7cd9a4a7443c, 0x169840ef017da3b1}, // 236 + {0x9d412e0806e88aa5, 0x8e1f289560ee864e}, // 237 + {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2}, // 238 + {0xf5b5d7ec8acb58a2, 0xae10af696774b1db}, // 239 + {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29}, // 240 + {0xbff610b0cc6edd3f, 0x17fd090a58d32af3}, // 241 + {0xeff394dcff8a948e, 0xddfc4b4cef07f5b0}, // 242 + {0x95f83d0a1fb69cd9, 0x4abdaf101564f98e}, // 243 + {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1}, // 244 + {0xea53df5fd18d5513, 0x84c86189216dc5ed}, // 245 + {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4}, // 246 + {0xb7118682dbb66a77, 0x3fbc8c33221dc2a1}, // 247 + {0xe4d5e82392a40515, 0x0fabaf3feaa5334a}, // 248 + {0x8f05b1163ba6832d, 0x29cb4d87f2a7400e}, // 249 + {0xb2c71d5bca9023f8, 0x743e20e9ef511012}, // 250 + {0xdf78e4b2bd342cf6, 0x914da9246b255416}, // 251 + {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e}, // 252 + {0xae9672aba3d0c320, 0xa184ac2473b529b1}, // 253 + {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e}, // 254 + {0x8865899617fb1871, 0x7e2fa67c7a658892}, // 255 + {0xaa7eebfb9df9de8d, 0xddbb901b98feeab7}, // 256 + {0xd51ea6fa85785631, 0x552a74227f3ea565}, // 257 + {0x8533285c936b35de, 0xd53a88958f87275f}, // 258 + {0xa67ff273b8460356, 0x8a892abaf368f137}, // 259 + {0xd01fef10a657842c, 0x2d2b7569b0432d85}, // 260 + {0x8213f56a67f6b29b, 0x9c3b29620e29fc73}, // 261 + {0xa298f2c501f45f42, 0x8349f3ba91b47b8f}, // 262 + {0xcb3f2f7642717713, 0x241c70a936219a73}, // 263 + {0xfe0efb53d30dd4d7, 0xed238cd383aa0110}, // 264 + {0x9ec95d1463e8a506, 0xf4363804324a40aa}, // 265 + {0xc67bb4597ce2ce48, 0xb143c6053edcd0d5}, // 266 + {0xf81aa16fdc1b81da, 0xdd94b7868e94050a}, // 267 + {0x9b10a4e5e9913128, 0xca7cf2b4191c8326}, // 268 + {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0}, // 269 + {0xf24a01a73cf2dccf, 0xbc633b39673c8cec}, // 270 + {0x976e41088617ca01, 0xd5be0503e085d813}, // 271 + {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18}, // 272 + {0xec9c459d51852ba2, 0xddf8e7d60ed1219e}, // 273 + {0x93e1ab8252f33b45, 0xcabb90e5c942b503}, // 274 + {0xb8da1662e7b00a17, 0x3d6a751f3b936243}, // 275 + {0xe7109bfba19c0c9d, 0x0cc512670a783ad4}, // 276 + {0x906a617d450187e2, 0x27fb2b80668b24c5}, // 277 + {0xb484f9dc9641e9da, 0xb1f9f660802dedf6}, // 278 + {0xe1a63853bbd26451, 0x5e7873f8a0396973}, // 279 + {0x8d07e33455637eb2, 0xdb0b487b6423e1e8}, // 280 + {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62}, // 281 + {0xdc5c5301c56b75f7, 0x7641a140cc7810fb}, // 282 + {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d}, // 283 + {0xac2820d9623bf429, 0x546345fa9fbdcd44}, // 284 + {0xd732290fbacaf133, 0xa97c177947ad4095}, // 285 + {0x867f59a9d4bed6c0, 0x49ed8eabcccc485d}, // 286 + {0xa81f301449ee8c70, 0x5c68f256bfff5a74}, // 287 + {0xd226fc195c6a2f8c, 0x73832eec6fff3111}, // 288 + {0x83585d8fd9c25db7, 0xc831fd53c5ff7eab}, // 289 + {0xa42e74f3d032f525, 0xba3e7ca8b77f5e55}, // 290 + {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb}, // 291 + {0x80444b5e7aa7cf85, 0x7980d163cf5b81b3}, // 292 + {0xa0555e361951c366, 0xd7e105bcc332621f}, // 293 + {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7}, // 294 + {0xfa856334878fc150, 0xb14f98f6f0feb951}, // 295 + {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3}, // 296 + {0xc3b8358109e84f07, 0x0a862f80ec4700c8}, // 297 + {0xf4a642e14c6262c8, 0xcd27bb612758c0fa}, // 298 + {0x98e7e9cccfbd7dbd, 0x8038d51cb897789c}, // 299 + {0xbf21e44003acdd2c, 0xe0470a63e6bd56c3}, // 300 + {0xeeea5d5004981478, 0x1858ccfce06cac74}, // 301 + {0x95527a5202df0ccb, 0x0f37801e0c43ebc8}, // 302 + {0xbaa718e68396cffd, 0xd30560258f54e6ba}, // 303 + {0xe950df20247c83fd, 0x47c6b82ef32a2069}, // 304 + {0x91d28b7416cdd27e, 0x4cdc331d57fa5441}, // 305 + {0xb6472e511c81471d, 0xe0133fe4adf8e952}, // 306 + {0xe3d8f9e563a198e5, 0x58180fddd97723a6}, // 307 + {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648}, // 308 + {0xb201833b35d63f73, 0x2cd2cc6551e513da}, // 309 + {0xde81e40a034bcf4f, 0xf8077f7ea65e58d1}, // 310 + {0x8b112e86420f6191, 0xfb04afaf27faf782}, // 311 + {0xadd57a27d29339f6, 0x79c5db9af1f9b563}, // 312 + {0xd94ad8b1c7380874, 0x18375281ae7822bc}, // 313 + {0x87cec76f1c830548, 0x8f2293910d0b15b5}, // 314 + {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb22}, // 315 + {0xd433179d9c8cb841, 0x5fa60692a46151eb}, // 316 + {0x849feec281d7f328, 0xdbc7c41ba6bcd333}, // 317 + {0xa5c7ea73224deff3, 0x12b9b522906c0800}, // 318 + {0xcf39e50feae16bef, 0xd768226b34870a00}, // 319 + {0x81842f29f2cce375, 0xe6a1158300d46640}, // 320 + {0xa1e53af46f801c53, 0x60495ae3c1097fd0}, // 321 + {0xca5e89b18b602368, 0x385bb19cb14bdfc4}, // 322 + {0xfcf62c1dee382c42, 0x46729e03dd9ed7b5}, // 323 + {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d1}, // 324 + {0xc5a05277621be293, 0xc7098b7305241885}, // 325 + {0xf70867153aa2db38, 0xb8cbee4fc66d1ea7}, // 326 + {0x9a65406d44a5c903, 0x737f74f1dc043328}, // 327 + {0xc0fe908895cf3b44, 0x505f522e53053ff2}, // 328 + {0xf13e34aabb430a15, 0x647726b9e7c68fef}, // 329 + {0x96c6e0eab509e64d, 0x5eca783430dc19f5}, // 330 + {0xbc789925624c5fe0, 0xb67d16413d132072}, // 331 + {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e88f}, // 332 + {0x933e37a534cbaae7, 0x8e91b962f7b6f159}, // 333 + {0xb80dc58e81fe95a1, 0x723627bbb5a4adb0}, // 334 + {0xe61136f2227e3b09, 0xcec3b1aaa30dd91c}, // 335 + {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b1}, // 336 + {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19d}, // 337 + {0xe0accfa875af45a7, 0x93eb1b80a33b8605}, // 338 + {0x8c6c01c9498d8b88, 0xbc72f130660533c3}, // 339 + {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b4}, // 340 + {0xdb68c2ca82ed2a05, 0xa67398db9f6820e1}, // 341 + }, + // .fixed_layouts = + { + // clang-format off + // dec_exp = -4 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}, + .last_digit_pos = {15, 16}) + .start_pos = 5, .point_pos = 1, .shift_pos = 1, + .end_pos = {1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17}}, + // dec_exp = -3 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}, + .last_digit_pos = {15, 16}) + .start_pos = 4, .point_pos = 1, .shift_pos = 1, + .end_pos = {1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17}}, + // dec_exp = -2 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}, + .last_digit_pos = {15, 16}) + .start_pos = 3, .point_pos = 1, .shift_pos = 1, + .end_pos = {1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17}}, + // dec_exp = -1 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}, + .last_digit_pos = {15, 16}) + .start_pos = 2, .point_pos = 1, .shift_pos = 1, + .end_pos = {1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17}}, + // dec_exp = 0 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, ~0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {0, ~0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 1, .shift_pos = 2, + .end_pos = {1, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18}}, + // dec_exp = 1 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, ~0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {0, 1, ~0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 2, .shift_pos = 3, + .end_pos = {2, 2, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18}}, + // dec_exp = 2 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, ~0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {0, 1, 2, ~0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 3, .shift_pos = 4, + .end_pos = {3, 3, 3, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18}}, + // dec_exp = 3 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, ~0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {0, 1, 2, 3, ~0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 4, .shift_pos = 5, + .end_pos = {4, 4, 4, 4, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18}}, + // dec_exp = 4 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, ~0, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {0, 1, 2, 3, 4, ~0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 5, .shift_pos = 6, + .end_pos = {5, 5, 5, 5, 5, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18}}, + // dec_exp = 5 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, ~0, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {0, 1, 2, 3, 4, 5, ~0, 6, 7, 8, 9, 10, 11, 12, 13, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 6, .shift_pos = 7, + .end_pos = {6, 6, 6, 6, 6, 6, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18}}, + // dec_exp = 6 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, ~0, 8, 9, 10, 11, 12, 13, 14, 15}, + {0, 1, 2, 3, 4, 5, 6, ~0, 7, 8, 9, 10, 11, 12, 13, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 7, .shift_pos = 8, + .end_pos = {7, 7, 7, 7, 7, 7, 7, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18}}, + // dec_exp = 7 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, 8, ~0, 9, 10, 11, 12, 13, 14, 15}, + {0, 1, 2, 3, 4, 5, 6, 7, ~0, 8, 9, 10, 11, 12, 13, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 8, .shift_pos = 9, + .end_pos = {8, 8, 8, 8, 8, 8, 8, 8, 10, + 11, 12, 13, 14, 15, 16, 17, 18}}, + // dec_exp = 8 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, 8, 9, ~0, 10, 11, 12, 13, 14, 15}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, ~0, 9, 10, 11, 12, 13, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 9, .shift_pos = 10, + .end_pos = {9, 9, 9, 9, 9, 9, 9, 9, 9, + 11, 12, 13, 14, 15, 16, 17, 18}}, + // dec_exp = 9 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ~0, 11, 12, 13, 14, 15}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ~0, 10, 11, 12, 13, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 10, .shift_pos = 11, + .end_pos = {10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 12, 13, 14, 15, 16, 17, 18}}, + // dec_exp = 10 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ~0, 12, 13, 14, 15}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ~0, 11, 12, 13, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 11, .shift_pos = 12, + .end_pos = {11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 13, 14, 15, 16, 17, 18}}, + // dec_exp = 11 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, ~0, 13, 14, 15}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ~0, 12, 13, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 12, .shift_pos = 13, + .end_pos = {12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 14, 15, 16, 17, 18}}, + // dec_exp = 12 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, ~0, 14, 15}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, ~0, 13, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 13, .shift_pos = 14, + .end_pos = {13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 15, 16, 17, 18}}, + // dec_exp = 13 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, ~0, 15}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, ~0, 14}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 14, .shift_pos = 15, + .end_pos = {14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 16, 17, 18}}, + // dec_exp = 14 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ~0}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, ~0}}, + .last_digit_pos = {16, 17}) + .start_pos = 0, .point_pos = 15, .shift_pos = 16, + .end_pos = {15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 17, 18}}, + // dec_exp = 15 + {ZMIJ_FIXED_SSE( + .shuffle = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}, + .last_digit_pos = {15, 17}) + .start_pos = 0, .point_pos = 16, .shift_pos = 17, + .end_pos = {16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 18}}, + // clang-format on + }, + // .shift_shuffle = + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0}, +}; +#undef ZMIJ_FIXED_SSE + +// Returns the 128-bit significand of 10**dec_exp. +static uint128 get_pow10_significand(int dec_exp, const zmij_data* d) { + const int dec_exp_min = -307; + int index = dec_exp - dec_exp_min; + assert(index >= 0 && (size_t)index < sizeof(d->pow10_significands) / + sizeof(*d->pow10_significands)); + return d->pow10_significands[index]; +} + +typedef struct { + uint64_t bcd; + int len; +} bcd_result; + +#if ZMIJ_USE_NEON +typedef uint16x8_t digits_double_type; +#elif ZMIJ_USE_SSE +typedef __m128i digits_double_type; +#else +typedef struct { + uint64_t hi; + uint64_t lo; +} digits_double_type; +#endif + +typedef struct { + digits_double_type digits; + int num_digits; +} dec_digits_double; + +typedef struct { + uint64_t digits; + int num_digits; +} dec_digits_float; + +#if ZMIJ_USE_NEON +// Converts four numbers < 10000, one in each 32-bit lane, to BCD digits. +static ZMIJ_INLINE uint8x16_t to_bcd_4x4(int32x4_t efgh_abcd_mnop_ijkl, + const zmij_data* d) { + // Compiler barrier, or clang breaks the subsequent MLA into UADDW + MUL. + ZMIJ_ASM(("" : "+w"(efgh_abcd_mnop_ijkl))); + + int32x4_t ef_ab_mn_ij = + vqdmulhq_n_s32(efgh_abcd_mnop_ijkl, d->multipliers32[2]); + int16x8_t gh_ef_cd_ab_op_mn_kl_ij = vreinterpretq_s16_s32( + vmlaq_n_s32(efgh_abcd_mnop_ijkl, ef_ab_mn_ij, d->multipliers32[3])); + int16x8_t high_10s = + vqdmulhq_n_s16(gh_ef_cd_ab_op_mn_kl_ij, d->multipliers16[0]); + return vreinterpretq_u8_s16( + vmlaq_n_s16(gh_ef_cd_ab_op_mn_kl_ij, high_10s, d->multipliers16[1])); +} + +static ZMIJ_INLINE uint8x16_t to_unshuffled_digits(uint64_t value, + const zmij_data* d) { + uint64_t hundred_million = d->hundred_million; + uint64_t mul_const = d->mul_const; + + // Compiler barrier, or clang narrows the load to 32-bit and unpairs it. + ZMIJ_ASM(("" : "+r"(hundred_million))); + + // abcdefgh = value / 100000000, ijklmnop = value % 100000000. + uint64_t abcdefgh = lo64(uint128_rshift(umul128(value, mul_const), 90)); + uint64_t ijklmnop = value - abcdefgh * hundred_million; + + uint64x1_t ijklmnop_abcdefgh_64 = {(ijklmnop << 32) | abcdefgh}; + int32x2_t abcdefgh_ijklmnop = vreinterpret_s32_u64(ijklmnop_abcdefgh_64); + + int32x2_t abcd_ijkl = vreinterpret_s32_u32( + vshr_n_u32(vreinterpret_u32_s32( + vqdmulh_n_s32(abcdefgh_ijklmnop, d->multipliers32[0])), + 9)); + int32x2_t efgh_abcd_mnop_ijkl_32 = + vmla_n_s32(abcdefgh_ijklmnop, abcd_ijkl, d->multipliers32[1]); + + int32x4_t efgh_abcd_mnop_ijkl = vreinterpretq_s32_u32( + vshll_n_u16(vreinterpret_u16_s32(efgh_abcd_mnop_ijkl_32), 0)); + return to_bcd_4x4(efgh_abcd_mnop_ijkl, d); +} +#elif ZMIJ_USE_SSE +// Converts four numbers < 10000, one in each 32-bit lane, to BCD digits. +// Digits in each 32-bit lane will be in order for SSE2, reversed for SSE4.1. +static ZMIJ_INLINE __m128i to_bcd_4x4(__m128i y, const zmij_data* d) { + const __m128i div100 = _mm_load_si128((const __m128i*)&d->div100); + const __m128i div10 = _mm_load_si128((const __m128i*)&d->div10); +# if ZMIJ_USE_SSE4_1 + const __m128i neg100 = _mm_load_si128((const __m128i*)&d->neg100); + const __m128i neg10 = _mm_load_si128((const __m128i*)&d->neg10); + + // _mm_mullo_epi32 is SSE 4.1 + __m128i z = _mm_add_epi64( + y, + _mm_mullo_epi32(neg100, _mm_srli_epi32(_mm_mulhi_epu16(y, div100), 3))); + return _mm_add_epi16(z, _mm_mullo_epi16(neg10, _mm_mulhi_epu16(z, div10))); +# else + const __m128i hundred = _mm_load_si128((const __m128i*)&d->hundred); + const __m128i moddiv10 = _mm_load_si128((const __m128i*)&d->moddiv10); + + __m128i y_div_100 = _mm_srli_epi16(_mm_mulhi_epu16(y, div100), 3); + __m128i y_mod_100 = _mm_sub_epi16(y, _mm_mullo_epi16(y_div_100, hundred)); + __m128i z = _mm_or_si128(_mm_slli_epi32(y_mod_100, 16), y_div_100); + return _mm_sub_epi16(_mm_slli_epi16(z, 8), + _mm_mullo_epi16(moddiv10, _mm_mulhi_epu16(z, div10))); +# endif // ZMIJ_USE_SSE4_1 +} +#endif // ZMIJ_USE_SSE + +static ZMIJ_MAYBE_UNUSED ZMIJ_INLINE int ctz(uint64_t x) { +#if ZMIJ_HAS_BUILTIN(__builtin_ctzll) + return __builtin_ctzll(x); +#elif ZMIJ_MSC_VER + unsigned long index; + _BitScanForward64(&index, x); + return (int)index; +#else + int n = 0; + while ((x & 1) == 0) { + x >>= 1; + ++n; + } + return n; +#endif +} + +// Converts an 8-decimal-digit value to its BCD representation along with the +// number of trailing-zero-trimmed bytes. +static ZMIJ_INLINE bcd_result to_bcd8(uint64_t abcdefgh, const zmij_data* d) { + (void)d; // Unused on the scalar path. + if (!ZMIJ_USE_SSE && !ZMIJ_USE_NEON) { + // An optimization from Xiang JunBo. + // Three steps BCD. Base 10000 -> base 100 -> base 10. + // div and mod are evaluated simultaneously as, e.g. + // (abcdefgh / 10000) << 32 + (abcdefgh % 10000) + // == abcdefgh + (2**32 - 10000) * (abcdefgh / 10000))) + // where the division on the RHS is implemented by the multiply + shift + // trick and the fractional bits are masked away. + uint64_t abcd_efgh = + abcdefgh + neg10k * ((abcdefgh * div10k_sig) >> div10k_exp); + uint64_t ab_cd_ef_gh = + abcd_efgh + + neg100 * (((abcd_efgh * div100_sig) >> div100_exp) & 0x7f0000007f); + uint64_t a_b_c_d_e_f_g_h = + ab_cd_ef_gh + + neg10 * (((ab_cd_ef_gh * div10_sig) >> div10_exp) & 0xf000f000f000f); + uint64_t bcd = is_big_endian ? a_b_c_d_e_f_g_h : bswap64(a_b_c_d_e_f_g_h); + bcd_result result = {bcd, count_trailing_nonzeros(bcd)}; + return result; + } + +#if ZMIJ_USE_NEON + uint64_t abcd_efgh_64 = + abcdefgh + neg10k * ((abcdefgh * div10k_sig) >> div10k_exp); + int32x4_t abcd_efgh = vcombine_s32( + vreinterpret_s32_u64(vcreate_u64(abcd_efgh_64)), vdup_n_s32(0)); + uint8x16_t digits_128 = to_bcd_4x4(abcd_efgh, d); + uint8x8_t digits = vget_low_u8(digits_128); + uint64_t bcd = vget_lane_u64(vreinterpret_u64_u8(vrev64_u8(digits)), 0); + bcd_result result = {bcd, count_trailing_nonzeros(bcd)}; + return result; +#elif ZMIJ_USE_SSE4_1 + uint64_t abcd_efgh = + abcdefgh + neg10k * ((abcdefgh * div10k_sig) >> div10k_exp); + uint64_t unshuffled_bcd = + _mm_cvtsi128_si64(to_bcd_4x4(_mm_set_epi64x(0, abcd_efgh), d)); + int len = unshuffled_bcd ? 8 - ctz(unshuffled_bcd) / 8 : 0; + bcd_result result = {bswap64(unshuffled_bcd), len}; + return result; +#elif ZMIJ_USE_SSE + // Evaluate the 4-digit limbs and arrange them such that we get a result + // which is in the correct order. + uint64_t abcd_efgh = + (abcdefgh << 32) - (uint64_t)((10000ull << 32) - 1) * + ((abcdefgh * div10k_sig) >> div10k_exp); + __m128i v = to_bcd_4x4(_mm_set_epi64x(0, abcd_efgh), d); +# if ZMIJ_X86_64 + uint64_t bcd = _mm_cvtsi128_si64(v); +# else + uint64_t bcd = (uint64_t)_mm_cvtsi128_si32(_mm_srli_si128(v, 4)) << 32 | + (uint32_t)_mm_cvtsi128_si32(v); +# endif + bcd_result result = {bcd, count_trailing_nonzeros(bcd)}; + return result; +#endif // ZMIJ_USE_SSE +} + +// Converts a value (up to 8 decimal digits) to BCD representation. +static ZMIJ_INLINE dec_digits_float to_digits_float(uint64_t value, + const zmij_data* d) { + bcd_result result = to_bcd8(value, d); + dec_digits_float dig = {result.bcd + zeros, result.len}; + return dig; +} + +// Converts a value (up to 16 decimal digits) to BCD representation. +static ZMIJ_INLINE dec_digits_double to_digits_double(uint64_t value, + const zmij_data* d) { +#if !ZMIJ_USE_NEON && !ZMIJ_USE_SSE + uint32_t hi = (uint32_t)(value / 100000000); + uint32_t lo = (uint32_t)(value % 100000000); + bcd_result hi_bcd = to_bcd8(hi, d); + if (lo == 0) { + digits_double_type dd = {hi_bcd.bcd + zeros, zeros}; + dec_digits_double result = {dd, hi_bcd.len}; + return result; + } + bcd_result lo_bcd = to_bcd8(lo, d); + digits_double_type dd = {hi_bcd.bcd + zeros, lo_bcd.bcd + zeros}; + dec_digits_double result = {dd, 8 + lo_bcd.len}; + return result; +#elif ZMIJ_USE_NEON + uint8x16_t unshuffled_digits = to_unshuffled_digits(value, d); + uint8x16_t digits = vrev64q_u8(unshuffled_digits); + uint16x8_t str = vaddq_u16(vreinterpretq_u16_u8(digits), + vreinterpretq_u16_s8(vdupq_n_s8('0'))); + uint16x8_t is_not_zero = + vreinterpretq_u16_u8(vcgtzq_s8(vreinterpretq_s8_u8(digits))); + uint64_t nonzero_mask = + vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(is_not_zero, 4)), 0); + dec_digits_double result = { + str, 16 - (nonzero_mask == 0 ? 64 : clz(nonzero_mask)) / 4}; + // Match C++: 16 - clz/4 (treating 0 specially to avoid UB). + result.num_digits = + nonzero_mask == 0 ? 0 : 16 - (int)(clz(nonzero_mask) >> 2); + return result; +#else // ZMIJ_USE_SSE + uint32_t hi = (uint32_t)(value / 100000000); + uint32_t lo = (uint32_t)(value % 100000000); + + const __m128i div10k = _mm_load_si128((const __m128i*)&d->div10k); + const __m128i neg10k_v = _mm_load_si128((const __m128i*)&d->neg10k); + __m128i x = _mm_set_epi64x(hi, lo); + __m128i y = _mm_add_epi64( + x, _mm_mul_epu32(neg10k_v, + _mm_srli_epi64(_mm_mul_epu32(x, div10k), div10k_exp))); + + // Shuffle to ensure correctly ordered result from SSE2 path. + if (!ZMIJ_USE_SSE4_1) y = _mm_shuffle_epi32(y, _MM_SHUFFLE(0, 1, 2, 3)); + + __m128i bcd = to_bcd_4x4(y, d); + const __m128i zeros_v = _mm_load_si128((const __m128i*)&d->zeros_v); + + // Computed against current bcd (rather than the post-bswap bcd) so the mask + // is derived in parallel with the shuffle on the SSE4.1 path. + uint64_t mask = + (uint64_t)_mm_movemask_epi8(_mm_cmpgt_epi8(bcd, _mm_setzero_si128())); + // Trailing zeros are in the low bits for SSE4.1, the high bits for SSE2. + int len = ZMIJ_USE_SSE4_1 ? (mask == 0 ? 0 : 16 - ctz(mask)) + : (mask == 0 ? 0 : 64 - clz(mask)); +# if ZMIJ_USE_SSE4_1 + bcd = _mm_shuffle_epi8(bcd, + _mm_load_si128((const __m128i*)&d->bswap)); // SSSE3 +# endif + dec_digits_double result = {_mm_or_si128(bcd, zeros_v), len}; + return result; +#endif // ZMIJ_USE_SSE +} + +// Writes 16 BCD characters to `buffer`. When drop_leading_zero is set, shifts +// the digits left by 1 (used to drop the leading '0' of a 16-digit +// significand). On SIMD, the shift is folded into the digit shuffle. +static ZMIJ_INLINE void write_digits_double(char* buffer, + digits_double_type digits, + bool drop_leading_zero, + const zmij_data* d) { + (void)d; // Unused on the scalar path. + if (!ZMIJ_USE_NEON && !ZMIJ_USE_SSE4_1) { + memcpy(buffer, &digits, sizeof(digits)); + memmove(buffer, buffer + drop_leading_zero, sizeof(digits)); + return; + } +#if ZMIJ_USE_NEON + uint8x16_t shuffle = vld1q_u8(d->shift_shuffle + drop_leading_zero); + uint8x16_t shifted = vqtbl1q_u8(vreinterpretq_u8_u16(digits), shuffle); + vst1q_u8((uint8_t*)buffer, shifted); +#elif ZMIJ_USE_SSE4_1 + __m128i shuffle = + _mm_loadu_si128((const __m128i*)(d->shift_shuffle + drop_leading_zero)); + _mm_storeu_si128((__m128i*)buffer, _mm_shuffle_epi8(digits, shuffle)); +#endif +} + +static ZMIJ_INLINE void write_digits_float(char* buffer, uint64_t digits, + bool drop_leading_zero) { + memcpy(buffer, &digits, sizeof(digits)); + memmove(buffer, buffer + drop_leading_zero, sizeof(digits)); +} + +// Writes "inf"/"nan" with one 4-byte store, so the buffer must hold 4 bytes. +static char* write_inf_nan(char* buffer, bool is_nan) { + memcpy(buffer, is_nan ? "nan" : "inf", 4); + return buffer + 3; +} + +// Writes zero in fixed notation, e.g. "0.000" (or "0" when precision is 0). +static ZMIJ_INLINE char* write_zero(char* buffer, int precision) { + *buffer++ = '0'; + if (precision == 0) return buffer; + *buffer = '.'; + memset(buffer + 1, '0', (size_t)precision); + return buffer + 1 + precision; +} + +// Writes the exponent as 'e', a sign and at least two digits (e.g. e+05). +// Only double reaches a third digit. +static ZMIJ_INLINE char* write_exp(char* buffer, int dec_exp, + const int num_bits) { + buffer = write2(buffer, 'e', dec_exp >= 0 ? '+' : '-'); + uint32_t abs_exp = dec_exp >= 0 ? (uint32_t)dec_exp : (uint32_t)-dec_exp; + if (num_bits == 64) { + uint32_t hi = use_umul128_hi64 + ? (uint32_t)umul128_hi64(abs_exp, 0x290000000000000) + : (abs_exp * div100_sig) >> div100_exp; + *buffer = (char)('0' + hi); // hundreds (0-3) + buffer += abs_exp >= 100; + abs_exp -= hi * 100; + } + memcpy(buffer, digits2(abs_exp), 2); + return buffer + 2; +} + +// Writes the decimal exponent as 'e', a sign and at least two digits, up to +// four (e.g. e+05 or e+4932). Used by the big-integer path, which is not on a +// hot path and so does not specialize on the type's exponent range. +static char* write_big_exp(char* buffer, int dec_exp) { + buffer = write2(buffer, 'e', dec_exp >= 0 ? '+' : '-'); + uint32_t abs_exp = dec_exp >= 0 ? (uint32_t)dec_exp : (uint32_t)-dec_exp; + uint32_t hi = (abs_exp * div100_sig) >> div100_exp; // abs_exp / 100 + *buffer = (char)('0' + hi / 10); + buffer += hi >= 10; + *buffer = (char)('0' + hi % 10); + buffer += abs_exp >= 100; + memcpy(buffer, digits2(abs_exp - hi * 100), 2); + return buffer + 2; +} + +// Writes the binary exponent as 'p', a sign and at least one digit (e.g. p+3). +static char* write_hex_exp(char* buffer, int bin_exp) { + buffer = write2(buffer, 'p', bin_exp < 0 ? '-' : '+'); + unsigned abs_exp = (unsigned)(bin_exp < 0 ? -bin_exp : bin_exp); + char digits[8]; + char* p = digits + sizeof(digits); + do { + *--p = (char)('0' + abs_exp % 10); + abs_exp /= 10; + } while (abs_exp != 0); + size_t n = (size_t)(digits + sizeof(digits) - p); + memcpy(buffer, p, n); + return buffer + n; +} + +// Scales a subnormal significand up so that its leading bit lands at the +// implicit-bit position, adjusting the raw exponent to keep the value the same. +static ZMIJ_INLINE void normalize(uint64_t* bin_sig, int64_t* bin_exp, + int num_sig_bits) { + // clz counts from the top of its 64-bit operand, so measure from that width. + int shift = clz(*bin_sig) - (64 - 1 - num_sig_bits); + *bin_sig <<= shift; + *bin_exp = 1 - shift; +} + +// Writes num_digits significant digits in scientific form, d[.ddd]e±EE +// (trailing zeros kept), from the top 16 BCD digits `digits` and low two digits +// lo. dec_exp is the leading digit's exponent. +static ZMIJ_INLINE char* write_scientific_digits( + char* buffer, digits_double_type digits, unsigned lo, int num_digits, + int dec_exp, const int num_bits, const zmij_data* d) { + write_digits_double(buffer + 1, digits, false, d); + memcpy(buffer + 17, digits2(lo), 2); + buffer[0] = buffer[1]; + buffer[1] = '.'; // Overwritten by the exponent when num_digits is 1. + return write_exp(buffer + num_digits + (num_digits > 1), dec_exp, num_bits); +} + +// An output sink that appends to `out`, discarding anything past `end`. +typedef struct { + char* out; + char* end; + size_t count; // total chars, including any dropped past `end` +} writer; + +// The write functions return `count`, the running total of requested chars. +static ZMIJ_INLINE size_t write_char(writer* w, char c) { + if (w->out < w->end) *w->out++ = c; + return ++w->count; +} + +// Copies at most `end - out` chars of `s[0..n)`; `n` must be non-negative. +static ZMIJ_INLINE size_t write_chars(writer* w, const char* s, int n) { + assert(n >= 0); + size_t take = n < w->end - w->out ? (size_t)n : (size_t)(w->end - w->out); + memcpy(w->out, s, take); + w->out += take; + return w->count += (size_t)n; +} + +// Writes at most `end - out` copies of '0'; `n` must be non-negative. +static ZMIJ_INLINE size_t write_zeros(writer* w, int n) { + assert(n >= 0); + size_t take = n < w->end - w->out ? (size_t)n : (size_t)(w->end - w->out); + memset(w->out, '0', take); + w->out += take; + return w->count += (size_t)n; +} + +// Returns true if any character in [first, last) is not '0'. +static bool any_nonzero(const char* first, const char* last) { + for (; first < last; ++first) + if (*first != '0') return true; + return false; +} + +typedef struct { + long long sig; + int exp; + int last_digit; + bool has_last_digit; +} to_decimal_result; + +// Here be 🐉s. +// Converts a binary FP number bin_sig * 2**bin_exp to the shortest decimal +// representation, where bin_exp = raw_exp - exp_offset. +static ZMIJ_INLINE to_decimal_result to_decimal_double(uint64_t bin_sig, + int64_t raw_exp, + bool regular, + const zmij_data* d) { + int64_t bin_exp = raw_exp - double_exp_offset; + const int extra_shift = 6; + + if (ZMIJ_UNLIKELY(!regular)) { + int dec_exp = compute_dec_exp((int)bin_exp, false); + unsigned char shift = + compute_exp_shift((int)bin_exp, dec_exp + 1) + extra_shift; + uint128 pow10 = get_pow10_significand(-dec_exp - 1, d); + uint128 p = umul192_hi128(pow10.hi, pow10.lo, bin_sig << shift); + + long long integral = p.hi >> extra_shift; + uint64_t fractional = (p.hi << (64 - extra_shift)) | (p.lo >> extra_shift); + + uint64_t half_ulp = pow10.hi >> (extra_shift + 1 - shift); + bool round_up = half_ulp > ~(uint64_t)0 - fractional; + bool round_down = (half_ulp >> 1) > fractional; + integral += round_up; + + int digit = (int)umul128_add_hi64(fractional, 10, ((uint64_t)1 << 63) - 1); + int lo = + (int)umul128_add_hi64(fractional - (half_ulp >> 1), 10, ~(uint64_t)0); + if (digit < lo) digit = lo; + to_decimal_result result = {integral, dec_exp, digit, + (round_up + round_down) == 0}; + return result; + } + + const uint64_t log10_2_sig = 78913; + const int log10_2_exp = 18; + int dec_exp = + use_umul128_hi64 + ? (int)umul128_hi64(bin_exp, log10_2_sig << (64 - log10_2_exp)) + : compute_dec_exp((int)bin_exp, true); + ZMIJ_ASM(("" : "+r"(dec_exp))); // Force 32-bit reg for sxtw addressing. + unsigned char shift = d->exp_shifts[bin_exp + double_exp_offset]; + uint64_t even = 1 - (bin_sig & 1); + + // An optimization by Xiang JunBo: + // Scale by 10**(-dec_exp-1) to directly produce the shorter candidate + // (15-16 digits), deriving the extra digit from the fractional part. + // This eliminates div10 from the critical path. + uint128 pow10 = get_pow10_significand(-dec_exp - 1, d); + uint128 p = umul192_hi128(pow10.hi, pow10.lo, bin_sig << shift); + + long long integral = p.hi >> extra_shift; + uint64_t fractional = (p.hi << (64 - extra_shift)) | (p.lo >> extra_shift); + + uint64_t half_ulp = (pow10.hi >> (extra_shift + 1 - shift)) + even; + bool round_up = fractional + half_ulp < fractional; + bool round_down = half_ulp > fractional; + integral += round_up; // Compute integral before digit. + + // Derive the extra digit from the fractional part (parallel with rounding). + int digit = (int)umul128_add_hi64(fractional, 10, d->biased_half); + if (ZMIJ_UNLIKELY(fractional == (1ull << 62))) digit = 2; // Round 2.5 to 2. + to_decimal_result result = {integral, dec_exp, digit, + (round_up + round_down) == 0}; + return result; +} + +// Converts a binary FP number bin_sig * 2**bin_exp to the shortest decimal +// representation, where bin_exp = raw_exp - exp_offset. +static ZMIJ_INLINE to_decimal_result to_decimal_float(uint32_t bin_sig, + int64_t raw_exp, + bool regular, + const zmij_data* d) { + int64_t bin_exp = raw_exp - float_exp_offset; + const int irregular_extra_shift = 6; + + if (ZMIJ_UNLIKELY(!regular)) { + int dec_exp = compute_dec_exp((int)bin_exp, false); + unsigned char shift = + compute_exp_shift((int)bin_exp, dec_exp + 1) + irregular_extra_shift; + uint128 pow10 = get_pow10_significand(-dec_exp - 1, d); + uint128 p = umul192_hi128(pow10.hi, pow10.lo, (uint64_t)bin_sig << shift); + + long long integral = p.hi >> irregular_extra_shift; + uint64_t fractional = (p.hi << (64 - irregular_extra_shift)) | + (p.lo >> irregular_extra_shift); + + uint64_t half_ulp = pow10.hi >> (irregular_extra_shift + 1 - shift); + bool round_up = half_ulp > ~(uint64_t)0 - fractional; + bool round_down = (half_ulp >> 1) > fractional; + integral += round_up; + + int digit = (int)umul128_add_hi64(fractional, 10, ((uint64_t)1 << 63) - 1); + int lo = + (int)umul128_add_hi64(fractional - (half_ulp >> 1), 10, ~(uint64_t)0); + if (digit < lo) digit = lo; + to_decimal_result result = {integral, dec_exp, digit, + (round_up + round_down) == 0}; + return result; + } + + const uint64_t log10_2_sig = 78913; + const int log10_2_exp = 18; + int dec_exp = + use_umul128_hi64 + ? (int)umul128_hi64(bin_exp, log10_2_sig << (64 - log10_2_exp)) + : compute_dec_exp((int)bin_exp, true); + unsigned char shift = d->exp_shifts[bin_exp + double_exp_offset]; + uint64_t even = 1 - (bin_sig & 1); + + const int extra_shift = 34; + shift += extra_shift - irregular_extra_shift; + uint64_t pow10_hi = get_pow10_significand(-dec_exp - 1, d).hi; + uint64_t p = umul128_hi64(pow10_hi + 1, (uint64_t)bin_sig << shift); + + long long integral = p >> extra_shift; + uint64_t fractional = p & (((uint64_t)1 << extra_shift) - 1); + + uint64_t half_ulp = (pow10_hi >> (65 - shift)) + even; + bool round_up = (fractional + half_ulp) >> extra_shift; + bool round_down = half_ulp > fractional; + integral += round_up; + + int digit = (int)((fractional * 10 + ((uint64_t)1 << (extra_shift - 1))) >> + extra_shift); + if (ZMIJ_UNLIKELY(fractional == ((uint64_t)1 << (extra_shift - 2)))) + digit = 2; // Round 2.5 to 2. + to_decimal_result result = {integral, dec_exp, digit, + (round_up + round_down) == 0}; + return result; +} + +typedef struct { + uint64_t integral; + uint64_t fraction; + uint64_t fraction_tail; +} scaled_value; + +// Scales bin_sig * 2**bin_exp by 10**-dec_exp, retaining the fractional part. +static ZMIJ_INLINE scaled_value scale(uint64_t bin_sig, int bin_exp, + int dec_exp, int num_sig_bits, + const zmij_data* d) { + const int shift = 64 - (num_sig_bits + 1); + int point_shift = shift - compute_exp_shift(bin_exp, dec_exp); + assert(point_shift >= 1 && point_shift < 64); + uint128 pow10 = get_pow10_significand(-dec_exp, d); + uint128 p = umul192_hi128(pow10.hi, pow10.lo + 1, bin_sig << shift); + scaled_value result = {p.hi >> point_shift, p.hi << (64 - point_shift), p.lo}; + return result; +} + +// Rounds a retained fixed-point value to nearest, ties to even. +static ZMIJ_INLINE uint64_t round_even(scaled_value value) { + const uint64_t half = (uint64_t)1 << 63; + bool round_up = half < value.fraction || + (value.fraction == half && + (value.fraction_tail != 0 || (value.integral & 1) != 0)); + return value.integral + round_up; +} + +// A value rounded to `precision` significant digits. +typedef struct { + uint64_t sig; // precision significant digits, zero-padded right to 18. + int lead_exp; // Leading digit's decimal exponent. +} precision_decimal; + +// Converts a binary FP number bin_sig * 2**bin_exp to a correctly rounded +// decimal with `precision` significant digits. +static ZMIJ_INLINE precision_decimal to_decimal_precision(uint64_t bin_sig, + int bin_exp, + int precision, + int num_sig_bits, + const zmij_data* d) { + int dec_exp = compute_dec_exp(bin_exp + num_sig_bits, true) - (precision - 1); + scaled_value scaled = scale(bin_sig, bin_exp, dec_exp, num_sig_bits, d); + uint64_t dec_sig = round_even(scaled); + + if (dec_sig >= pow10s[precision]) { + // Round one decimal place coarser; the fraction disambiguates a trailing 5. + dec_sig = scaled.integral / 10; + uint64_t last_digit = scaled.integral - dec_sig * 10; + bool has_fraction = (scaled.fraction | scaled.fraction_tail) != 0; + bool round_up = 5 < last_digit || + (last_digit == 5 && (has_fraction || (dec_sig & 1) != 0)); + dec_sig += round_up; + ++dec_exp; + } + precision_decimal result = {dec_sig * pow10s[18 - precision], + dec_exp + precision - 1}; + return result; +} + +// A minimal binary big integer (little-endian base-2**32 limbs) over caller- +// provided storage. +typedef struct { + uint32_t* limbs; + int num_limbs; // Significant limbs; 0 represents zero. + int max_limbs; // Available limbs in `limbs`. +} bigint; + +static void bigint_trim(bigint* n) { + while (n->num_limbs > 0 && n->limbs[n->num_limbs - 1] == 0) --n->num_limbs; +} + +// Callers must size `max_limbs` for the largest value the number will reach. +static bigint bigint_init(uint64_t value, uint32_t* buffer, int max_limbs) { + bigint n; + assert(max_limbs >= 2); + n.limbs = buffer; + n.max_limbs = max_limbs; + buffer[0] = (uint32_t)value; + buffer[1] = (uint32_t)(value >> 32); + n.num_limbs = 2; + bigint_trim(&n); + return n; +} + +// Shifts left by `n`. +static void bigint_shl(bigint* x, int n) { + assert(n >= 0); + if (x->num_limbs == 0) return; + uint32_t* limbs = x->limbs; + int limb_shift = n >> 5, bit_shift = n & 31; + assert(x->num_limbs + limb_shift + (bit_shift != 0) <= x->max_limbs); + if (bit_shift == 0) { + for (int i = x->num_limbs - 1; i >= 0; --i) + limbs[i + limb_shift] = limbs[i]; + x->num_limbs += limb_shift; + } else { + limbs[x->num_limbs + limb_shift] = + limbs[x->num_limbs - 1] >> (32 - bit_shift); + for (int i = x->num_limbs - 1; i > 0; --i) { + limbs[i + limb_shift] = + limbs[i] << bit_shift | limbs[i - 1] >> (32 - bit_shift); + } + limbs[limb_shift] = limbs[0] << bit_shift; + x->num_limbs += limb_shift + 1; + } + for (int i = 0; i < limb_shift; ++i) limbs[i] = 0; + bigint_trim(x); +} + +// Divides by 10**9 in place and returns the remainder. +static uint32_t bigint_divmod_1e9(bigint* x) { + uint64_t rem = 0; + for (int i = x->num_limbs - 1; i >= 0; --i) { + uint64_t div = rem << 32 | x->limbs[i]; + x->limbs[i] = (uint32_t)(div / 1000000000u); + rem = div % 1000000000u; + } + bigint_trim(x); + return (uint32_t)rem; +} + +// Multiplies in place by a nonzero `factor`. +static void bigint_mul(bigint* x, uint32_t factor) { + assert(factor != 0); + uint64_t carry = 0; + for (int i = 0; i < x->num_limbs; ++i) { + uint64_t product = (uint64_t)x->limbs[i] * factor + carry; + x->limbs[i] = (uint32_t)product; + carry = product >> 32; + } + if (carry == 0) return; + assert(x->num_limbs < x->max_limbs); + x->limbs[x->num_limbs++] = (uint32_t)carry; +} + +// Multiplies in place by 5**n. +static void bigint_mul_pow5(bigint* x, int n) { + assert(n >= 0); + static const uint32_t pow5[] = {1, 5, 25, 125, 625, + 3125, 15625, 78125, 390625, 1953125, + 9765625, 48828125, 244140625}; + while (n >= 13) { // 5**13 is the largest power of five below 2**32. + bigint_mul(x, 1220703125u); + n -= 13; + } + if (n != 0) bigint_mul(x, pow5[n]); +} + +// Emits n's decimal digits (most significant first) ending at `end`, consuming +// n, and returns a pointer to the first (most significant) digit. +static char* write_bigint_digits(bigint n, char* end) { + char* p = end; + uint32_t group = bigint_divmod_1e9(&n); + while (n.num_limbs != 0) { // Lower groups keep all 9 digits. + for (int k = 0; k < 9; ++k, group /= 10) *--p = (char)('0' + group % 10); + group = bigint_divmod_1e9(&n); + } + do { // The most significant group drops its leading zeros. + *--p = (char)('0' + group % 10); + } while ((group /= 10) != 0); + return p; +} + +// Floating-point formatting style. Values match zmij::format. +typedef enum { + zmij_format_scientific = 1, + zmij_format_fixed = 2, + zmij_format_general = 3, +} zmij_format; + +// Emits the digit run digits[0..num_digits) with the decimal point placed per +// lead_exp, in fixed or scientific notation. Fixed pads to decimal_places +// fractional digits; scientific appends num_tail_zeros to the fraction. +static size_t write_number(writer* w, const char* digits, int num_digits, + int lead_exp, bool fixed, int decimal_places, + int num_tail_zeros) { + if (!fixed) { + // Emit scientific notation d.ddde±XX. + write_char(w, digits[0]); + // Emit a point only when fractional digits follow it. + if (num_digits - 1 + num_tail_zeros > 0) { + write_char(w, '.'); + write_chars(w, digits + 1, num_digits - 1); + write_zeros(w, num_tail_zeros); // pad the fraction to precision + } + char exp[8]; + char* end = write_big_exp(exp, lead_exp); + return write_chars(w, exp, (int)(end - exp)); + } + + // Emit fixed notation. + int point_pos = lead_exp + 1; + int num_int_digits = 0; // significant digits before the point + if (point_pos <= 0) { // |value| < 1, e.g. 0.00123 + write_char(w, '0'); + } else { + num_int_digits = point_pos < num_digits ? point_pos : num_digits; + write_chars(w, digits, num_int_digits); + write_zeros(w, point_pos - num_int_digits); // integer zeros, e.g. 12300 + } + if (decimal_places <= 0) return w->count; + write_char(w, '.'); + int num_lead_zeros = point_pos < 0 ? -point_pos : 0; + write_zeros(w, num_lead_zeros); // 0.00... + int num_frac_digits = num_digits - num_int_digits; + write_chars(w, digits + num_int_digits, num_frac_digits); + return write_zeros(w, decimal_places - num_lead_zeros - num_frac_digits); +} + +static size_t write_big(writer* w, bigint num, int bin_exp, int precision, + char* digits, int digits_size, zmij_format fmt) { + bool general = fmt == zmij_format_general; + bool fixed = fmt == zmij_format_fixed; + assert(precision >= general); + + digits[0] = '0'; + char* p = digits; + int num_digits = 1; + int lead_exp = 0; // exponent of the leading digit + + if (num.num_limbs != 0) { + // Represent the value exactly as an integer num times a power of ten: + // value = sig * 2**bin_exp = num * 10**base_exp, so its decimal digits are + // num's. For bin_exp < 0 the identity 2**bin_exp = 5**-bin_exp * + // 10**bin_exp keeps num integral via a power-of-five multiply. + int base_exp = 0; + if (bin_exp >= 0) { + bigint_shl(&num, bin_exp); + } else { + bigint_mul_pow5(&num, -bin_exp); + base_exp = bin_exp; + } + + p = write_bigint_digits(num, digits + digits_size); + num_digits = (int)(digits + digits_size - p); + lead_exp = num_digits - 1 + base_exp; + + // Significant digits to keep: precision for %g. For %e/%f it counts + // fractional digits, so add one leading digit; %f adds lead_exp more. + int max_digits = precision + !general + (fixed ? lead_exp : 0); + // Round to max_digits significant digits, ties to even. + if (max_digits < 1) { + // |value| < 10**-precision: rounds to 0, or up to 10**-precision when the + // discarded part exceeds half a unit (a tie rounds to even, i.e. 0). + bool round_up = false; + if (max_digits == 0) { + round_up = + p[0] > '5' || (p[0] == '5' && any_nonzero(p + 1, p + num_digits)); + } + digits[0] = (char)('0' + round_up); + p = digits; + num_digits = 1; + lead_exp = round_up ? -precision : 0; + } else if (num_digits > max_digits) { + char dropped = p[max_digits]; + bool round_up = dropped > '5'; + // A dropped 5 ties to even unless a lower nonzero digit rounds up. + if (dropped == '5') { + round_up = ((p[max_digits - 1] - '0') & 1) != 0 || + any_nonzero(p + max_digits + 1, p + num_digits); + } + num_digits = max_digits; + if (round_up) { + char* q = p + max_digits - 1; + // Propagate the carry over trailing nines. + while (*q == '9') *q-- = '0'; + // 999.. rolling over to 1000.. adds a significant digit. + if (q < p) { + *--p = '1'; + ++lead_exp; + } else { + ++*q; + } + } + } + } + + int decimal_places = precision; // fractional digit positions to emit + if (general) { + // Drop trailing zeros and pick fixed or scientific. + while (num_digits > 1 && p[num_digits - 1] == '0') --num_digits; + fixed = lead_exp >= -4 && lead_exp < precision; + decimal_places = num_digits - lead_exp - 1; + } + + // %e pads the fraction to `precision` digits; %g and shortest emit none. + int num_tail_zeros = general ? 0 : precision - num_digits + 1; + return write_number(w, p, num_digits, lead_exp, fixed, decimal_places, + num_tail_zeros); +} + +// Writes `value` with `precision` digits in `fmt` notation using exact +// big-integer arithmetic, for precisions beyond the 64-bit path's reach. A +// float is exact as a double, so both types share this entry point. +static size_t do_write_big(char* out, size_t n, double value, int precision, + zmij_format fmt) { + uint64_t bits = double_to_bits(value); + int64_t bin_exp = double_get_exp(bits); + uint64_t bin_sig = double_get_sig(bits); + + writer w; + w.out = out; + w.end = out + n; + w.count = 0; + if (double_is_negative(bits)) write_char(&w, '-'); + + bool is_normal = (unsigned)(bin_exp - 1) < (unsigned)(double_exp_mask - 1); + if (ZMIJ_UNLIKELY(!is_normal)) { + if (bin_exp != 0) // inf or nan + return write_chars(&w, bin_sig != 0 ? "nan" : "inf", 3); + if (bin_sig != 0) normalize(&bin_sig, &bin_exp, double_num_sig_bits); + // Paired with the unconditional flip below, this leaves zero at zero and + // restores the implicit bit that normalize shifted in. + bin_sig ^= double_implicit_bit; + } + bin_sig ^= double_implicit_bit; + + // One scratch block holds the big integer's limbs followed by the decimal + // digits. + enum { + digit_words = (double_big_digits + 3) / 4, + scratch_words = double_big_limbs + digit_words, + }; + uint32_t scratch[scratch_words]; + bigint num = bigint_init(bin_sig, scratch, double_big_limbs); + return write_big(&w, num, (int)(bin_exp - double_exp_offset), precision, + (char*)(scratch + double_big_limbs), double_big_digits, fmt); +} + +// Shared implementation of the public write_scientific entry points. +// `num_digits` is the number of significant digits, in [1, 18]; `num_bits` is a +// compile-time constant after ZMIJ_INLINE, so the branches on it fold away. +static ZMIJ_INLINE char* do_write_scientific(uint64_t bin_sig, int64_t bin_exp, + bool negative, char* buffer, + int num_digits, + const int num_bits) { + assert(num_digits >= 1 && num_digits <= 18); + const zmij_data* d = &static_data; + const int exp_mask = num_bits == 64 ? double_exp_mask : float_exp_mask; + const int num_sig_bits = + num_bits == 64 ? double_num_sig_bits : float_num_sig_bits; + const int exp_offset = num_bits == 64 ? double_exp_offset : float_exp_offset; + const uint64_t implicit_bit = (uint64_t)1 << num_sig_bits; + + *buffer = '-'; + buffer += negative; + + bool is_normal = (unsigned)(bin_exp - 1) < (unsigned)(exp_mask - 1); + if (ZMIJ_UNLIKELY(!is_normal)) { + if (bin_exp != 0) return write_inf_nan(buffer, bin_sig != 0); + if (bin_sig == 0) { // zero, e.g. 0.000e+00 + memset(buffer, '0', (size_t)num_digits + 1); + buffer[1] = '.'; // Overwritten by the exponent when num_digits is 1. + return write_exp(buffer + num_digits + (num_digits > 1), 0, num_bits); + } + normalize(&bin_sig, &bin_exp, num_sig_bits); + } + + precision_decimal dec = + to_decimal_precision(bin_sig | implicit_bit, (int)(bin_exp - exp_offset), + num_digits, num_sig_bits, d); + dec_digits_double dig = to_digits_double(dec.sig / 100, d); + return write_scientific_digits(buffer, dig.digits, (unsigned)(dec.sig % 100), + num_digits, dec.lead_exp, num_bits, d); +} + +// Shared implementation of the public write_general entry points. `precision` +// is the number of significant digits, in [1, 18]; `num_bits` is a +// compile-time constant after ZMIJ_INLINE, so the branches on it fold away. +static ZMIJ_INLINE char* do_write_general(uint64_t bin_sig, int64_t bin_exp, + bool negative, char* buffer, + int precision, const int num_bits) { + assert(precision >= 1 && precision <= 18); + const zmij_data* d = &static_data; + const int exp_mask = num_bits == 64 ? double_exp_mask : float_exp_mask; + const int num_sig_bits = + num_bits == 64 ? double_num_sig_bits : float_num_sig_bits; + const int exp_offset = num_bits == 64 ? double_exp_offset : float_exp_offset; + const uint64_t implicit_bit = (uint64_t)1 << num_sig_bits; + + *buffer = '-'; + buffer += negative; + + bool is_normal = (unsigned)(bin_exp - 1) < (unsigned)(exp_mask - 1); + if (ZMIJ_UNLIKELY(!is_normal)) { + if (bin_exp != 0) return write_inf_nan(buffer, bin_sig != 0); + if (bin_sig == 0) { // zero + *buffer = '0'; + return buffer + 1; + } + normalize(&bin_sig, &bin_exp, num_sig_bits); + } + + precision_decimal dec = + to_decimal_precision(bin_sig | implicit_bit, (int)(bin_exp - exp_offset), + precision, num_sig_bits, d); + dec_digits_double dig = to_digits_double(dec.sig / 100, d); + // to_decimal_precision pads on the right with zeros, so the significant + // digits run to the last nonzero one: in the low pair, or in the top sixteen + // when both of those are zero. + unsigned lo = (unsigned)(dec.sig % 100); + int num_digits = lo != 0 ? 18 - (lo % 10 == 0) : dig.num_digits; + int lead_exp = dec.lead_exp; + + if (lead_exp < -4 || lead_exp >= precision) + return write_scientific_digits(buffer, dig.digits, lo, num_digits, lead_exp, + num_bits, d); + + if (lead_exp < 0) { // fixed with a leading 0.00...: lead_exp in [-4, -1] + // "0.", then -lead_exp-1 zeros. + memcpy(buffer, "0.0000", (size_t)(1 - lead_exp)); + write_digits_double(buffer + 1 - lead_exp, dig.digits, false, d); + memcpy(buffer + 17 - lead_exp, digits2(lo), 2); + return buffer + (1 - lead_exp) + num_digits; + } + + write_digits_double(buffer, dig.digits, false, d); + memcpy(buffer + 16, digits2(lo), 2); + int point_pos = lead_exp + 1; + if (point_pos >= num_digits) { // All significant digits are integral. + memset(buffer + num_digits, '0', (size_t)(point_pos - num_digits)); + return buffer + point_pos; + } + memmove(buffer + point_pos + 1, buffer + point_pos, + (size_t)(num_digits - point_pos)); + buffer[point_pos] = '.'; + return buffer + num_digits + 1; +} + +// Shared implementation of the public write_fixed entry points. +static ZMIJ_INLINE char* do_write_fixed(double value, char* buffer, + int precision) { + assert(precision >= 0 && precision <= 18); + const zmij_data* d = &static_data; + uint64_t bits = double_to_bits(value); + int64_t bin_exp = double_get_exp(bits); + uint64_t bin_sig = double_get_sig(bits); + char* start = buffer; + + *buffer = '-'; + buffer += double_is_negative(bits); + + bool is_normal = (unsigned)(bin_exp - 1) < (unsigned)(double_exp_mask - 1); + if (ZMIJ_UNLIKELY(!is_normal)) { + if (bin_exp != 0) return write_inf_nan(buffer, bin_sig != 0); + if (bin_sig == 0) return write_zero(buffer, precision); // e.g. 0.000 + normalize(&bin_sig, &bin_exp, double_num_sig_bits); + } + + int value_exp = (int)(bin_exp - double_exp_offset); + bin_sig |= double_implicit_bit; + + // Scale to 18 significant digits, retaining the fraction for later rounding. + int dec_exp = compute_dec_exp(value_exp + double_num_sig_bits, true) - 17; + scaled_value scaled = + scale(bin_sig, value_exp, dec_exp, double_num_sig_bits, d); + // A one-too-small dec_exp estimate leaves 19 significant digits, so derive + // the count (and the exact leading exponent) from the truncated integral. + uint64_t integral = scaled.integral; + bool has_fraction = (scaled.fraction | scaled.fraction_tail) != 0; + int num_scaled_digits = 18 + (integral >= pow10s[18]); + int lead_exp = dec_exp + num_scaled_digits - 1; + int num_digits = lead_exp + 1 + precision; // significant digits to emit + + if (ZMIJ_UNLIKELY(num_digits <= 0)) { + // |value| < 10**-precision, so it rounds to 0, or up to 10**-precision iff + // |value| > 0.5 * 10**-precision (a tie rounds to even, i.e. 0). + char* end = write_zero(buffer, precision); + uint64_t half = 5 * pow10s[17]; + if (num_scaled_digits == 18 && lead_exp == -precision - 1 && + (half < integral || (half == integral && has_fraction))) { + end[-1] = '1'; + } + return end; + } + + if (ZMIJ_UNLIKELY(num_digits > 18)) { + // The largest result is -DBL_MAX with 18 fractional digits: 329 chars. + size_t size = do_write_big(start, 329, value, precision, zmij_format_fixed); + return start + size; + } + + uint64_t dec_sig; + if (num_digits < num_scaled_digits) { + // Round the dropped decimal digits and retained fraction in one pass. + uint64_t pow = pow10s[num_scaled_digits - num_digits]; + dec_sig = integral / pow; + uint64_t remainder = integral - dec_sig * pow; + uint64_t half = pow / 2; + bool round_up = half < remainder || + (half == remainder && (has_fraction || (dec_sig & 1) != 0)); + dec_sig += round_up; + } else { + dec_sig = round_even(scaled); + } + if (dec_sig >= pow10s[num_digits]) { // carry to next power of ten + ++lead_exp; + dec_sig /= 10; + } + dec_sig *= pow10s[18 - num_digits]; + unsigned lo = (unsigned)(dec_sig % 100); + dec_digits_double dig = to_digits_double(dec_sig / 100, d); + + int num_int_digits = lead_exp + 1; + int total = num_int_digits + precision; // significant digits + zero padding + + if (num_int_digits <= 0) { // |value| < 1: "0." + leading zeros + digits + write2(buffer, '0', '.'); + memset(buffer + 2, '0', (size_t)-num_int_digits); + buffer += 2 - num_int_digits; + write_digits_double(buffer, dig.digits, false, d); + memcpy(buffer + 16, digits2(lo), 2); + return buffer + total; + } + + write_digits_double(buffer, dig.digits, false, d); + memcpy(buffer + 16, digits2(lo), 2); + buffer[18] = '0'; // at most one carry digit + if (precision == 0) return buffer + total; + memmove(buffer + num_int_digits + 1, buffer + num_int_digits, + (size_t)precision); + buffer[num_int_digits] = '.'; + return buffer + total + 1; +} + +static ZMIJ_INLINE char* do_write_hex(double value, char* buffer) { + uint64_t bits = double_to_bits(value); + int64_t bin_exp = double_get_exp(bits); + uint64_t bin_sig = double_get_sig(bits); + + *buffer = '-'; + buffer += double_is_negative(bits); + + bool zero = false; + bool is_normal = (unsigned)(bin_exp - 1) < (unsigned)(double_exp_mask - 1); + if (ZMIJ_UNLIKELY(!is_normal)) { + if (bin_exp != 0) return write_inf_nan(buffer, bin_sig != 0); + zero = bin_sig == 0; + if (zero) + bin_exp = double_exp_bias; // This cancels to 0 below. + else + normalize(&bin_sig, &bin_exp, double_num_sig_bits); + } + bin_exp -= double_exp_bias; + + buffer = write2(buffer, '0', 'x'); + *buffer++ = (char)('0' + !zero); + + bin_sig <<= 64 - double_num_sig_bits; + if (bin_sig != 0) { + *buffer++ = '.'; + do { + *buffer++ = "0123456789abcdef"[bin_sig >> 60]; + bin_sig <<= 4; + } while (bin_sig != 0); + } + + return write_hex_exp(buffer, (int)bin_exp); +} + +// Shared implementation of the public write entry points. `num_bits` is a +// compile-time constant after ZMIJ_INLINE; the few branches on it fold away. +static ZMIJ_INLINE char* do_write(uint64_t bin_sig, int64_t bin_exp, + bool negative, char* buffer, + const int num_bits) { + *buffer = '-'; + buffer += negative; + + const zmij_data* d = &static_data; + ZMIJ_ASM(("" : "+r"(d))); // Load constants from memory. + const uint64_t threshold = num_bits == 64 ? d->threshold : (uint64_t)1e7; + + to_decimal_result dec; + const int exp_mask = num_bits == 64 ? double_exp_mask : float_exp_mask; + bool is_normal = (unsigned)(bin_exp - 1) < (unsigned)(exp_mask - 1); + if (ZMIJ_UNLIKELY(!is_normal)) { + if (bin_exp != 0) { + memcpy(buffer, bin_sig == 0 ? "inf" : "nan", 4); + return buffer + 3; + } + if (bin_sig == 0) { + memcpy(buffer, "0", 2); + return buffer + 1; + } + dec = num_bits == 64 ? to_decimal_double(bin_sig, 1, true, d) + : to_decimal_float((uint32_t)bin_sig, 1, true, d); + long long dec_sig = + dec.sig * 10 + (-(int)dec.has_last_digit & dec.last_digit); + int dec_exp = dec.exp; + while ((uint64_t)dec_sig < threshold) { + dec_sig *= 10; + --dec_exp; + } + long long q = div10(dec_sig); + int last_digit = (int)(dec_sig - q * 10); + dec.sig = q; + dec.exp = dec_exp; + dec.last_digit = last_digit; + dec.has_last_digit = last_digit != 0; + } else { + if (num_bits == 64) { + dec = to_decimal_double(bin_sig | double_implicit_bit, bin_exp, + bin_sig != 0, d); + } else { + dec = to_decimal_float((uint32_t)(bin_sig | float_implicit_bit), bin_exp, + bin_sig != 0, d); + } + } + bool has_last_digit = dec.has_last_digit; + bool extra_digit = (uint64_t)dec.sig >= threshold; + const int max_digits10 = num_bits == 64 ? DBL_DECIMAL_DIG : FLT_DECIMAL_DIG; + int dec_exp = dec.exp + max_digits10 - 2 + extra_digit; + if (num_bits == 32 && ZMIJ_UNLIKELY(dec.sig < (uint32_t)1e6)) { + dec.sig = 10 * dec.sig + (-(int)has_last_digit & dec.last_digit); + has_last_digit = false; + --dec_exp; + } + + // Write significand/fixed. + char* start = buffer; + // Convert the significand to digits once, before the fixed/scientific + // branch, so the long-latency conversion overlaps the branch logic. + dec_digits_double dig64; + dec_digits_float dig32; + int dig_num_digits; + if (num_bits == 64) { + dig64 = to_digits_double(dec.sig, d); + dig_num_digits = dig64.num_digits; + } else { + dig32 = to_digits_float(dec.sig, d); + dig_num_digits = dig32.num_digits; + } + const int bcd_size = num_bits == 64 ? 16 : 8; + const int min_fixed_dec_exp = -4; + const int max_fixed_dec_exp = num_bits == 64 ? 15 : 6; + if (dec_exp >= min_fixed_dec_exp && dec_exp <= max_fixed_dec_exp) { + write8(start, zeros); // For dec_exp < 0. + char last_digit_char = + (char)('0' + (-(int)has_last_digit & dec.last_digit)); + int num_digits = has_last_digit ? bcd_size : dig_num_digits - 1; + + // Materialize the base early so the entry address is `base + idx*32`; + // otherwise Clang folds the offset in and adds a cycle to the idx chain. + const fixed_layout_entry* fixed_layouts = d->fixed_layouts; + if (ZMIJ_AARCH64) ZMIJ_ASM(("" : "+r"(fixed_layouts))); + + const fixed_layout_entry* layout = + &fixed_layouts[dec_exp - min_fixed_dec_exp]; + buffer += layout->start_pos; +#if ZMIJ_USE_SSE4_1 + if (num_bits == 64) { + __m128i digits = dig64.digits; + __m128i tbl = + _mm_load_si128((const __m128i*)&layout->shuffle[extra_digit]); + __m128i out = _mm_shuffle_epi8(digits, tbl); + memcpy(buffer, &out, bcd_size); // Store the assembled digits in one go. + // The point can push BCD[15] outside the vector to buffer[16], so write + // it unconditionally (otherwise it's in-vector or overwritten below). + buffer[bcd_size] = (char)_mm_extract_epi8(digits, 15); + start[layout->point_pos] = '.'; + buffer[layout->last_digit_pos[extra_digit]] = last_digit_char; + return buffer + layout->end_pos[num_digits + extra_digit - 1]; + } +#endif // ZMIJ_USE_SSE4_1 + if (num_bits == 64) + write_digits_double(buffer, dig64.digits, !extra_digit, d); + else + write_digits_float(buffer, dig32.digits, !extra_digit); + buffer[bcd_size + extra_digit - 1] = last_digit_char; + unsigned point_pos = layout->point_pos; + memmove(start + layout->shift_pos, start + point_pos, bcd_size); + start[point_pos] = '.'; + return buffer + layout->end_pos[num_digits + extra_digit - 1]; + } + + buffer += extra_digit; + if (num_bits == 64) + memcpy(buffer, &dig64.digits, bcd_size); + else + memcpy(buffer, &dig32.digits, bcd_size); + buffer[bcd_size] = (char)('0' + dec.last_digit); + buffer += has_last_digit ? bcd_size + 1 : dig_num_digits; + start[0] = start[1]; + start[1] = '.'; + buffer -= (buffer - 1 == start + 1); // Remove trailing point. + + // Write exponent. +#if ZMIJ_USE_EXP_STRING_TABLE + uint64_t exp_data = d->exp_strings[dec_exp + exp_string_offset]; + int len = (int)(exp_data >> 48); + if (is_big_endian) exp_data = bswap64(exp_data); + memcpy(buffer, &exp_data, num_bits == 64 ? 8 : 4); + return buffer + len; +#else + uint16_t e_sign = dec_exp >= 0 ? ('+' << 8 | 'e') : ('-' << 8 | 'e'); + if (is_big_endian) e_sign = e_sign << 8 | e_sign >> 8; + memcpy(buffer, &e_sign, 2); + buffer += 2; + dec_exp = dec_exp >= 0 ? dec_exp : -dec_exp; + if (num_bits == 64) { + // digit = dec_exp / 100 + uint32_t digit = use_umul128_hi64 + ? (uint32_t)umul128_hi64(dec_exp, 0x290000000000000) + : ((uint32_t)dec_exp * div100_sig) >> div100_exp; + *buffer = (char)('0' + digit); + buffer += dec_exp >= 100; + dec_exp -= digit * 100; + } + memcpy(buffer, digits2(dec_exp), 2); + return buffer + 2; +#endif // ZMIJ_USE_EXP_STRING_TABLE +} + +char* zmij_detail_write_f(float value, char* buffer) { + uint32_t bits = float_to_bits(value); + int64_t bin_exp = float_get_exp(bits); // binary exponent + uint32_t bin_sig = float_get_sig(bits); // binary significand + return do_write(bin_sig, bin_exp, float_is_negative(bits), buffer, 32); +} + +// It is slightly faster to return a pointer to the end than the size. +char* zmij_detail_write(double value, char* buffer) { + uint64_t bits = double_to_bits(value); + int64_t bin_exp = double_get_exp(bits); // binary exponent + uint64_t bin_sig = double_get_sig(bits); // binary significand + return do_write(bin_sig, bin_exp, double_is_negative(bits), buffer, 64); +} + +char* zmij_detail_write_scientific_f(float value, char* buffer, + int num_digits) { + uint32_t bits = float_to_bits(value); + int64_t bin_exp = float_get_exp(bits); // binary exponent + uint32_t bin_sig = float_get_sig(bits); // binary significand + return do_write_scientific(bin_sig, bin_exp, float_is_negative(bits), buffer, + num_digits, 32); +} + +char* zmij_detail_write_scientific(double value, char* buffer, int num_digits) { + uint64_t bits = double_to_bits(value); + int64_t bin_exp = double_get_exp(bits); // binary exponent + uint64_t bin_sig = double_get_sig(bits); // binary significand + return do_write_scientific(bin_sig, bin_exp, double_is_negative(bits), buffer, + num_digits, 64); +} + +size_t zmij_detail_write_scientific_big(char* out, size_t n, double value, + int precision) { + return do_write_big(out, n, value, precision, zmij_format_scientific); +} + +char* zmij_detail_write_general_f(float value, char* buffer, int precision) { + uint32_t bits = float_to_bits(value); + int64_t bin_exp = float_get_exp(bits); // binary exponent + uint32_t bin_sig = float_get_sig(bits); // binary significand + return do_write_general(bin_sig, bin_exp, float_is_negative(bits), buffer, + precision, 32); +} + +char* zmij_detail_write_general(double value, char* buffer, int precision) { + uint64_t bits = double_to_bits(value); + int64_t bin_exp = double_get_exp(bits); // binary exponent + uint64_t bin_sig = double_get_sig(bits); // binary significand + return do_write_general(bin_sig, bin_exp, double_is_negative(bits), buffer, + precision, 64); +} + +size_t zmij_detail_write_general_big(char* out, size_t n, double value, + int precision) { + return do_write_big(out, n, value, precision, zmij_format_general); +} + +char* zmij_detail_write_fixed_f(float value, char* buffer, int precision) { + // A float is exact as a double, so both produce the same digits. + return do_write_fixed((double)value, buffer, precision); +} + +char* zmij_detail_write_fixed(double value, char* buffer, int precision) { + return do_write_fixed(value, buffer, precision); +} + +size_t zmij_detail_write_fixed_big(char* out, size_t n, double value, + int precision) { + return do_write_big(out, n, value, precision, zmij_format_fixed); +} + +char* zmij_detail_write_hex(double value, char* buffer) { + return do_write_hex(value, buffer); +} diff --git a/tools/sync_zmij.sh b/tools/sync_zmij.sh new file mode 100755 index 0000000..b064110 --- /dev/null +++ b/tools/sync_zmij.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# Sync the vendored zmij C library into third_party/zmij/ from a local +# checkout of upstream (https://github.com/vitaut/zmij). +# +# Usage: tools/sync_zmij.sh [/path/to/zmij] +# Default: ~/zmij +# +# Only the C port is vendored (zmij.c + zmij-c.h); the C++ headers are +# not needed. The checkout must be recent enough to carry the fixed / +# scientific precision writers in the C API (zmij_detail_write_fixed_big +# et al., upstream main since mid-2026) — older checkouts and the +# Ryu-backed `cpython` fork branch only expose the shortest writer and +# are rejected. + +set -euo pipefail + +ZMIJ="${1:-$HOME/zmij}" + +if [[ ! -d "$ZMIJ/.git" ]]; then + echo "error: $ZMIJ is not a git repo" >&2 + echo " git clone https://github.com/vitaut/zmij $ZMIJ" >&2 + exit 1 +fi +if ! grep -q "zmij_detail_write_fixed_big" "$ZMIJ/zmij-c.h"; then + echo "error: $ZMIJ/zmij-c.h has no precision writers; checkout is too old" >&2 + echo " (or on a branch without the C precision API)" >&2 + exit 1 +fi + +repo_root="$(cd "$(dirname "$0")/.." && pwd)" +dest="$repo_root/third_party/zmij" + +echo "==> syncing vendored zmij from $ZMIJ" +mkdir -p "$dest" +cp "$ZMIJ/zmij.c" "$dest/zmij.c" +cp "$ZMIJ/zmij-c.h" "$dest/zmij-c.h" +cp "$ZMIJ/LICENSE" "$dest/LICENSE" + +commit="$(cd "$ZMIJ" && git rev-parse HEAD)" +short="$(cd "$ZMIJ" && git rev-parse --short HEAD)" +tag="$(cd "$ZMIJ" && git describe --tags --exact-match HEAD 2>/dev/null || git -C "$ZMIJ" describe --tags --always)" +date_iso="$(cd "$ZMIJ" && git show -s --format=%cs HEAD)" + +cat > "$dest/README.vendor" < \`__asm__\` in the ZMIJ_ASM macro (zmij.c). With \`-std=c11\`, + which is what CPython compiles with, \`asm\` is not a keyword and the + three ZMIJ_ASM sites fail to compile. floatium builds with gnu11, so + it is unaffected; CPython would not be. +2. A to-decimal entry point in the C API: \`{sig, exp}\` for the shortest + representation (the shape of zmij.c's internal to_decimal_result), + so a dtoa-style caller can skip writing and re-reading the text. + Optionally the same for fixed / scientific precision. +3. \`extern "C"\` guards in zmij-c.h for inclusion from C++ TUs. + +Resync: tools/sync_zmij.sh +EOV +echo "==> done. Commit: $commit" +echo "==> review: git -C '$repo_root' diff --stat third_party/zmij/" From cc9e74e19e47eff3898f78c9922b9993a9f601ec Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 19 Oct 2026 23:45:55 +0200 Subject: [PATCH 08/13] bench: add zmij column to BENCHMARKS.md Co-Authored-By: Claude Fable 5.1 --- BENCHMARKS.md | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/BENCHMARKS.md b/BENCHMARKS.md index d2b6fca..d402674 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -2,7 +2,7 @@ Nanoseconds per operation for `repr(float)`, fixed-precision f-string formatting, and `float(str)`, comparing stock CPython against every -floatium format backend (`fmt_opt`, `fmt`, `ryu_opt`) and every parse +floatium format backend (`fmt_opt`, `fmt`, `ryu_opt`, `zmij`) and every parse backend (`fast_float`, `wuffs`, `stock`). ## Methodology @@ -30,20 +30,25 @@ backend (`fast_float`, `wuffs`, `stock`). ## Format backends `repr(x)` and `f"{x:.4f}"` over each corpus. Speedup is `stock / backend`; -values above 1.00x are faster than stock. +values above 1.00x are faster than stock. `zmij` is the pure-C +single-library backend proposed for CPython; its `repr` carries a text +re-read of ~10 ns that a to-decimal entry point in Żmij's C API would +remove, and its fixed-precision path on huge magnitudes (`random_bits`) +is bigint-based and ~2x slower than Ryu's `d2fixed`, while still 4x +faster than stock. -| Corpus | Operation | Stock (ns) | fmt_opt | fmt | ryu_opt | fmt_opt speedup | fmt speedup | ryu_opt speedup | -|---|---|---:|---:|---:|---:|---:|---:|---:| -| random_uniform | `repr(x)` | 284.7 | 92.2 | 91.6 | 95.4 | 3.09x | 3.11x | 2.98x | -| random_uniform | `f"{x:.4f}"` | 119.9 | 99.9 | 100.7 | 105.6 | 1.20x | 1.19x | 1.14x | -| random_bits | `repr(x)` | 821.6 | 130.1 | 129.9 | 142.4 | 6.31x | 6.32x | 5.77x | -| random_bits | `f"{x:.4f}"` | 1,933.9 | 196.8 | 5,532.8 | 215.1 | 9.83x | 0.35x | 8.99x | -| financial | `repr(x)` | 171.0 | 75.0 | 73.8 | 101.2 | 2.28x | 2.32x | 1.69x | -| financial | `f"{x:.4f}"` | 144.7 | 96.5 | 98.1 | 114.4 | 1.50x | 1.47x | 1.27x | -| scientific | `repr(x)` | 638.4 | 127.2 | 127.3 | 138.5 | 5.02x | 5.02x | 4.61x | -| scientific | `f"{x:.4f}"` | 1,078.7 | 161.4 | 2,969.0 | 173.4 | 6.68x | 0.36x | 6.22x | -| integer_valued | `repr(x)` | 142.8 | 84.1 | 83.3 | 78.8 | 1.70x | 1.71x | 1.81x | -| integer_valued | `f"{x:.4f}"` | 167.9 | 102.2 | 101.7 | 107.4 | 1.64x | 1.65x | 1.56x | +| Corpus | Operation | Stock (ns) | fmt_opt | fmt | ryu_opt | zmij | fmt_opt speedup | fmt speedup | ryu_opt speedup | zmij speedup | +|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| random_uniform | `repr(x)` | 284.0 | 90.8 | 89.5 | 91.5 | 102.9 | 3.13x | 3.17x | 3.10x | 2.76x | +| random_uniform | `f"{x:.4f}"` | 119.2 | 100.0 | 100.2 | 104.4 | 109.4 | 1.19x | 1.19x | 1.14x | 1.09x | +| random_bits | `repr(x)` | 819.9 | 131.2 | 128.5 | 143.0 | 154.3 | 6.25x | 6.38x | 5.73x | 5.31x | +| random_bits | `f"{x:.4f}"` | 1,933.7 | 193.2 | 5,539.0 | 211.5 | 452.3 | 10.01x | 0.35x | 9.14x | 4.28x | +| financial | `repr(x)` | 172.5 | 72.4 | 71.9 | 96.6 | 90.6 | 2.38x | 2.40x | 1.78x | 1.90x | +| financial | `f"{x:.4f}"` | 144.6 | 97.1 | 96.0 | 115.9 | 113.8 | 1.49x | 1.51x | 1.25x | 1.27x | +| scientific | `repr(x)` | 635.2 | 127.5 | 127.1 | 139.5 | 150.6 | 4.98x | 5.00x | 4.55x | 4.22x | +| scientific | `f"{x:.4f}"` | 1,078.2 | 158.7 | 2,969.7 | 170.2 | 279.4 | 6.79x | 0.36x | 6.33x | 3.86x | +| integer_valued | `repr(x)` | 142.1 | 83.4 | 81.2 | 78.2 | 94.0 | 1.70x | 1.75x | 1.82x | 1.51x | +| integer_valued | `f"{x:.4f}"` | 167.3 | 99.2 | 101.5 | 108.1 | 108.1 | 1.69x | 1.65x | 1.55x | 1.55x | ## Parse backend @@ -52,8 +57,8 @@ values above 1.00x are faster than stock. | Corpus | Operation | Stock (ns) | fast_float | wuffs | stock | fast_float speedup | wuffs speedup | stock speedup | |---|---|---:|---:|---:|---:|---:|---:|---:| -| random_uniform | `float(s)` | 125.6 | 46.0 | 57.0 | 108.5 | 2.73x | 2.20x | 1.16x | -| random_bits | `float(s)` | 276.3 | 66.8 | 300.9 | 210.8 | 4.14x | 0.92x | 1.31x | -| financial | `float(s)` | 36.8 | 37.6 | 41.9 | 78.5 | 0.98x | 0.88x | 0.47x | -| scientific | `float(s)` | 213.6 | 63.8 | 74.1 | 185.4 | 3.35x | 2.88x | 1.15x | -| integer_valued | `float(s)` | 43.2 | 44.0 | 47.5 | 69.1 | 0.98x | 0.91x | 0.63x | +| random_uniform | `float(s)` | 121.0 | 46.6 | 56.9 | 109.1 | 2.60x | 2.13x | 1.11x | +| random_bits | `float(s)` | 278.5 | 62.7 | 297.5 | 210.4 | 4.44x | 0.94x | 1.32x | +| financial | `float(s)` | 36.7 | 36.6 | 41.6 | 78.5 | 1.00x | 0.88x | 0.47x | +| scientific | `float(s)` | 212.5 | 57.1 | 68.0 | 186.2 | 3.72x | 3.13x | 1.14x | +| integer_valued | `float(s)` | 43.0 | 42.8 | 47.6 | 68.0 | 1.01x | 0.90x | 0.63x | From f1f5823fadd8820a6764427d32e49daa15e962f9 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 20 Oct 2026 20:18:51 +0200 Subject: [PATCH 09/13] tools: exact-arithmetic fuzzer for float<->str (stock dtoa.c or any floatium backend) Oracles are int/int true division (correctly rounded by contract), Decimal quantize/round and brute-force shortest search, so they share no code with dtoa.c or the vendored libraries. ~1M checks per 40k-value run; stock dtoa.c and every floatium backend pass with zero mismatches, so this is the parity evidence for the CPython proposal rather than a bug finder. Co-Authored-By: Claude Fable 5.1 --- tools/fuzz_float_oracle.py | 302 +++++++++++++++++++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 tools/fuzz_float_oracle.py diff --git a/tools/fuzz_float_oracle.py b/tools/fuzz_float_oracle.py new file mode 100644 index 0000000..0653559 --- /dev/null +++ b/tools/fuzz_float_oracle.py @@ -0,0 +1,302 @@ +"""Differential fuzzer for float <-> str conversion against exact arithmetic. + +Checks float(s), format(x, ".Nf" / ".Ne" / ".Ng"), repr(x) and round(x, n) +of the *running* interpreter against oracles that never touch dtoa.c or any +floatium backend: + + * parsing : digits + exponent as exact ints -> int/int true division, + which CPython guarantees to be correctly rounded (including + subnormals); huge exponents are short-circuited. + * format : Decimal(x) is exact; quantize / round with ROUND_HALF_EVEN. + * repr : brute-force shortest-then-closest search over Decimal + candidates, round-trip checked with the parse oracle. + * round : exact Fraction rounding (half-even) -> int/int division. + +Inputs are biased towards the hard cases: exact binary midpoints and their +decimal neighbours (full expansions up to ~1100 digits, truncations, bumped +last digits), random 1-1500 digit strings with random exponents, subnormals, +powers of ten +- ulps, and the overflow / underflow thresholds. + + FLOATIUM_AUTOPATCH=0 python tools/fuzz_float_oracle.py [seed] [N] # stock CPython + FLOATIUM_FORMAT_BACKEND=zmij FLOATIUM_PARSE_BACKEND=wuffs \ + python tools/fuzz_float_oracle.py 1 40000 # a floatium backend + +N=40000 is ~1M checks and takes ~12 s with the C decimal module. +FUZZ_NO_ORACLE=1 only exercises the conversions (for sanitizer builds that +lack libmpdec). +""" +import math, random, struct, sys, time, re +from fractions import Fraction +from decimal import Decimal, Context, ROUND_HALF_EVEN, localcontext, getcontext + +getcontext().prec = 2000 +SEED = int(sys.argv[1]) if len(sys.argv) > 1 else 1 +N = int(sys.argv[2]) if len(sys.argv) > 2 else 20000 +rng = random.Random(SEED) +report = {} +def bad(cat, item): + lst = report.setdefault(cat, []) + lst.append(item) + +def bits(b): return struct.unpack(" float: + """Correctly rounded double of a non-negative Fraction (independent of dtoa).""" + try: + v = fr.numerator / fr.denominator + except OverflowError: + v = math.inf + return -v if neg else v + +_NUM = re.compile(r"(\d*)\.?(\d*)(?:[eE]([+-]?\d+))?") +def parse_oracle(s: str) -> float: + """Correctly rounded value of a decimal string via exact ints + int/int + division. Huge exponents are short-circuited instead of materialised.""" + neg = s.lstrip().startswith("-") + core = s.strip().lstrip("+-") + m = _NUM.fullmatch(core); assert m, s + ip, fp, e = m.groups() + digits = int((ip + fp) or "0") + if digits == 0: + return -0.0 if neg else 0.0 + exp10 = (int(e) if e else 0) - len(fp) + adjusted = len(str(digits)) + exp10 # decimal magnitude + if adjusted > 312: + return -math.inf if neg else math.inf + if adjusted < -330: + return -0.0 if neg else 0.0 + fr = Fraction(digits * 10 ** exp10) if exp10 >= 0 else Fraction(digits, 10 ** -exp10) + return nearest(fr, neg) + +NO_ORACLE = __import__("os").environ.get("FUZZ_NO_ORACLE") == "1" +if NO_ORACLE: # exercise-only mode (for sanitizer builds without libmpdec) + parse_oracle = lambda s: float(s) + +# ---------------------------------------------------------------- values +def random_values(n): + vals = [] + for _ in range(n): + r = rng.random() + if r < 0.4: + x = bits(rng.getrandbits(64)) + if not math.isfinite(x): continue + elif r < 0.55: + x = bits(rng.getrandbits(52) | (rng.getrandbits(1) << 63)) # subnormal + elif r < 0.7: + k = rng.randint(-320, 308); x = 10.0 ** k + for _ in range(rng.randint(0, 3)): x = math.nextafter(x, rng.choice([0.0, math.inf])) + elif r < 0.85: + x = rng.uniform(-1e6, 1e6) + else: + x = float(rng.randint(-10**17, 10**17)) * 10.0 ** rng.randint(-20, 20) + if math.isfinite(x): vals.append(x) + return vals + +def one_value(): + while True: + v = random_values(1) + if v: return v[0] + +# ---------------------------------------------------------------- parse +def exact_decimal_string(fr: Fraction) -> str: + """Finite decimal expansion of a dyadic rational (as digits, no exponent).""" + d = Decimal(fr.numerator) / Decimal(fr.denominator) # exact at prec 2000 for dyadics < 2^1024 / 2^1074... use scaleb + return format(d, "f") + +def check_parse(n): + t0 = time.time(); cnt = 0 + # (a) halfway cases and their neighbours, full expansion and truncations + for _ in range(n): + x = abs(one_value()) if rng.random() < 0.9 else abs(bits(rng.getrandbits(52))) + if x == 0 or not math.isfinite(x): continue + y = math.nextafter(x, math.inf) + if not math.isfinite(y): continue + mid = (Fraction(x) + Fraction(y)) / 2 + with localcontext() as ctx: + ctx.prec = 1200 + dm = Decimal(mid.numerator) / Decimal(mid.denominator) + s_full = format(dm, "f") + variants = [s_full] + # perturb last digit, append digits, truncate + if "." in s_full: + variants.append(s_full + "1") + variants.append(s_full + "0" * rng.randint(1, 50) + "1") + cut = rng.randint(1, len(s_full) - 1) + if s_full[cut-1] != ".": + variants.append(s_full[:cut]) + # bump last digit down/up + last = s_full[-1] + if last != "0": variants.append(s_full[:-1] + str(int(last) - 1)) + if last != "9": variants.append(s_full[:-1] + str(int(last) + 1)) + # exponent form of the same number + mant = s_full.replace(".", "").lstrip("0") or "0" + intlen = s_full.index(".") if "." in s_full else len(s_full) + lead = len(s_full.replace(".", "")) - len(s_full.replace(".", "").lstrip("0")) + e = intlen - lead - 1 + variants.append(f"{mant[0]}.{mant[1:]}e{e}" if len(mant) > 1 else f"{mant}e{e}") + for s in variants: + cnt += 1 + got = float(s); exp = parse_oracle(s) + if tobits(got) != tobits(exp): + bad("parse", (s[:80] + ("..." if len(s) > 80 else ""), got, exp)) + # (b) random digit strings + for _ in range(n): + nd = rng.choice([1, 5, 17, 18, 19, 20, 25, 40, 41, 60, 100, 400, 800, 1500]) + digits = "".join(rng.choice("0123456789") for _ in range(nd)) + if rng.random() < 0.3: digits = "0" * rng.randint(1, 30) + digits + if rng.random() < 0.5: + p = rng.randint(0, len(digits)); digits = digits[:p] + "." + digits[p:] + if digits.startswith("."): digits = "0" + digits + if digits.endswith("."): digits += "0" + s = digits + rng.choice(["", "", f"e{rng.randint(-400, 400)}", f"E+{rng.randint(0, 330)}", f"e-{rng.randint(0, 350)}"]) + if rng.random() < 0.3: s = "-" + s + cnt += 1 + got = float(s); exp = parse_oracle(s) + if tobits(got) != tobits(exp): + bad("parse", (s[:80], got, exp)) + # (c) overflow / underflow thresholds + dmax = Fraction(1.7976931348623157e308); half_ulp = Fraction(2) ** 970 + tiny = Fraction(2) ** -1075 # half of min subnormal + for base, name in [(dmax + half_ulp, "DBL_MAX+half"), (tiny, "min_sub/2")]: + with localcontext() as ctx: + ctx.prec = 1200 + dd = Decimal(base.numerator) / Decimal(base.denominator) + s = format(dd, "f") + for v in [s, s + "1", s[:-1] + "0", s[:-3]]: + cnt += 1 + got = float(v); exp = parse_oracle(v) + if tobits(got) != tobits(exp): bad("parse-threshold", (name, v[:60], got, exp)) + for s in ["1e308", "1.8e308", "2e308", "1e309", "1e400", "1e99999", "1e-99999", "1e-400", "1e-324", "2e-324", "2.5e-324", "3e-324", + "4.9406564584124654e-324", "2.4703282292062327e-324", "2.4703282292062328e-324", "2.47032822920623272e-324", + "0." + "0" * 400 + "1", "1" + "0" * 400, "1" + "0" * 400 + "e-400", "9" * 400 + "e-400", + "1e+" + "0" * 30 + "308", "1e-" + "0" * 30 + "324", "0e99999999999999999999", "1e-" + "9" * 30, "1e" + "9" * 30]: + cnt += 1 + got = float(s); exp = parse_oracle(s) + if tobits(got) != tobits(exp): bad("parse-edge", (s[:60], got, exp)) + print(f"parse: {cnt} cases in {time.time()-t0:.1f}s, {len(report.get('parse', [])) + len(report.get('parse-threshold', [])) + len(report.get('parse-edge', []))} mismatches") + +# ---------------------------------------------------------------- format +def norm_e(s): + m = re.fullmatch(r"(-?)(\d)(?:\.(\d*))?[eE]([+-]?\d+)", s) + assert m, s + sign, d0, frac, e = m.groups() + return f"{sign}{d0}.{frac or ''}e{int(e)}" + +def fmt_f_oracle(x, nd): + d = Decimal(x) + q = d.quantize(Decimal(1).scaleb(-nd), rounding=ROUND_HALF_EVEN) + return format(q, "f") + +def sig_round(x, nsig): + """Decimal(x) rounded half-even to nsig significant digits (exact ctx).""" + d = Decimal(x) + if d == 0: return d + ctx = Context(prec=nsig, rounding=ROUND_HALF_EVEN) + return ctx.plus(d) + +def fmt_e_oracle(x, nd): + q = sig_round(x, nd + 1) + return norm_e(format(q, f".{nd}e")) + +def fmt_g_oracle(x, prec): + P = prec if prec else 1 + q = sig_round(x, P) + if q == 0: + return "-0" if math.copysign(1, x) < 0 else "0" + E = q.adjusted() + if -4 <= E < P: + s = format(q.quantize(Decimal(1).scaleb(E - P + 1), rounding=ROUND_HALF_EVEN), "f") + if "." in s: s = s.rstrip("0").rstrip(".") + return s + s = format(q, f".{P-1}e") + mant, _, e = s.partition("e") + if "." in mant: mant = mant.rstrip("0").rstrip(".") + return f"{mant}e{int(e)}" + +def check_format(n): + global fmt_f_oracle, fmt_e_oracle, fmt_g_oracle + if NO_ORACLE: + fmt_f_oracle = lambda x, nd: format(x, f".{nd}f") + fmt_e_oracle = lambda x, nd: norm_e(format(x, f".{nd}e")) + fmt_g_oracle = lambda x, p: (lambda g: (lambda m, _, e: f"{m}e{int(e)}")(*g.partition("e")) if "e" in g else g)(format(x, f".{p}g")) + t0 = time.time(); cnt = 0 + vals = random_values(n) + for x in vals: + for nd in rng.sample([0, 1, 2, 3, 6, 10, 15, 16, 17, 18, 19, 20, 25, 30, 50, 100, 400, 1100], 5): + cnt += 1 + got = format(x, f".{nd}f"); exp = fmt_f_oracle(x, nd) + if got != exp: bad("format-f", (repr(x), nd, got[:60], exp[:60])) + for nd in rng.sample([0, 1, 2, 5, 6, 14, 15, 16, 17, 18, 19, 25, 60, 400], 4): + cnt += 1 + got = norm_e(format(x, f".{nd}e")); exp = fmt_e_oracle(x, nd) + if got != exp: bad("format-e", (repr(x), nd, got[:60], exp[:60])) + for p in rng.sample([0, 1, 2, 5, 6, 10, 15, 16, 17, 18, 20, 30, 100], 3): + cnt += 1 + got = format(x, f".{p}g") + if "e" in got: + mant, _, e = got.partition("e"); got = f"{mant}e{int(e)}" + exp = fmt_g_oracle(x, p) + if got != exp: bad("format-g", (repr(x), p, got[:60], exp[:60])) + print(f"format: {cnt} cases in {time.time()-t0:.1f}s, {sum(len(report.get(k, [])) for k in ('format-f','format-e','format-g'))} mismatches") + +# ---------------------------------------------------------------- repr +def check_repr(n): + t0 = time.time(); cnt = 0; ambiguous = 0 + for x in random_values(n): + if x == 0: continue + cnt += 1 + if NO_ORACLE: + float(repr(x)); float(str(x)); continue + ax = abs(x); exact = Fraction(ax) + r = repr(ax) + rd = Decimal(r) + # 1. round trip (oracle parse) + if nearest(Fraction(rd)) != ax: + bad("repr-roundtrip", (r,)); continue + rdig = rd.normalize().as_tuple().digits; L = len(rdig) + # 2. shortest & closest + best = None + for nsig in range(1, 18): + q = sig_round(ax, nsig) + ulp = Decimal(1).scaleb(q.adjusted() - nsig + 1) + cands = {q, q - ulp, q + ulp} + ok = [c for c in cands if c > 0 and nearest(Fraction(c)) == ax] + if ok: + dist = {c: abs(Fraction(c) - exact) for c in ok} + m = min(dist.values()) + closest = [c for c in ok if dist[c] == m] + best = (nsig, closest); break + nsig, closest = best + if L != nsig: + bad("repr-not-shortest", (r, nsig, [str(c) for c in closest])); continue + if rd.normalize() not in [c.normalize() for c in closest]: + # repr is shortest but not the closest candidate + if any(nearest(Fraction(c)) == ax for c in closest): + bad("repr-not-closest", (r, [str(c) for c in closest], str(abs(Fraction(rd)-exact) - min(abs(Fraction(c)-exact) for c in closest)))) + if len(closest) > 1: ambiguous += 1 + print(f"repr: {cnt} values in {time.time()-t0:.1f}s, {len(report.get('repr-roundtrip', [])) + len(report.get('repr-not-shortest', [])) + len(report.get('repr-not-closest', []))} mismatches, {ambiguous} equidistant ties") + +# ---------------------------------------------------------------- round +def check_round(n): + t0 = time.time(); cnt = 0 + for x in random_values(n): + fx = Fraction(x) + for nd in rng.sample(range(-25, 26), 6): + cnt += 1 + got = round(x, nd) + if nd >= 0: + r = round(fx * 10 ** nd); fr = Fraction(r, 10 ** nd) + else: + r = round(fx / 10 ** (-nd)); fr = Fraction(r * 10 ** (-nd)) + exp = nearest(abs(fr)) if fr else 0.0 + if fr < 0: exp = -exp + if fr == 0: exp = math.copysign(0.0, x) + if tobits(got) != tobits(exp): bad("round", (repr(x), nd, got, exp)) + print(f"round: {cnt} cases in {time.time()-t0:.1f}s, {len(report.get('round', []))} mismatches") + +print(sys.version.split()[0], "float_repr_style =", sys.float_repr_style, "seed", SEED, "N", N, flush=True) +check_parse(N); check_format(N); check_repr(N // 4); check_round(N) +for k, v in report.items(): + print(f"== {k}: {len(v)}") + for item in v[:8]: print(" ", item) From e980460518e30f19042166e3b035abf5e4182731 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 20 Oct 2026 20:29:20 +0200 Subject: [PATCH 10/13] add ffc parse backend (ffc.h, C99 port of fast_float): the CPython parse candidate Vendors kolemannix/ffc.h v26.09.01+1 (7e85c6d; Apache-2.0 / MIT / BSL-1.0 like fast_float) and adds an "ffc" parse backend: the same Clinger + Eisel-Lemire + bigint digit-comparison pipeline as the fast_float backend, but a single C99 header with no C++. Used by valkey and hiredis. Compiles under CPython's -std=c11 without warnings. The adapter (src/cpython_adapter/ffc_strtod.c) is plain C with the _Py_dg_strtod contract: no whitespace skipping, inf/nan left to _Py_parse_inf_or_nan, endptr and errno = ERANGE semantics. Chosen over Wuffs for the CPython proposal: Wuffs' parser is an extract of a transpiler-generated multi-codec file, rejects numbers with leading zeros (10,839 of 268k hard-case strings, 12 of 5.3M corpus lines, all of which float() must accept), and its HPD fallback is ~12x slower on 17-digit inputs. ffc.h matched fast_float and glibc bit for bit on all 5.3M published corpus lines and 268k exact-midpoint / long-mantissa cases, tracks fast_float within noise in BENCHMARKS.md, and passes tools/fuzz_float_oracle.py (1M checks) installed as zmij + ffc. INTERNALS.md now names zmij + ffc as the CPython candidate pair and explains the choice; BENCHMARKS.md gains the ffc column. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/ci.yml | 2 +- BENCHMARKS.md | 41 +- CMakeLists.txt | 15 +- INTERNALS.md | 28 +- README.md | 8 +- floatium/__init__.py | 2 +- src/backends/ffc_parse.cc | 28 + src/backends/zmij_format.cc | 2 +- src/cpython_adapter/ffc_strtod.c | 56 + third_party/ffc/LICENSE-APACHE | 190 ++ third_party/ffc/LICENSE-BOOST | 23 + third_party/ffc/LICENSE-MIT | 27 + third_party/ffc/README.vendor | 44 + third_party/ffc/ffc.h | 3798 ++++++++++++++++++++++++++++++ tools/sync_ffc.sh | 87 + 15 files changed, 4316 insertions(+), 35 deletions(-) create mode 100644 src/backends/ffc_parse.cc create mode 100644 src/cpython_adapter/ffc_strtod.c create mode 100644 third_party/ffc/LICENSE-APACHE create mode 100644 third_party/ffc/LICENSE-BOOST create mode 100644 third_party/ffc/LICENSE-MIT create mode 100644 third_party/ffc/README.vendor create mode 100644 third_party/ffc/ffc.h create mode 100755 tools/sync_ffc.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 373794b..0129427 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,7 @@ jobs: shell: bash test-all-backends: - # Every optional backend (fmt, ryu_opt, zmij, wuffs, stock) compiled + # Every optional backend (fmt, ryu_opt, zmij, wuffs, ffc, stock) compiled # in, so tests/test_backends.py can check each one against stock. # The wheels only ship the defaults; this job is what keeps the # others honest — and, on all three OSes, what proves the pure-C diff --git a/BENCHMARKS.md b/BENCHMARKS.md index d402674..2eedba0 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -3,7 +3,7 @@ Nanoseconds per operation for `repr(float)`, fixed-precision f-string formatting, and `float(str)`, comparing stock CPython against every floatium format backend (`fmt_opt`, `fmt`, `ryu_opt`, `zmij`) and every parse -backend (`fast_float`, `wuffs`, `stock`). +backend (`fast_float`, `wuffs`, `ffc`, `stock`). ## Methodology @@ -39,26 +39,29 @@ faster than stock. | Corpus | Operation | Stock (ns) | fmt_opt | fmt | ryu_opt | zmij | fmt_opt speedup | fmt speedup | ryu_opt speedup | zmij speedup | |---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:| -| random_uniform | `repr(x)` | 284.0 | 90.8 | 89.5 | 91.5 | 102.9 | 3.13x | 3.17x | 3.10x | 2.76x | -| random_uniform | `f"{x:.4f}"` | 119.2 | 100.0 | 100.2 | 104.4 | 109.4 | 1.19x | 1.19x | 1.14x | 1.09x | -| random_bits | `repr(x)` | 819.9 | 131.2 | 128.5 | 143.0 | 154.3 | 6.25x | 6.38x | 5.73x | 5.31x | -| random_bits | `f"{x:.4f}"` | 1,933.7 | 193.2 | 5,539.0 | 211.5 | 452.3 | 10.01x | 0.35x | 9.14x | 4.28x | -| financial | `repr(x)` | 172.5 | 72.4 | 71.9 | 96.6 | 90.6 | 2.38x | 2.40x | 1.78x | 1.90x | -| financial | `f"{x:.4f}"` | 144.6 | 97.1 | 96.0 | 115.9 | 113.8 | 1.49x | 1.51x | 1.25x | 1.27x | -| scientific | `repr(x)` | 635.2 | 127.5 | 127.1 | 139.5 | 150.6 | 4.98x | 5.00x | 4.55x | 4.22x | -| scientific | `f"{x:.4f}"` | 1,078.2 | 158.7 | 2,969.7 | 170.2 | 279.4 | 6.79x | 0.36x | 6.33x | 3.86x | -| integer_valued | `repr(x)` | 142.1 | 83.4 | 81.2 | 78.2 | 94.0 | 1.70x | 1.75x | 1.82x | 1.51x | -| integer_valued | `f"{x:.4f}"` | 167.3 | 99.2 | 101.5 | 108.1 | 108.1 | 1.69x | 1.65x | 1.55x | 1.55x | +| random_uniform | `repr(x)` | 284.6 | 91.0 | 90.6 | 93.8 | 100.7 | 3.13x | 3.14x | 3.03x | 2.83x | +| random_uniform | `f"{x:.4f}"` | 119.2 | 97.9 | 97.6 | 102.9 | 109.8 | 1.22x | 1.22x | 1.16x | 1.09x | +| random_bits | `repr(x)` | 821.9 | 130.9 | 129.0 | 146.1 | 153.3 | 6.28x | 6.37x | 5.62x | 5.36x | +| random_bits | `f"{x:.4f}"` | 1,936.1 | 193.6 | 5,550.4 | 213.6 | 452.8 | 10.00x | 0.35x | 9.07x | 4.28x | +| financial | `repr(x)` | 169.5 | 73.5 | 73.5 | 99.2 | 89.3 | 2.30x | 2.31x | 1.71x | 1.90x | +| financial | `f"{x:.4f}"` | 143.5 | 95.2 | 96.0 | 114.1 | 114.0 | 1.51x | 1.49x | 1.26x | 1.26x | +| scientific | `repr(x)` | 633.8 | 127.6 | 126.9 | 142.1 | 149.7 | 4.97x | 5.00x | 4.46x | 4.23x | +| scientific | `f"{x:.4f}"` | 1,079.2 | 159.3 | 2,973.1 | 172.2 | 279.4 | 6.77x | 0.36x | 6.27x | 3.86x | +| integer_valued | `repr(x)` | 141.8 | 81.8 | 82.0 | 78.0 | 92.4 | 1.73x | 1.73x | 1.82x | 1.54x | +| integer_valued | `f"{x:.4f}"` | 167.6 | 98.2 | 99.0 | 107.2 | 109.3 | 1.71x | 1.69x | 1.56x | 1.53x | ## Parse backend `float(s)` over each corpus, `s` being a stock CPython repr. Speedup is -`stock / backend`. +`stock / backend`. `ffc` (ffc.h, the C99 port of fast_float) is the +pure-C parse backend proposed for CPython; it tracks `fast_float` +within noise, while `wuffs` loses on 17-digit inputs (`random_bits`) +where its high-precision-decimal fallback runs. -| Corpus | Operation | Stock (ns) | fast_float | wuffs | stock | fast_float speedup | wuffs speedup | stock speedup | -|---|---|---:|---:|---:|---:|---:|---:|---:| -| random_uniform | `float(s)` | 121.0 | 46.6 | 56.9 | 109.1 | 2.60x | 2.13x | 1.11x | -| random_bits | `float(s)` | 278.5 | 62.7 | 297.5 | 210.4 | 4.44x | 0.94x | 1.32x | -| financial | `float(s)` | 36.7 | 36.6 | 41.6 | 78.5 | 1.00x | 0.88x | 0.47x | -| scientific | `float(s)` | 212.5 | 57.1 | 68.0 | 186.2 | 3.72x | 3.13x | 1.14x | -| integer_valued | `float(s)` | 43.0 | 42.8 | 47.6 | 68.0 | 1.01x | 0.90x | 0.63x | +| Corpus | Operation | Stock (ns) | fast_float | wuffs | ffc | stock | fast_float speedup | wuffs speedup | ffc speedup | stock speedup | +|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| random_uniform | `float(s)` | 120.8 | 45.6 | 56.2 | 44.2 | 109.3 | 2.65x | 2.15x | 2.73x | 1.11x | +| random_bits | `float(s)` | 276.5 | 58.8 | 293.4 | 59.5 | 212.1 | 4.70x | 0.94x | 4.65x | 1.30x | +| financial | `float(s)` | 36.6 | 37.6 | 39.9 | 37.3 | 79.9 | 0.98x | 0.92x | 0.98x | 0.46x | +| scientific | `float(s)` | 214.3 | 57.1 | 67.3 | 56.9 | 187.7 | 3.75x | 3.18x | 3.77x | 1.14x | +| integer_valued | `float(s)` | 43.0 | 43.4 | 47.9 | 44.1 | 66.4 | 0.99x | 0.90x | 0.98x | 0.65x | diff --git a/CMakeLists.txt b/CMakeLists.txt index 190d823..53bcdfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,8 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # upstream library; the CPython-proposal backend), # all. # Parse backend: fast_float (default), wuffs (pure-C Eisel-Lemire + HPD), +# ffc (pure-C: ffc.h, the C99 port of fast_float — +# the CPython-proposal backend), # stock (libc strtod via floatium's wrapper), all. # # For a "stock CPython" format baseline, just `floatium.uninstall()` — @@ -28,9 +30,9 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # With "all", every backend is compiled in and the active one is chosen at # runtime via env vars FLOATIUM_FORMAT_BACKEND / FLOATIUM_PARSE_BACKEND. set(FLOATIUM_FORMAT_BACKEND "fmt_opt" CACHE STRING "Format backend: fmt_opt, fmt, ryu_opt, zmij, all") -set(FLOATIUM_PARSE_BACKEND "fast_float" CACHE STRING "Parse backend: fast_float, wuffs, stock, all") +set(FLOATIUM_PARSE_BACKEND "fast_float" CACHE STRING "Parse backend: fast_float, wuffs, ffc, stock, all") set_property(CACHE FLOATIUM_FORMAT_BACKEND PROPERTY STRINGS fmt_opt fmt ryu_opt zmij all) -set_property(CACHE FLOATIUM_PARSE_BACKEND PROPERTY STRINGS fast_float wuffs stock all) +set_property(CACHE FLOATIUM_PARSE_BACKEND PROPERTY STRINGS fast_float wuffs ffc stock all) # --- Python ----------------------------------------------------------------- # scikit-build-core passes Python3_EXECUTABLE via the FindPython hint file. @@ -96,6 +98,13 @@ if(FLOATIUM_PARSE_BACKEND STREQUAL "wuffs" OR FLOATIUM_PARSE_BACKEND STREQUAL "a ) set(FLOATIUM_HAS_WUFFS_PARSE 1) endif() +if(FLOATIUM_PARSE_BACKEND STREQUAL "ffc" OR FLOATIUM_PARSE_BACKEND STREQUAL "all") + list(APPEND FLOATIUM_SRCS + src/backends/ffc_parse.cc + src/cpython_adapter/ffc_strtod.c + ) + set(FLOATIUM_HAS_FFC_PARSE 1) +endif() if(FLOATIUM_PARSE_BACKEND STREQUAL "stock" OR FLOATIUM_PARSE_BACKEND STREQUAL "all") list(APPEND FLOATIUM_SRCS src/backends/stock_parse.cc) set(FLOATIUM_HAS_STOCK_PARSE 1) @@ -111,6 +120,7 @@ target_include_directories(_ext PRIVATE third_party/ryu third_party/wuffs third_party/zmij + third_party/ffc ) # Match the build flags the CPython fmt-fastfloat branch uses for the @@ -155,6 +165,7 @@ target_compile_definitions(_ext PRIVATE $<$:FLOATIUM_HAS_ZMIJ_FORMAT=1> $<$:FLOATIUM_HAS_FAST_FLOAT_PARSE=1> $<$:FLOATIUM_HAS_WUFFS_PARSE=1> + $<$:FLOATIUM_HAS_FFC_PARSE=1> $<$:FLOATIUM_HAS_STOCK_PARSE=1> FLOATIUM_DEFAULT_FORMAT_BACKEND="${FLOATIUM_DEFAULT_FORMAT}" FLOATIUM_DEFAULT_PARSE_BACKEND="${FLOATIUM_DEFAULT_PARSE}" diff --git a/INTERNALS.md b/INTERNALS.md index c618ce3..a2f52d6 100644 --- a/INTERNALS.md +++ b/INTERNALS.md @@ -12,7 +12,7 @@ see the [README](README.md) first. 'format_backend': 'fmt_opt', 'parse_backend': 'fast_float', 'available_format_backends': 'fmt,fmt_opt,ryu_opt,zmij', - 'available_parse_backends': 'fast_float,wuffs,stock', + 'available_parse_backends': 'fast_float,wuffs,ffc,stock', 'default_format_backend': 'fmt_opt', 'default_parse_backend': 'fast_float'} ``` @@ -43,6 +43,7 @@ time, or build all of them and switch at install time: |----------------------|------------------------------------------------------------|------| | `fast_float` (default) | Eisel–Lemire 64-bit + bignum fallback | yes | | `wuffs` | Wuffs `parse_number_f64` — Eisel–Lemire + HPD | **no** | +| `ffc` | ffc.h, the C99 port of fast_float — same pipeline, no C++ | **no** | | `stock` | libc `strtod` via floatium's wrapper | no | For a true stock-CPython baseline on the format side, call @@ -52,15 +53,15 @@ formatter produces a packaged string directly. The parse side does have a `stock` backend (libc `strtod`) because libc happens to match the contract. -`zmij` or `ryu_opt` paired with `wuffs` gives fully pure-C operation — -zero C++ on either the format or parse path: +`zmij` or `ryu_opt` paired with `ffc` or `wuffs` gives fully pure-C +operation — zero C++ on either the format or parse path: ```python import floatium -floatium.install(format_backend="zmij", parse_backend="wuffs") +floatium.install(format_backend="zmij", parse_backend="ffc") ``` -## The CPython candidate: `zmij` + `wuffs` +## The CPython candidate: `zmij` + `ffc` `zmij` is the configuration floatium proposes for CPython itself, and it is built to be lifted out as-is: @@ -89,7 +90,20 @@ already computes internally) so mode 0 needs no text round-trip, and The parse side is independent of this: `_Py_dg_strtod` and `_Py_dg_dtoa` only share dtoa.c's bignum helpers. A format-only change can land first, but `dtoa.c` can only be deleted once string-to-float -is replaced too; `wuffs` is the pure-C candidate for that half. +is replaced too. `ffc` is the candidate for that half: one unmodified +single header ([ffc.h](https://github.com/kolemannix/ffc.h), the C99 +port of fast_float, Apache-2.0 / MIT / BSL-1.0) plus the plain-C +adapter `src/cpython_adapter/ffc_strtod.c`, which only maps +`ffc_from_chars_double_options` onto the `_Py_dg_strtod` contract +(`endptr`, `errno = ERANGE`, inf/nan left to `_Py_parse_inf_or_nan`). +It was chosen over Wuffs because Wuffs' parser is an extract of a +transpiler-generated file, rejects leading zeros (`float("0001.5")` +would have to fall back), and is ~12× slower on 17-digit inputs; ffc.h +matched fast_float bit for bit on 5.3M published corpus lines and +268k exact-midpoint / long-mantissa cases, and `tools/fuzz_float_oracle.py` +passes with it installed. Neither zmij nor ffc.h performs any +floating-point arithmetic, so the x87 precision control dtoa.c needs +(and the `legacy` float repr mode it implies) can go too. The `ryu_opt` adapter (mode dispatch, FP fast path for `%e`/`%g`, banker's rounding for `round(x, k)` with negative `k`) is ported from @@ -108,7 +122,7 @@ combinations: # Build only the pure-C path (smaller wheel, no C++ in float I/O): pip install -e . \ -C cmake.define.FLOATIUM_FORMAT_BACKEND=zmij \ - -C cmake.define.FLOATIUM_PARSE_BACKEND=wuffs + -C cmake.define.FLOATIUM_PARSE_BACKEND=ffc # Build every backend, switch at install time: pip install -e . \ diff --git a/README.md b/README.md index d0d1f65..4fd57af 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ pip install -e '.[test]' ``` No system libraries are required; `{fmt}`, `fast_float`, Ryu, Żmij, -and Wuffs are all vendored. See [INTERNALS.md](INTERNALS.md) for the build +ffc.h, and Wuffs are all vendored. See [INTERNALS.md](INTERNALS.md) for the build options and backend matrix. ## Why this package exists @@ -82,10 +82,10 @@ there's not enough work to amortize either parser's setup cost. The table above is the default configuration. For the full per-backend matrix — `fmt_opt` / `fmt` / `ryu_opt` / `zmij` formatting -and `fast_float` / `wuffs` / `stock` parsing — see -[BENCHMARKS.md](BENCHMARKS.md). The `zmij` + `wuffs` pair is the pure-C +and `fast_float` / `wuffs` / `ffc` / `stock` parsing — see +[BENCHMARKS.md](BENCHMARKS.md). The `zmij` + `ffc` pair is the pure-C configuration floatium proposes for CPython itself; see -[INTERNALS.md](INTERNALS.md#the-cpython-candidate-zmij--wuffs). +[INTERNALS.md](INTERNALS.md#the-cpython-candidate-zmij--ffc). Run locally with: diff --git a/floatium/__init__.py b/floatium/__init__.py index 48a2af5..e9e3905 100644 --- a/floatium/__init__.py +++ b/floatium/__init__.py @@ -1,7 +1,7 @@ """floatium — drop-in replacement for CPython float formatting/parsing. Backs float repr/str/__format__/__new__ with the {fmt} and fast_float C++ -libraries (or a pure-C combination of Żmij or Ryu + Wuffs) to demonstrate the +libraries (or a pure-C combination of Żmij + ffc.h) to demonstrate the performance and correctness case for a forthcoming CPython PEP. Basic usage: diff --git a/src/backends/ffc_parse.cc b/src/backends/ffc_parse.cc new file mode 100644 index 0000000..79d685b --- /dev/null +++ b/src/backends/ffc_parse.cc @@ -0,0 +1,28 @@ +// ffc: pure-C parse backend backed by ffc.h, the C99 port of fast_float +// (Clinger + Eisel-Lemire + bigint digit comparison). Same algorithm and +// grammar as the fast_float backend, but with no C++ anywhere — paired +// with the zmij format backend this is the configuration floatium +// proposes for CPython. +#include "common/backend.h" + +#include + +extern "C" { +// Provided by src/cpython_adapter/ffc_strtod.c (compiled as C). +double _Py_ffc_strtod(const char *nptr, char **endptr); +} + +namespace { + +struct Register { + Register() { + static const FloatiumParseBackend b = { + /*name=*/"ffc", + /*strtod=*/&_Py_ffc_strtod, + }; + floatium::register_parse_backend(b); + } +}; +Register _reg; + +} // namespace diff --git a/src/backends/zmij_format.cc b/src/backends/zmij_format.cc index 9682871..0dbc380 100644 --- a/src/backends/zmij_format.cc +++ b/src/backends/zmij_format.cc @@ -3,7 +3,7 @@ // correctly rounded fixed / scientific writers, bigint-backed above 18 // digits). This is the backend floatium proposes for CPython itself: one // unmodified upstream C library plus the plain-C adapter in -// src/cpython_adapter/zmij_dtoa.c. Pair with parse_backend="wuffs" for a +// src/cpython_adapter/zmij_dtoa.c. Pair with parse_backend="ffc" for a // fully pure-C float path. #include "common/backend.h" diff --git a/src/cpython_adapter/ffc_strtod.c b/src/cpython_adapter/ffc_strtod.c new file mode 100644 index 0000000..bfd1a3e --- /dev/null +++ b/src/cpython_adapter/ffc_strtod.c @@ -0,0 +1,56 @@ +/* ffc_strtod.c - _Py_dg_strtod drop-in backed by ffc.h (C99 fast_float port). + * + * This is the parse half of the pair floatium proposes for CPython (the + * format half is zmij_dtoa.c), so it is plain C11 against the exact + * _Py_dg_strtod contract and uses nothing from CPython: + * + * double _Py_ffc_strtod(const char *nptr, char **endptr); + * + * - no leading-whitespace skipping (PyOS_string_to_double relies on it); + * - optional sign, decimal mantissa with optional '.', optional exponent; + * - "inf" / "nan" are NOT accepted: pystrtod.c's _Py_parse_inf_or_nan + * owns those so CPython's exact spellings and casing rules stay put; + * - *endptr points past the consumed characters, or at nptr when no + * number was found (return value 0.0); + * - overflow returns +-HUGE_VAL, underflow the nearest value (possibly + * 0 or subnormal); both set errno = ERANGE like strtod. + * + * ffc.h is the same pipeline as fast_float: Clinger fast path, then + * Eisel-Lemire, then an exact big-integer digit comparison for the rare + * hard cases. Every result is correctly rounded (ties to even) and no + * floating-point arithmetic is done on the way, so the result does not + * depend on the x87 precision mode that dtoa.c needs to control. + */ + +#define FFC_IMPL +#include "ffc.h" + +#include +#include + +double +_Py_ffc_strtod(const char *nptr, char **endptr) +{ + const char *last = nptr + strlen(nptr); + double value = 0.0; + + ffc_parse_options options = ffc_parse_options_default(); + options.format = FFC_PRESET_GENERAL + | FFC_FORMAT_FLAG_ALLOW_LEADING_PLUS + | FFC_FORMAT_FLAG_NO_INFNAN; + ffc_result result = ffc_from_chars_double_options(nptr, last, &value, options); + + if (result.outcome == FFC_OUTCOME_INVALID_INPUT) { + if (endptr) { + *endptr = (char *)nptr; + } + return 0.0; + } + if (endptr) { + *endptr = (char *)result.ptr; + } + if (result.outcome == FFC_OUTCOME_OUT_OF_RANGE) { + errno = ERANGE; + } + return value; +} diff --git a/third_party/ffc/LICENSE-APACHE b/third_party/ffc/LICENSE-APACHE new file mode 100644 index 0000000..26f4398 --- /dev/null +++ b/third_party/ffc/LICENSE-APACHE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2021 The fast_float authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/ffc/LICENSE-BOOST b/third_party/ffc/LICENSE-BOOST new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/third_party/ffc/LICENSE-BOOST @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/third_party/ffc/LICENSE-MIT b/third_party/ffc/LICENSE-MIT new file mode 100644 index 0000000..2fb2a37 --- /dev/null +++ b/third_party/ffc/LICENSE-MIT @@ -0,0 +1,27 @@ +MIT License + +Copyright (c) 2021 The fast_float authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/third_party/ffc/README.vendor b/third_party/ffc/README.vendor new file mode 100644 index 0000000..1d31987 --- /dev/null +++ b/third_party/ffc/README.vendor @@ -0,0 +1,44 @@ +ffc.h — C99 port of fast_float (string-to-double) +================================================= + +Upstream: https://github.com/kolemannix/ffc.h +Version: v26.09.01-1-g7e85c6d (FFC_VERSION 26.09.01) +Commit: 7e85c6d (2026-10-02) +License: Apache-2.0 OR MIT OR BSL-1.0 + (see LICENSE-APACHE, LICENSE-MIT, LICENSE-BOOST) + +Files vendored +-------------- +ffc.h — the amalgamated single header: Clinger fast path, + Eisel-Lemire, and fast_float's digit-comparison bigint + fallback, ported line by line from fast_float to C99. + Define FFC_IMPL in exactly one C translation unit. + +Why this and not fast_float / Wuffs +----------------------------------- +fast_float is C++, which CPython cannot vendor. Wuffs is C but its +float parser is an extract of a transpiler-generated multi-codec file, +rejects numbers with leading zeros ("0001.5"), and falls into a slow +high-precision-decimal path on 17-digit inputs (~12x slower than +ffc.h on floatium's random_bits corpus). ffc.h is a direct port of the +same algorithm fast_float uses, accepts the full strtod grammar, and +matched fast_float bit for bit on 5.3M published corpus lines plus +268k exact-midpoint / long-mantissa cases. It is used by valkey and +hiredis. + +Used by which backends +---------------------- +ffc — bridges floatium's parse path to ffc_from_chars_double_options + via src/cpython_adapter/ffc_strtod.c (plain C, _Py_dg_strtod + contract). Together with the zmij format backend this is the + pure-C pair proposed for CPython. + +Build configuration +------------------- + $(CC) -std=c11 (compiles cleanly under CPython's flags) + +Local modifications +------------------- +None. The vendored files are byte-identical to upstream. + +Resync: tools/sync_ffc.sh diff --git a/third_party/ffc/ffc.h b/third_party/ffc/ffc.h new file mode 100644 index 0000000..8ebbb94 --- /dev/null +++ b/third_party/ffc/ffc.h @@ -0,0 +1,3798 @@ +// fast_float by Daniel Lemire (Original) +// fast_float by João Paulo Magalhaes (Original) +// fast_float by Koleman Nix (C Port) +// +// +// with contributions from Eugene Golushkov +// with contributions from Maksim Kita +// with contributions from Marcin Wojdyr +// with contributions from Neal Richardson +// with contributions from Tim Paine +// with contributions from Fabio Pellacini +// with contributions from Lénárd Szolnoki +// with contributions from Jan Pharago +// with contributions from Maya Warrier +// with contributions from Taha Khokhar +// with contributions from Anders Dalvander +// with contributions from Koleman Nix +// with contributions from Michael Grunder +// +// +// Licensed under the Apache License, Version 2.0, or the +// MIT License or the Boost License. This file may not be copied, +// modified, or distributed except according to those terms. +// +// MIT License Notice +// +// MIT License +// +// Copyright (c) 2021 The fast_float authors +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// Apache License (Version 2.0) Notice +// +// Copyright 2021 The fast_float authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// +// BOOST License Notice +// +// Boost Software License - Version 1.0 - August 17th, 2003 +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// + +// This file is auto-generated by amalgamate.py from the sources in src/. +// Do not edit it directly — edit the source files and regenerate. +// + +/* ffc.h + single-header decimal float parser using eisel-lemire + This is a direct port by Koleman Nix of the fast_float library + authored by Daniel Lemire +*/ + +#ifndef FFC_H +#define FFC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef FFC_API +#define FFC_API + +#define FFC_VERSION_YEAR 26 +#define FFC_VERSION_MONTH 09 +#define FFC_VERSION_BUILD 01 +#define FFC_VERSION ((FFC_VERSION_YEAR << 16) | (FFC_VERSION_MONTH << 8) | (FFC_VERSION_BUILD)) +#define FFC_VERSION_STRINGIFY_(x) #x +#define FFC_VERSION_STRINGIFY(x) FFC_VERSION_STRINGIFY_(x) +#define FFC_VERSION_STRING \ + FFC_VERSION_STRINGIFY(FFC_VERSION_YEAR) "." \ + FFC_VERSION_STRINGIFY(FFC_VERSION_MONTH) "." \ + FFC_VERSION_STRINGIFY(FFC_VERSION_BUILD) + +#include +#include + +/* always_inline marker for internal static helpers. + * common.h re-uses this same definition under an #ifndef guard. */ +#if defined(_MSC_VER) + #define ffc_inline __forceinline +#elif defined(__GNUC__) || defined(__clang__) + #define ffc_inline __attribute__((always_inline)) inline +#else + #define ffc_inline inline +#endif + +typedef uint32_t ffc_outcome; +enum ffc_outcome_bits { + FFC_OUTCOME_OK = 0, + FFC_OUTCOME_INVALID_INPUT = 1, + FFC_OUTCOME_OUT_OF_RANGE = 2, +}; + +typedef struct ffc_result { + // Where parsing stopped + char const *ptr; + // The outcome of the call + ffc_outcome outcome; +} ffc_result; + +typedef uint64_t ffc_format; +enum ffc_format_bits { + FFC_FORMAT_FLAG_SCIENTIFIC = 1ULL << 0, + FFC_FORMAT_FLAG_FIXED = 1ULL << 2, // Gap is present in fast_float original + FFC_FORMAT_FLAG_HEX = 1ULL << 3, + FFC_FORMAT_FLAG_NO_INFNAN = 1ULL << 4, + FFC_FORMAT_FLAG_BASIC_JSON = 1ULL << 5, + FFC_FORMAT_FLAG_BASIC_FORTRAN = 1ULL << 6, + FFC_FORMAT_FLAG_ALLOW_LEADING_PLUS = 1ULL << 7, + FFC_FORMAT_FLAG_SKIP_WHITE_SPACE = 1ULL << 8, + + /* Presets */ + FFC_PRESET_GENERAL = FFC_FORMAT_FLAG_FIXED | FFC_FORMAT_FLAG_SCIENTIFIC, + + FFC_PRESET_JSON = FFC_FORMAT_FLAG_BASIC_JSON | + FFC_PRESET_GENERAL | + FFC_FORMAT_FLAG_NO_INFNAN, + + FFC_PRESET_JSON_OR_INFNAN = FFC_FORMAT_FLAG_BASIC_JSON | + FFC_PRESET_GENERAL, + + FFC_PRESET_FORTRAN = FFC_FORMAT_FLAG_BASIC_FORTRAN | + FFC_PRESET_GENERAL +}; + +typedef struct ffc_parse_options { + /** Which number formats are accepted */ + ffc_format format; + /** The character used as decimal point; period will be used if decimal_point == '\0' */ + char decimal_point; +} ffc_parse_options; + +ffc_parse_options ffc_parse_options_default(void); + +typedef enum ffc_parse_outcome { + FFC_PARSE_OUTCOME_NO_ERROR = 0, + // [JSON-only] The minus sign must be followed by an integer. + FFC_PARSE_OUTCOME_JSON_MISSING_INTEGER_AFTER_SIGN = 1, + // A sign must be followed by an integer or dot. + FFC_PARSE_OUTCOME_MISSING_INTEGER_OR_DOT_AFTER_SIGN = 2, + // [JSON-only] The integer part must not have leading zeros. + FFC_PARSE_OUTCOME_JSON_LEADING_ZEROS_IN_INTEGER_PART = 3, + // [JSON-only] The integer part must have at least one digit. + FFC_PARSE_OUTCOME_JSON_NO_DIGITS_IN_INTEGER_PART = 4, + // [JSON-only] If there is a decimal point, there must be digits in the + // fractional part. + FFC_PARSE_OUTCOME_JSON_NO_DIGITS_IN_FRACTIONAL_PART = 5, + // The mantissa must have at least one digit. + FFC_PARSE_OUTCOME_NO_DIGITS_IN_MANTISSA = 6, + // Scientific notation requires an exponential part. + FFC_PARSE_OUTCOME_MISSING_EXPONENTIAL_PART = 7, +} ffc_parse_outcome; + +/* + * A simplified API; the result will be 0.0 on error, not uninitialized. + * If outcome is null, it will not be written to + */ +double ffc_parse_double_simple(size_t len, const char *input, ffc_outcome *outcome); +ffc_result ffc_parse_double(size_t len, const char *input, double *out); +/** + * Implements the fast_float algorithm from https://github.com/fastfloat/fast_float + * See original for more details + * + * This function parses the character sequence [first,last) for a number. It + * parses floating-point numbers expecting a locale-independent format equivalent + * to what is used by std::strtod in the default ("C") locale. The resulting + * floating-point value is the closest floating-point value (using either float + * or double), using the "round to even" convention for values that would + * otherwise fall right in-between two values. That is, we provide exact parsing + * according to the IEEE standard. + * + * Given a successful parse, the pointer (`ptr`) in the returned value is set to + * point right after the parsed number, and the `value` referenced is set to the + * parsed value. In case of error, the returned `ec` contains a representative + * error, otherwise the default (`FFC_OUTCOME_OK`) value is stored. + * + * The implementation does not allocate heap memory. + * + * Like the C++17 standard, the `fast_float::from_chars` functions take an + * optional last argument of the type `fast_float::chars_format`. It is a bitset + * value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt & + * fast_float::chars_format::scientific` are set to determine whether we allow + * the fixed point and scientific notation respectively. The default is + * `fast_float::chars_format::general` which allows both `fixed` and + * `scientific`. + */ +/* When included from a FFC_IMPL translation unit, the critical-path API + * functions below are declared always_inline so calls in that same TU get the + * parser specialized at the call site. In non-FFC_IMPL TUs the declarations + * are plain extern. + * + * FFC_API_INLINE is the one place that knows how to spell this so that the + * function is force-inlined in-TU while still being emitted as an external + * symbol for callers in other TUs: + * - Clang honors always_inline without the `inline` keyword, in C and C++. + * Spelling it with `inline` would give the function inline linkage in C++ + * and the symbol would not be emitted, breaking separate-TU callers. + * - GCC only honors always_inline on functions declared `inline`; in C99 + * `extern inline` still yields an external definition. In C++ it would + * not, so the GCC branch is C-only. + * - Elsewhere the API is left out-of-line. */ +#if defined(__clang__) +# define FFC_API_INLINE __attribute__((always_inline)) +#elif defined(__GNUC__) && !defined(__cplusplus) +# define FFC_API_INLINE extern __attribute__((always_inline)) inline +#else +# define FFC_API_INLINE +#endif + +/* Double parsing is always inlined in the FFC_IMPL TU. */ +#ifdef FFC_IMPL +# define FFC_DOUBLE_IMPL_INLINE FFC_API_INLINE +#else +# define FFC_DOUBLE_IMPL_INLINE +#endif + +/* Float parsing is inlined only when FFC_ENABLE_FLOAT_INLINING is also + * defined: it speeds up float parsing considerably but grows the caller and + * has been measured to cost double parsing in the same TU a few percent. */ +#if defined(FFC_IMPL) && defined(FFC_ENABLE_FLOAT_INLINING) +# define FFC_FLOAT_IMPL_INLINE FFC_API_INLINE +#else +# define FFC_FLOAT_IMPL_INLINE +#endif + +FFC_DOUBLE_IMPL_INLINE ffc_result ffc_from_chars_double(const char *start, const char *end, double* out); +FFC_DOUBLE_IMPL_INLINE ffc_result ffc_from_chars_double_options(const char *start, const char *end, double* out, ffc_parse_options options); + +/* + * A simplified API; the result will be 0.0 on error, not uninitialized. + * If outcome is null, it will not be written to + */ +float ffc_parse_float_simple(size_t len, const char *s, ffc_outcome *outcome); +ffc_result ffc_parse_float(size_t len, const char *s, float *out); +FFC_FLOAT_IMPL_INLINE ffc_result ffc_from_chars_float(const char *start, const char *end, float* out); +FFC_FLOAT_IMPL_INLINE ffc_result ffc_from_chars_float_options(const char *start, const char *end, float* out, ffc_parse_options options); + + + +/* + * A simplified API; the result will be 0 on error, not uninitialized. + * If outcome is null, it will not be written to + */ +int64_t ffc_parse_i64_simple(size_t len, const char *input, int base, ffc_outcome *outcome); +ffc_result ffc_parse_i64(size_t len, const char *input, int base, int64_t *out); +ffc_result ffc_from_chars_i64(char const* first, char const* last, int base, int64_t* out); +ffc_result ffc_from_chars_i64_options(const char *start, const char *end, int base, int64_t* out, ffc_parse_options options); + +/* + * A simplified API; the result will be 0 on error, not uninitialized. + * If outcome is null, it will not be written to + */ +uint64_t ffc_parse_u64_simple(size_t len, const char *input, int base, ffc_outcome *outcome); +ffc_result ffc_parse_u64(size_t len, const char *input, int base, uint64_t *out); +ffc_result ffc_from_chars_u64(char const* first, char const* last, int base, uint64_t* out); +ffc_result ffc_from_chars_u64_options(const char *start, const char *end, int base, uint64_t* out, ffc_parse_options options); + +/* + * A simplified API; the result will be 0 on error, not uninitialized. + * If outcome is null, it will not be written to + */ +int32_t ffc_parse_i32_simple(size_t len, const char *input, int base, ffc_outcome *outcome); +ffc_result ffc_parse_i32(size_t len, const char *input, int base, int32_t *out); +ffc_result ffc_from_chars_i32(char const* first, char const* last, int base, int32_t* out); +ffc_result ffc_from_chars_i32_options(const char *start, const char *end, int base, int32_t* out, ffc_parse_options options); + +/* + * A simplified API; the result will be 0 on error, not uninitialized. + * If outcome is null, it will not be written to + */ +uint32_t ffc_parse_u32_simple(size_t len, const char *input, int base, ffc_outcome *outcome); +ffc_result ffc_parse_u32(size_t len, const char *input, int base, uint32_t *out); +ffc_result ffc_from_chars_u32(char const* first, char const* last, int base, uint32_t* out); +ffc_result ffc_from_chars_u32_options(const char *start, const char *end, int base, uint32_t* out, ffc_parse_options options); + +/** + * Parse a JSON number from the range [start, end) and return an int64_t or a double + * + * If the outcome is FCC_OUTCOME_OK + * If kind == FFC_JSON_NUM_KIND_INT64, value will be an int64 + * If kind == FCC_JSON_NUM_DOUBLE, value will be a double + * + * The returned ffc_result's ptr points at the byte where parsing stopped + */ + +typedef uint32_t ffc_json_number_kind; +enum ffc_json_number_kind_bits { + FFC_JSON_NUM_KIND_INT64 = 0, + FFC_JSON_NUM_KIND_DOUBLE = 1, +}; + +typedef struct ffc_json_number { + ffc_json_number_kind kind; + union { + int64_t i64; + double f64; + } value; +} ffc_json_number; + +ffc_result ffc_parse_json_number(const char *start, const char *end, ffc_json_number *out); + +/** + * Exact fixed-notation formatting, equivalent to printf("%.*f", places, value) + * on a libc that prints the exact binary value (glibc, musl, macOS): every + * digit is exact and rounding is round-half-to-even on the true binary value. + * No floating-point arithmetic is performed, so the output does not depend on + * the FPU rounding mode. Non-finite values print as "inf", "-inf", "nan", "-nan". + * + * A negative `places` means the printf default of 6. + * + * Writes at most `cap` bytes to `buf` and does not NUL-terminate. Returns the + * full length of the formatted number even when it exceeds `cap`, so a call + * with cap == 0 measures the required size. A buffer of 311 + places bytes + * (sign, up to 309 integer digits, the point, the fraction) always suffices. + * + * A float is exactly representable as a double, so pass (double)f to get the + * same output as printf("%.*f", places, f). + */ +size_t ffc_format_double_fixed(char *buf, size_t cap, double value, int places); + +#endif // FFC_API + +#ifdef FFC_IMPL + +#ifndef FFC_COMMON_H +#define FFC_COMMON_H + +#include // for NAN, FLT_MIN +#include +#include // for memcpy +#include + +#ifndef ffc_internal +#define ffc_internal static +#endif + +#ifndef ffc_inline +#if defined(_MSC_VER) + #define ffc_inline __forceinline +#elif defined(__GNUC__) || defined(__clang__) + #define ffc_inline __attribute__((always_inline)) inline +#else + #define ffc_inline inline +#endif +#endif + +#if FFC_DEBUG +#include +#define ffc_debug(...) do { fprintf(stderr, __VA_ARGS__); } while(0) +#else +#define ffc_debug(...) do { } while(0) +#endif + + +ffc_internal ffc_inline +uint64_t ffc_get_double_bits(double d) { + uint64_t bits; + memcpy(&bits, &d, sizeof(double)); + return bits; +} + +ffc_internal ffc_inline +uint32_t ffc_get_float_bits(float d) { + uint32_t bits; + memcpy(&bits, &d, sizeof(float)); + return bits; +} + +typedef union ffc_value { + double d; + float f; +} ffc_value; + +typedef union ffc_value_bits { + uint64_t di; + uint32_t fi; +} ffc_value_bits; + +typedef uint8_t ffc_value_kind; +enum ffc_value_kind_bits { + FFC_VALUE_KIND_FLOAT = 0, + FFC_VALUE_KIND_DOUBLE = 1, +}; + +typedef union ffc_int_value { + int64_t s64; + int32_t s32; + uint64_t u64; + uint32_t u32; +} ffc_int_value; + +typedef uint8_t ffc_int_kind; +enum ffc_int_kind_bits { + FFC_INT_KIND_S64 = 0, + FFC_INT_KIND_S32 = 1, + FFC_INT_KIND_U64 = 2, + FFC_INT_KIND_U32 = 3, +}; + +ffc_internal ffc_inline +bool ffc_int_kind_is_signed(ffc_int_kind ik) { + return ik == FFC_INT_KIND_S64 || ik == FFC_INT_KIND_S32; +} + + +ffc_internal ffc_inline +ffc_value_bits ffc_get_value_bits(ffc_value value, ffc_value_kind vk) { + ffc_value_bits bits; + if (vk == FFC_VALUE_KIND_DOUBLE) { + bits.di = ffc_get_double_bits(value.d); + } else { + bits.fi = ffc_get_float_bits(value.f); + } + return bits; +} + +ffc_internal ffc_inline +uint64_t ffc_int_value_max(ffc_int_kind ik) { + switch (ik) { + case FFC_INT_KIND_S64: return INT64_MAX; + case FFC_INT_KIND_S32: return INT32_MAX; + case FFC_INT_KIND_U64: return UINT64_MAX; + case FFC_INT_KIND_U32: return UINT32_MAX; + default: return 0; // should never happen + } +} + +#define ffc_set_value(ptr, value_kind, value) \ + (((value_kind) == FFC_VALUE_KIND_DOUBLE) ? ((ptr)->d = (double)(value)) : ((ptr)->f = (float)(value))) + +#define ffc_read_value(ptr, value_kind) \ + (((value_kind) == FFC_VALUE_KIND_DOUBLE) ? (ptr)->d : (ptr)->f) + +typedef struct ffc_adjusted_mantissa { + uint64_t mantissa; + int32_t power2; // a negative value indicates an invalid result +} ffc_adjusted_mantissa; + +ffc_internal ffc_inline size_t ffc_get_value_size(ffc_value_kind vk) { + if (vk == FFC_VALUE_KIND_DOUBLE) { + return sizeof(double); + } else { + return sizeof(float); + } +} + +// Bias so we can get the real exponent with an invalid adjusted_mantissa. +#define FFC_INVALID_AM_BIAS ((int32_t)-0x8000) + +/********************* context crack: word size *********************/ + +#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) || \ + defined(__MINGW64__) || defined(__s390x__) || \ + (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \ + defined(__PPC64LE__)) || \ + defined(__loongarch64) || (defined(__riscv) && __riscv_xlen == 64)) +#define FFC_64BIT 1 +#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__arm__) || defined(_M_ARM) || defined(__ppc__) || \ + defined(__MINGW32__) || defined(__EMSCRIPTEN__) || \ + (defined(__riscv) && __riscv_xlen == 32)) +#define FFC_32BIT 1 +#else +// 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. UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max +// portability. +#if SIZE_MAX == 0xffff +#error Unknown platform (16-bit, unsupported) +#elif SIZE_MAX == 0xffffffff +#define FFC_32BIT 1 +#elif SIZE_MAX == 0xffffffffffffffff +#define FFC_64BIT 1 +#else +#error Unknown platform (not 32-bit, not 64-bit?) +#endif +#endif + +/********************* context crack: intrinsics *********************/ +#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) || \ + (defined(_M_ARM64) && !defined(__MINGW32__)) +#include +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#define FFC_VISUAL_STUDIO 1 +#endif + +/********************* context crack: byte order / endianness *********************/ +#if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ +#define FFC_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#elif defined _WIN32 +#define FFC_IS_BIG_ENDIAN 0 +#else +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#elif defined(sun) || defined(__sun) +#include +#elif defined(__MVS__) +#include +#else +#ifdef __has_include +#if __has_include() +#include +#endif //__has_include() +#endif //__has_include +#endif +# +#ifndef __BYTE_ORDER__ +// safe choice +#define FFC_IS_BIG_ENDIAN 0 +#endif +# +#ifndef __ORDER_LITTLE_ENDIAN__ +// safe choice +#define FFC_IS_BIG_ENDIAN 0 +#endif +# +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define FFC_IS_BIG_ENDIAN 0 +#else +#define FFC_IS_BIG_ENDIAN 1 +#endif +#endif + +/********************* context crack: simd *********************/ +#if defined(__SSE2__) || (defined(FFC_VISUAL_STUDIO) && \ + (defined(_M_AMD64) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP == 2))) +#define FFC_SSE2 1 +#endif + +#if defined(__aarch64__) || defined(_M_ARM64) +#define FFC_NEON 1 +#endif + +#if defined(FFC_SSE2) || defined(FFC_NEON) +#define FFC_HAS_SIMD 1 +#endif + + +#ifdef FFC_SSE2 +#include +#endif + +#ifdef FFC_NEON +#include +#endif + + +#if defined(__GNUC__) +// disable -Wcast-align=strict (GCC only) +#define FFC_SIMD_DISABLE_WARNINGS \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wcast-align\"") +#else +#define FFC_SIMD_DISABLE_WARNINGS +#endif + +#if defined(__GNUC__) +#define FFC_SIMD_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") +#else +#define FFC_SIMD_RESTORE_WARNINGS +#endif + +ffc_internal ffc_inline +uint32_t ffc_count_leading_zeroes(uint64_t x) { +#if defined(__GNUC__) || defined(__clang__) + return x ? (uint32_t)__builtin_clzll(x) : 64u; +#else + if (x == 0) return 64u; + uint32_t n = 0; + if ((x >> 32) == 0) { n |= 32; x <<= 32; } + if ((x >> 48) == 0) { n |= 16; x <<= 16; } + if ((x >> 56) == 0) { n |= 8; x <<= 8; } + if ((x >> 60) == 0) { n |= 4; x <<= 4; } + if ((x >> 62) == 0) { n |= 2; x <<= 2; } + if ((x >> 63) == 0) { n |= 1; } + return n; +#endif +} + +typedef struct ffc_u128 { uint64_t low; uint64_t high; } ffc_u128; +ffc_internal ffc_inline +ffc_u128 ffc_mul_u64(uint64_t a, uint64_t b) { +#if defined(__SIZEOF_INT128__) + __uint128_t z = ((__uint128_t)a) * ((__uint128_t)b); + ffc_u128 output; + output.low = (uint64_t)z; + output.high = (uint64_t)(z >> 64); + return output; +#else + uint64_t a0 = a & 0xFFFFFFFF; + uint64_t a1 = a >> 32; + uint64_t b0 = b & 0xFFFFFFFF; + uint64_t b1 = b >> 32; + + uint64_t w0 = a0 * b0; + uint64_t t = (a1 * b0) + (w0 >> 32); + uint64_t w1 = t & 0xFFFFFFFF; + uint64_t w2 = t >> 32; + w1 += a0 * b1; + + ffc_u128 output; + output.low = a * b; + output.high = (a1 * b1) + w2 + (w1 >> 32); + return output; +#endif +} + +// slow emulation routine for 32-bit +ffc_internal ffc_inline uint64_t ffc_emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} + +ffc_internal ffc_inline +uint64_t ffc_umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { + uint64_t ad = ffc_emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = ffc_emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + ffc_emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = (uint64_t)(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = ffc_emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + (uint64_t)(lo < bd); + return lo; +} + +// compute 64-bit a*b +ffc_internal ffc_inline +ffc_u128 ffc_full_multiplication(uint64_t a, uint64_t b) { + ffc_u128 answer; +#if defined(_M_ARM64) && !defined(__MINGW32__) + // 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.low = a * b; +#elif (defined(_WIN64) && !defined(__clang__) && \ + !defined(_M_ARM64) && !defined(__GNUC__)) + answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 +#elif defined(FFC_64BIT) && defined(__SIZEOF_INT128__) + __uint128_t r = ((__uint128_t)a) * b; + answer.low = (uint64_t)(r); + answer.high = (uint64_t)(r >> 64); +#else + answer.low = ffc_umul128_generic(a, b, &answer.high); +#endif + return answer; +} + +#define FFC_DOUBLE_SMALLEST_POWER_OF_10 -342 +#define FFC_DOUBLE_LARGEST_POWER_OF_10 308 +#define FFC_DOUBLE_SIGN_INDEX 63 +#define FFC_DOUBLE_INFINITE_POWER 0x7FF +#define FFC_DOUBLE_MANTISSA_EXPLICIT_BITS 52 +#define FFC_DOUBLE_MINIMUM_EXPONENT -1023 +#define FFC_DOUBLE_MIN_EXPONENT_ROUND_TO_EVEN -4 +#define FFC_DOUBLE_MAX_EXPONENT_ROUND_TO_EVEN 23 +#define FFC_DOUBLE_MAX_EXPONENT_FAST_PATH 22 +#define FFC_DOUBLE_MAX_MANTISSA_FAST_PATH ((uint64_t)(2) << FFC_DOUBLE_MANTISSA_EXPLICIT_BITS) +#define FFC_DOUBLE_EXPONENT_MASK 0x7FF0000000000000 +#define FFC_DOUBLE_MANTISSA_MASK 0x000FFFFFFFFFFFFF +#define FFC_DOUBLE_HIDDEN_BIT_MASK 0x0010000000000000 +#define FFC_DOUBLE_MAX_DIGITS 769 + +#define FFC_FLOAT_SMALLEST_POWER_OF_10 -64 +#define FFC_FLOAT_LARGEST_POWER_OF_10 38 +#define FFC_FLOAT_SIGN_INDEX 31 +#define FFC_FLOAT_INFINITE_POWER 0xFF +#define FFC_FLOAT_MANTISSA_EXPLICIT_BITS 23 +#define FFC_FLOAT_MINIMUM_EXPONENT -127 +#define FFC_FLOAT_MIN_EXPONENT_ROUND_TO_EVEN -17 +#define FFC_FLOAT_MAX_EXPONENT_ROUND_TO_EVEN 10 +#define FFC_FLOAT_MAX_EXPONENT_FAST_PATH 10 +#define FFC_FLOAT_MAX_MANTISSA_FAST_PATH ((uint64_t)(2) << FFC_FLOAT_MANTISSA_EXPLICIT_BITS) +#define FFC_FLOAT_EXPONENT_MASK 0x7F800000 +#define FFC_FLOAT_MANTISSA_MASK 0x007FFFFF +#define FFC_FLOAT_HIDDEN_BIT_MASK 0x00800000 +#define FFC_FLOAT_MAX_DIGITS 114 + +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + #define FFC_DOUBLE_MIN_EXPONENT_FAST_PATH 0 + #define FFC_FLOAT_MIN_EXPONENT_FAST_PATH 0 +#else + #define FFC_DOUBLE_MIN_EXPONENT_FAST_PATH -22 + #define FFC_FLOAT_MIN_EXPONENT_FAST_PATH -10 +#endif + +#define ffc_const(value_kind, name) (value_kind == FFC_VALUE_KIND_DOUBLE ? FFC_DOUBLE_##name : FFC_FLOAT_##name) + +#define FFC_POWERS_OF_5_NUMBER_OF_ENTRIES (2 * (FFC_DOUBLE_LARGEST_POWER_OF_10 - FFC_DOUBLE_SMALLEST_POWER_OF_10 + 1)) + +// Powers of five from 5^-342 all the way to 5^308 rounded toward one. +ffc_internal uint64_t FFC_POWERS_OF_FIVE[FFC_POWERS_OF_5_NUMBER_OF_ENTRIES] = { + 0xeef453d6923bd65a, 0x113faa2906a13b3f, 0x9558b4661b6565f8, 0x4ac7ca59a424c507, 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, 0xe95a99df8ace6f53, 0xf4d82c2c107973dc, 0x91d8a02bb6c10594, 0x79071b9b8a4be869, 0xb64ec836a47146f9, 0x9748e2826cdee284, 0xe3e27a444d8d98b7, 0xfd1b1b2308169b25, 0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7, 0xb208ef855c969f4f, 0xbdbd2d335e51a935, 0xde8b2b66b3bc4723, 0xad2c788035e61382, 0x8b16fb203055ac76, 0x4c3bcb5021afcc31, 0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d, 0xd953e8624b85dd78, 0xd71d6dad34a2af0d, 0x87d4713d6f33aa6b, 0x8672648c40e5ad68, 0xa9c98d8ccb009506, 0x680efdaf511f18c2, 0xd43bf0effdc0ba48, 0x212bd1b2566def2, 0x84a57695fe98746d, 0x14bb630f7604b57, 0xa5ced43b7e3e9188, 0x419ea3bd35385e2d, 0xcf42894a5dce35ea, 0x52064cac828675b9, 0x818995ce7aa0e1b2, 0x7343efebd1940993, 0xa1ebfb4219491a1f, 0x1014ebe6c5f90bf8, 0xca66fa129f9b60a6, 0xd41a26e077774ef6, 0xfd00b897478238d0, 0x8920b098955522b4, 0x9e20735e8cb16382, 0x55b46e5f5d5535b0, 0xc5a890362fddbc62, 0xeb2189f734aa831d, 0xf712b443bbd52b7b, 0xa5e9ec7501d523e4, 0x9a6bb0aa55653b2d, 0x47b233c92125366e, 0xc1069cd4eabe89f8, 0x999ec0bb696e840a, 0xf148440a256e2c76, 0xc00670ea43ca250d, 0x96cd2a865764dbca, 0x380406926a5e5728, 0xbc807527ed3e12bc, 0xc605083704f5ecf2, 0xeba09271e88d976b, 0xf7864a44c633682e, 0x93445b8731587ea3, 0x7ab3ee6afbe0211d, 0xb8157268fdae9e4c, 0x5960ea05bad82964, 0xe61acf033d1a45df, 0x6fb92487298e33bd, 0x8fd0c16206306bab, 0xa5d3b6d479f8e056, 0xb3c4f1ba87bc8696, 0x8f48a4899877186c, 0xe0b62e2929aba83c, 0x331acdabfe94de87, 0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14, 0xaf8e5410288e1b6f, 0x7ecf0ae5ee44dd9, 0xdb71e91432b1a24a, 0xc9e82cd9f69d6150, 0x892731ac9faf056e, 0xbe311c083a225cd2, 0xab70fe17c79ac6ca, 0x6dbd630a48aaf406, 0xd64d3d9db981787d, 0x92cbbccdad5b108, 0x85f0468293f0eb4e, 0x25bbf56008c58ea5, 0xa76c582338ed2621, 0xaf2af2b80af6f24e, 0xd1476e2c07286faa, 0x1af5af660db4aee1, 0x82cca4db847945ca, 0x50d98d9fc890ed4d, 0xa37fce126597973c, 0xe50ff107bab528a0, 0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8, 0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a, 0x9faacf3df73609b1, 0x77b191618c54e9ac, 0xc795830d75038c1d, 0xd59df5b9ef6a2417, 0xf97ae3d0d2446f25, 0x4b0573286b44ad1d, 0x9becce62836ac577, 0x4ee367f9430aec32, 0xc2e801fb244576d5, 0x229c41f793cda73f, 0xf3a20279ed56d48a, 0x6b43527578c1110f, 0x9845418c345644d6, 0x830a13896b78aaa9, 0xbe5691ef416bd60c, 0x23cc986bc656d553, 0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8, 0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9, 0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53, 0xe858ad248f5c22c9, 0xd1b3400f8f9cff68, 0x91376c36d99995be, 0x23100809b9c21fa1, 0xb58547448ffffb2d, 0xabd40a0c2832a78a, 0xe2e69915b3fff9f9, 0x16c90c8f323f516c, 0x8dd01fad907ffc3b, 0xae3da7d97f6792e3, 0xb1442798f49ffb4a, 0x99cd11cfdf41779c, 0xdd95317f31c7fa1d, 0x40405643d711d583, 0x8a7d3eef7f1cfc52, 0x482835ea666b2572, 0xad1c8eab5ee43b66, 0xda3243650005eecf, 0xd863b256369d4a40, 0x90bed43e40076a82, 0x873e4f75e2224e68, 0x5a7744a6e804a291, 0xa90de3535aaae202, 0x711515d0a205cb36, 0xd3515c2831559a83, 0xd5a5b44ca873e03, 0x8412d9991ed58091, 0xe858790afe9486c2, 0xa5178fff668ae0b6, 0x626e974dbe39a872, 0xce5d73ff402d98e3, 0xfb0a3d212dc8128f, 0x80fa687f881c7f8e, 0x7ce66634bc9d0b99, 0xa139029f6a239f72, 0x1c1fffc1ebc44e80, 0xc987434744ac874e, 0xa327ffb266b56220, 0xfbe9141915d7a922, 0x4bf1ff9f0062baa8, 0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9, 0xc4ce17b399107c22, 0xcb550fb4384d21d3, 0xf6019da07f549b2b, 0x7e2a53a146606a48, 0x99c102844f94e0fb, 0x2eda7444cbfc426d, 0xc0314325637a1939, 0xfa911155fefb5308, 0xf03d93eebc589f88, 0x793555ab7eba27ca, 0x96267c7535b763b5, 0x4bc1558b2f3458de, 0xbbb01b9283253ca2, 0x9eb1aaedfb016f16, 0xea9c227723ee8bcb, 0x465e15a979c1cadc, 0x92a1958a7675175f, 0xbfacd89ec191ec9, 0xb749faed14125d36, 0xcef980ec671f667b, 0xe51c79a85916f484, 0x82b7e12780e7401a, 0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810, 0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15, 0xdfbdcece67006ac9, 0x67a791e093e1d49a, 0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0, 0xaecc49914078536d, 0x58fae9f773886e18, 0xda7f5bf590966848, 0xaf39a475506a899e, 0x888f99797a5e012d, 0x6d8406c952429603, 0xaab37fd7d8f58178, 0xc8e5087ba6d33b83, 0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64, 0x855c3be0a17fcd26, 0x5cf2eea09a55067f, 0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e, 0xd0601d8efc57b08b, 0xf13b94daf124da26, 0x823c12795db6ce57, 0x76c53d08d6b70858, 0xa2cb1717b52481ed, 0x54768c4b0c64ca6e, 0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09, 0xfe5d54150b090b02, 0xd3f93b35435d7c4c, 0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf, 0xc6b8e9b0709f109a, 0x359ab6419ca1091b, 0xf867241c8cc6d4c0, 0xc30163d203c94b62, 0x9b407691d7fc44f8, 0x79e0de63425dcf1d, 0xc21094364dfb5636, 0x985915fc12f542e4, 0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d, 0x979cf3ca6cec5b5a, 0xa705992ceecf9c42, 0xbd8430bd08277231, 0x50c6ff782a838353, 0xece53cec4a314ebd, 0xa4f8bf5635246428, 0x940f4613ae5ed136, 0x871b7795e136be99, 0xb913179899f68584, 0x28e2557b59846e3f, 0xe757dd7ec07426e5, 0x331aeada2fe589cf, 0x9096ea6f3848984f, 0x3ff0d2c85def7621, 0xb4bca50b065abe63, 0xfed077a756b53a9, 0xe1ebce4dc7f16dfb, 0xd3e8495912c62894, 0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c, 0xb080392cc4349dec, 0xbd8d794d96aacfb3, 0xdca04777f541c567, 0xecf0d7a0fc5583a0, 0x89e42caaf9491b60, 0xf41686c49db57244, 0xac5d37d5b79b6239, 0x311c2875c522ced5, 0xd77485cb25823ac7, 0x7d633293366b828b, 0x86a8d39ef77164bc, 0xae5dff9c02033197, 0xa8530886b54dbdeb, 0xd9f57f830283fdfc, 0xd267caa862a12d66, 0xd072df63c324fd7b, 0x8380dea93da4bc60, 0x4247cb9e59f71e6d, 0xa46116538d0deb78, 0x52d9be85f074e608, 0xcd795be870516656, 0x67902e276c921f8b, 0x806bd9714632dff6, 0xba1cd8a3db53b6, 0xa086cfcd97bf97f3, 0x80e8a40eccd228a4, 0xc8a883c0fdaf7df0, 0x6122cd128006b2cd, 0xfad2a4b13d1b5d6c, 0x796b805720085f81, 0x9cc3a6eec6311a63, 0xcbe3303674053bb0, 0xc3f490aa77bd60fc, 0xbedbfc4411068a9c, 0xf4f1b4d515acb93b, 0xee92fb5515482d44, 0x991711052d8bf3c5, 0x751bdd152d4d1c4a, 0xbf5cd54678eef0b6, 0xd262d45a78a0635d, 0xef340a98172aace4, 0x86fb897116c87c34, 0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0, 0xbae0a846d2195712, 0x8974836059cca109, 0xe998d258869facd7, 0x2bd1a438703fc94b, 0x91ff83775423cc06, 0x7b6306a34627ddcf, 0xb67f6455292cbf08, 0x1a3bc84c17b1d542, 0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93, 0x8e938662882af53e, 0x547eb47b7282ee9c, 0xb23867fb2a35b28d, 0xe99e619a4f23aa43, 0xdec681f9f4c31f31, 0x6405fa00e2ec94d4, 0x8b3c113c38f9f37e, 0xde83bc408dd3dd04, 0xae0b158b4738705e, 0x9624ab50b148d445, 0xd98ddaee19068c76, 0x3badd624dd9b0957, 0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6, 0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c, 0xd47487cc8470652b, 0x7647c3200069671f, 0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073, 0xa5fb0a17c777cf09, 0xf468107100525890, 0xcf79cc9db955c2cc, 0x7182148d4066eeb4, 0x81ac1fe293d599bf, 0xc6f14cd848405530, 0xa21727db38cb002f, 0xb8ada00e5a506a7c, 0xca9cf1d206fdc03b, 0xa6d90811f0e4851c, 0xfd442e4688bd304a, 0x908f4a166d1da663, 0x9e4a9cec15763e2e, 0x9a598e4e043287fe, 0xc5dd44271ad3cdba, 0x40eff1e1853f29fd, 0xf7549530e188c128, 0xd12bee59e68ef47c, 0x9a94dd3e8cf578b9, 0x82bb74f8301958ce, 0xc13a148e3032d6e7, 0xe36a52363c1faf01, 0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1, 0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9, 0xbcb2b812db11a5de, 0x7415d448f6b6f0e7, 0xebdf661791d60f56, 0x111b495b3464ad21, 0x936b9fcebb25c995, 0xcab10dd900beec34, 0xb84687c269ef3bfb, 0x3d5d514f40eea742, 0xe65829b3046b0afa, 0xcb4a5a3112a5112, 0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab, 0xb3f4e093db73a093, 0x59ed216765690f56, 0xe0f218b8d25088b8, 0x306869c13ec3532c, 0x8c974f7383725573, 0x1e414218c73a13fb, 0xafbd2350644eeacf, 0xe5d1929ef90898fa, 0xdbac6c247d62a583, 0xdf45f746b74abf39, 0x894bc396ce5da772, 0x6b8bba8c328eb783, 0xab9eb47c81f5114f, 0x66ea92f3f326564, 0xd686619ba27255a2, 0xc80a537b0efefebd, 0x8613fd0145877585, 0xbd06742ce95f5f36, 0xa798fc4196e952e7, 0x2c48113823b73704, 0xd17f3b51fca3a7a0, 0xf75a15862ca504c5, 0x82ef85133de648c4, 0x9a984d73dbe722fb, 0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba, 0xcc963fee10b7d1b3, 0x318df905079926a8, 0xffbbcfe994e5c61f, 0xfdf17746497f7052, 0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633, 0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0, 0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0, 0x9c1661a651213e2d, 0x6bea10ca65c084e, 0xc31bfa0fe5698db8, 0x486e494fcff30a62, 0xf3e2f893dec3f126, 0x5a89dba3c3efccfa, 0x986ddb5c6b3a76b7, 0xf89629465a75e01c, 0xbe89523386091465, 0xf6bbb397f1135823, 0xee2ba6c0678b597f, 0x746aa07ded582e2c, 0x94db483840b717ef, 0xa8c2a44eb4571cdc, 0xba121a4650e4ddeb, 0x92f34d62616ce413, 0xe896a0d7e51e1566, 0x77b020baf9c81d17, 0x915e2486ef32cd60, 0xace1474dc1d122e, 0xb5b5ada8aaff80b8, 0xd819992132456ba, 0xe3231912d5bf60e6, 0x10e1fff697ed6c69, 0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1, 0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2, 0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde, 0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b, 0xad4ab7112eb3929d, 0x86c16c98d2c953c6, 0xd89d64d57a607744, 0xe871c7bf077ba8b7, 0x87625f056c7c4a8b, 0x11471cd764ad4972, 0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf, 0xd389b47879823479, 0x4aff1d108d4ec2c3, 0x843610cb4bf160cb, 0xcedf722a585139ba, 0xa54394fe1eedb8fe, 0xc2974eb4ee658828, 0xce947a3da6a9273e, 0x733d226229feea32, 0x811ccc668829b887, 0x806357d5a3f525f, 0xa163ff802a3426a8, 0xca07c2dcb0cf26f7, 0xc9bcff6034c13052, 0xfc89b393dd02f0b5, 0xfc2c3f3841f17c67, 0xbbac2078d443ace2, 0x9d9ba7832936edc0, 0xd54b944b84aa4c0d, 0xc5029163f384a931, 0xa9e795e65d4df11, 0xf64335bcf065d37d, 0x4d4617b5ff4a16d5, 0x99ea0196163fa42e, 0x504bced1bf8e4e45, 0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6, 0xf07da27a82c37088, 0x5d767327bb4e5a4c, 0x964e858c91ba2655, 0x3a6a07f8d510f86f, 0xbbe226efb628afea, 0x890489f70a55368b, 0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e, 0x92c8ae6b464fc96f, 0x3b0b8bc90012929d, 0xb77ada0617e3bbcb, 0x9ce6ebb40173744, 0xe55990879ddcaabd, 0xcc420a6a101d0515, 0x8f57fa54c2a9eab6, 0x9fa946824a12232d, 0xb32df8e9f3546564, 0x47939822dc96abf9, 0xdff9772470297ebd, 0x59787e2b93bc56f7, 0x8bfbea76c619ef36, 0x57eb4edb3c55b65a, 0xaefae51477a06b03, 0xede622920b6b23f1, 0xdab99e59958885c4, 0xe95fab368e45eced, 0x88b402f7fd75539b, 0x11dbcb0218ebb414, 0xaae103b5fcd2a881, 0xd652bdc29f26a119, 0xd59944a37c0752a2, 0x4be76d3346f0495f, 0x857fcae62d8493a5, 0x6f70a4400c562ddb, 0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952, 0xd097ad07a71f26b2, 0x7e2000a41346a7a7, 0x825ecc24c873782f, 0x8ed400668c0c28c8, 0xa2f67f2dfa90563b, 0x728900802f0f32fa, 0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9, 0xfea126b7d78186bc, 0xe2f610c84987bfa8, 0x9f24b832e6b0f436, 0xdd9ca7d2df4d7c9, 0xc6ede63fa05d3143, 0x91503d1c79720dbb, 0xf8a95fcf88747d94, 0x75a44c6397ce912a, 0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba, 0xc24452da229b021b, 0xfbe85badce996168, 0xf2d56790ab41c2a2, 0xfae27299423fb9c3, 0x97c560ba6b0919a5, 0xdccd879fc967d41a, 0xbdb6b8e905cb600f, 0x5400e987bbc1c920, 0xed246723473e3813, 0x290123e9aab23b68, 0x9436c0760c86e30b, 0xf9a0b6720aaf6521, 0xb94470938fa89bce, 0xf808e40e8d5b3e69, 0xe7958cb87392c2c2, 0xb60b1d1230b20e04, 0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2, 0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3, 0xe2280b6c20dd5232, 0x25c6da63c38de1b0, 0x8d590723948a535f, 0x579c487e5a38ad0e, 0xb0af48ec79ace837, 0x2d835a9df0c6d851, 0xdcdb1b2798182244, 0xf8e431456cf88e65, 0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff, 0xac8b2d36eed2dac5, 0xe272467e3d222f3f, 0xd7adf884aa879177, 0x5b0ed81dcc6abb0f, 0x86ccbb52ea94baea, 0x98e947129fc2b4e9, 0xa87fea27a539e9a5, 0x3f2398d747b36224, 0xd29fe4b18e88640e, 0x8eec7f0d19a03aad, 0x83a3eeeef9153e89, 0x1953cf68300424ac, 0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7, 0xcdb02555653131b6, 0x3792f412cb06794d, 0x808e17555f3ebf11, 0xe2bbd88bbee40bd0, 0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4, 0xc8de047564d20a8b, 0xf245825a5a445275, 0xfb158592be068d2e, 0xeed6e2f0f0d56712, 0x9ced737bb6c4183d, 0x55464dd69685606b, 0xc428d05aa4751e4c, 0xaa97e14c3c26b886, 0xf53304714d9265df, 0xd53dd99f4b3066a8, 0x993fe2c6d07b7fab, 0xe546a8038efe4029, 0xbf8fdb78849a5f96, 0xde98520472bdd033, 0xef73d256a5c0f77c, 0x963e66858f6d4440, 0x95a8637627989aad, 0xdde7001379a44aa8, 0xbb127c53b17ec159, 0x5560c018580d5d52, 0xe9d71b689dde71af, 0xaab8f01e6e10b4a6, 0x9226712162ab070d, 0xcab3961304ca70e8, 0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22, 0xe45c10c42a2b3b05, 0x8cb89a7db77c506a, 0x8eb98a7a9a5b04e3, 0x77f3608e92adb242, 0xb267ed1940f1c61c, 0x55f038b237591ed3, 0xdf01e85f912e37a3, 0x6b6c46dec52f6688, 0x8b61313bbabce2c6, 0x2323ac4b3b3da015, 0xae397d8aa96c1b77, 0xabec975e0a0d081a, 0xd9c7dced53c72255, 0x96e7bd358c904a21, 0x881cea14545c7575, 0x7e50d64177da2e54, 0xaa242499697392d2, 0xdde50bd1d5d0b9e9, 0xd4ad2dbfc3d07787, 0x955e4ec64b44e864, 0x84ec3c97da624ab4, 0xbd5af13bef0b113e, 0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e, 0xcfb11ead453994ba, 0x67de18eda5814af2, 0x81ceb32c4b43fcf4, 0x80eacf948770ced7, 0xa2425ff75e14fc31, 0xa1258379a94d028d, 0xcad2f7f5359a3b3e, 0x96ee45813a04330, 0xfd87b5f28300ca0d, 0x8bca9d6e188853fc, 0x9e74d1b791e07e48, 0x775ea264cf55347e, 0xc612062576589dda, 0x95364afe032a819e, 0xf79687aed3eec551, 0x3a83ddbd83f52205, 0x9abe14cd44753b52, 0xc4926a9672793543, 0xc16d9a0095928a27, 0x75b7053c0f178294, 0xf1c90080baf72cb1, 0x5324c68b12dd6339, 0x971da05074da7bee, 0xd3f6fc16ebca5e04, 0xbce5086492111aea, 0x88f4bb1ca6bcf585, 0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6, 0x9392ee8e921d5d07, 0x3aff322e62439fd0, 0xb877aa3236a4b449, 0x9befeb9fad487c3, 0xe69594bec44de15b, 0x4c2ebe687989a9b4, 0x901d7cf73ab0acd9, 0xf9d37014bf60a11, 0xb424dc35095cd80f, 0x538484c19ef38c95, 0xe12e13424bb40e13, 0x2865a5f206b06fba, 0x8cbccc096f5088cb, 0xf93f87b7442e45d4, 0xafebff0bcb24aafe, 0xf78f69a51539d749, 0xdbe6fecebdedd5be, 0xb573440e5a884d1c, 0x89705f4136b4a597, 0x31680a88f8953031, 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e, 0xd6bf94d5e57a42bc, 0x3d32907604691b4d, 0x8637bd05af6c69b5, 0xa63f9a49c2c1b110, 0xa7c5ac471b478423, 0xfcf80dc33721d54, 0xd1b71758e219652b, 0xd3c36113404ea4a9, 0x83126e978d4fdf3b, 0x645a1cac083126ea, 0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4, 0xcccccccccccccccc, 0xcccccccccccccccd, 0x8000000000000000, 0x0, 0xa000000000000000, 0x0, 0xc800000000000000, 0x0, 0xfa00000000000000, 0x0, 0x9c40000000000000, 0x0, 0xc350000000000000, 0x0, 0xf424000000000000, 0x0, 0x9896800000000000, 0x0, 0xbebc200000000000, 0x0, 0xee6b280000000000, 0x0, 0x9502f90000000000, 0x0, 0xba43b74000000000, 0x0, 0xe8d4a51000000000, 0x0, 0x9184e72a00000000, 0x0, 0xb5e620f480000000, 0x0, 0xe35fa931a0000000, 0x0, 0x8e1bc9bf04000000, 0x0, 0xb1a2bc2ec5000000, 0x0, 0xde0b6b3a76400000, 0x0, 0x8ac7230489e80000, 0x0, 0xad78ebc5ac620000, 0x0, 0xd8d726b7177a8000, 0x0, 0x878678326eac9000, 0x0, 0xa968163f0a57b400, 0x0, 0xd3c21bcecceda100, 0x0, 0x84595161401484a0, 0x0, 0xa56fa5b99019a5c8, 0x0, 0xcecb8f27f4200f3a, 0x0, 0x813f3978f8940984, 0x4000000000000000, 0xa18f07d736b90be5, 0x5000000000000000, 0xc9f2c9cd04674ede, 0xa400000000000000, 0xfc6f7c4045812296, 0x4d00000000000000, 0x9dc5ada82b70b59d, 0xf020000000000000, 0xc5371912364ce305, 0x6c28000000000000, 0xf684df56c3e01bc6, 0xc732000000000000, 0x9a130b963a6c115c, 0x3c7f400000000000, 0xc097ce7bc90715b3, 0x4b9f100000000000, 0xf0bdc21abb48db20, 0x1e86d40000000000, 0x96769950b50d88f4, 0x1314448000000000, 0xbc143fa4e250eb31, 0x17d955a000000000, 0xeb194f8e1ae525fd, 0x5dcfab0800000000, 0x92efd1b8d0cf37be, 0x5aa1cae500000000, 0xb7abc627050305ad, 0xf14a3d9e40000000, 0xe596b7b0c643c719, 0x6d9ccd05d0000000, 0x8f7e32ce7bea5c6f, 0xe4820023a2000000, 0xb35dbf821ae4f38b, 0xdda2802c8a800000, 0xe0352f62a19e306e, 0xd50b2037ad200000, 0x8c213d9da502de45, 0x4526f422cc340000, 0xaf298d050e4395d6, 0x9670b12b7f410000, 0xdaf3f04651d47b4c, 0x3c0cdd765f114000, 0x88d8762bf324cd0f, 0xa5880a69fb6ac800, 0xab0e93b6efee0053, 0x8eea0d047a457a00, 0xd5d238a4abe98068, 0x72a4904598d6d880, 0x85a36366eb71f041, 0x47a6da2b7f864750, 0xa70c3c40a64e6c51, 0x999090b65f67d924, 0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d, 0x82818f1281ed449f, 0xbff8f10e7a8921a4, 0xa321f2d7226895c7, 0xaff72d52192b6a0d, 0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490, 0xfee50b7025c36a08, 0x2f236d04753d5b4, 0x9f4f2726179a2245, 0x1d762422c946590, 0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5, 0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2, 0x9b934c3b330c8577, 0x63cc55f49f88eb2f, 0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb, 0xf316271c7fc3908a, 0x8bef464e3945ef7a, 0x97edd871cfda3a56, 0x97758bf0e3cbb5ac, 0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317, 0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd, 0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a, 0xb975d6b6ee39e436, 0xb3e2fd538e122b44, 0xe7d34c64a9c85d44, 0x60dbbca87196b616, 0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd, 0xb51d13aea4a488dd, 0x6babab6398bdbe41, 0xe264589a4dcdab14, 0xc696963c7eed2dd1, 0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2, 0xb0de65388cc8ada8, 0x3b25a55f43294bcb, 0xdd15fe86affad912, 0x49ef0eb713f39ebe, 0x8a2dbf142dfcc7ab, 0x6e3569326c784337, 0xacb92ed9397bf996, 0x49c2c37f07965404, 0xd7e77a8f87daf7fb, 0xdc33745ec97be906, 0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3, 0xa8acd7c0222311bc, 0xc40832ea0d68ce0c, 0xd2d80db02aabd62b, 0xf50a3fa490c30190, 0x83c7088e1aab65db, 0x792667c6da79e0fa, 0xa4b8cab1a1563f52, 0x577001b891185938, 0xcde6fd5e09abcf26, 0xed4c0226b55e6f86, 0x80b05e5ac60b6178, 0x544f8158315b05b4, 0xa0dc75f1778e39d6, 0x696361ae3db1c721, 0xc913936dd571c84c, 0x3bc3a19cd1e38e9, 0xfb5878494ace3a5f, 0x4ab48a04065c723, 0x9d174b2dcec0e47b, 0x62eb0d64283f9c76, 0xc45d1df942711d9a, 0x3ba5d0bd324f8394, 0xf5746577930d6500, 0xca8f44ec7ee36479, 0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb, 0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e, 0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e, 0x95d04aee3b80ece5, 0xbba1f1d158724a12, 0xbb445da9ca61281f, 0x2a8a6e45ae8edc97, 0xea1575143cf97226, 0xf52d09d71a3293bd, 0x924d692ca61be758, 0x593c2626705f9c56, 0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c, 0xe498f455c38b997a, 0xb6dfb9c0f956447, 0x8edf98b59a373fec, 0x4724bd4189bd5eac, 0xb2977ee300c50fe7, 0x58edec91ec2cb657, 0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed, 0x8b865b215899f46c, 0xbd79e0d20082ee74, 0xae67f1e9aec07187, 0xecd8590680a3aa11, 0xda01ee641a708de9, 0xe80e6f4820cc9495, 0x884134fe908658b2, 0x3109058d147fdcdd, 0xaa51823e34a7eede, 0xbd4b46f0599fd415, 0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a, 0x850fadc09923329e, 0x3e2cf6bc604ddb0, 0xa6539930bf6bff45, 0x84db8346b786151c, 0xcfe87f7cef46ff16, 0xe612641865679a63, 0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e, 0xa26da3999aef7749, 0xe3be5e330f38f09d, 0xcb090c8001ab551c, 0x5cadf5bfd3072cc5, 0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6, 0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa, 0xc646d63501a1511d, 0xb281e1fd541501b8, 0xf7d88bc24209a565, 0x1f225a7ca91a4226, 0x9ae757596946075f, 0x3375788de9b06958, 0xc1a12d2fc3978937, 0x52d6b1641c83ae, 0xf209787bb47d6b84, 0xc0678c5dbd23a49a, 0x9745eb4d50ce6332, 0xf840b7ba963646e0, 0xbd176620a501fbff, 0xb650e5a93bc3d898, 0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe, 0x93ba47c980e98cdf, 0xc66f336c36b10137, 0xb8a8d9bbe123f017, 0xb80b0047445d4184, 0xe6d3102ad96cec1d, 0xa60dc059157491e5, 0x9043ea1ac7e41392, 0x87c89837ad68db2f, 0xb454e4a179dd1877, 0x29babe4598c311fb, 0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a, 0x8ce2529e2734bb1d, 0x1899e4a65f58660c, 0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f, 0xdc21a1171d42645d, 0x76707543f4fa1f73, 0x899504ae72497eba, 0x6a06494a791c53a8, 0xabfa45da0edbde69, 0x487db9d17636892, 0xd6f8d7509292d603, 0x45a9d2845d3c42b6, 0x865b86925b9bc5c2, 0xb8a2392ba45a9b2, 0xa7f26836f282b732, 0x8e6cac7768d7141e, 0xd1ef0244af2364ff, 0x3207d795430cd926, 0x8335616aed761f1f, 0x7f44e6bd49e807b8, 0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6, 0xcd036837130890a1, 0x36dba887c37a8c0f, 0x802221226be55a64, 0xc2494954da2c9789, 0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c, 0xc83553c5c8965d3d, 0x6f92829494e5acc7, 0xfa42a8b73abbf48c, 0xcb772339ba1f17f9, 0x9c69a97284b578d7, 0xff2a760414536efb, 0xc38413cf25e2d70d, 0xfef5138519684aba, 0xf46518c2ef5b8cd1, 0x7eb258665fc25d69, 0x98bf2f79d5993802, 0xef2f773ffbd97a61, 0xbeeefb584aff8603, 0xaafb550ffacfd8fa, 0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38, 0x952ab45cfa97a0b2, 0xdd945a747bf26183, 0xba756174393d88df, 0x94f971119aeef9e4, 0xe912b9d1478ceb17, 0x7a37cd5601aab85d, 0x91abb422ccb812ee, 0xac62e055c10ab33a, 0xb616a12b7fe617aa, 0x577b986b314d6009, 0xe39c49765fdf9d94, 0xed5a7e85fda0b80b, 0x8e41ade9fbebc27d, 0x14588f13be847307, 0xb1d219647ae6b31c, 0x596eb2d8ae258fc8, 0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb, 0x8aec23d680043bee, 0x25de7bb9480d5854, 0xada72ccc20054ae9, 0xaf561aa79a10ae6a, 0xd910f7ff28069da4, 0x1b2ba1518094da04, 0x87aa9aff79042286, 0x90fb44d2f05d0842, 0xa99541bf57452b28, 0x353a1607ac744a53, 0xd3fa922f2d1675f2, 0x42889b8997915ce8, 0x847c9b5d7c2e09b7, 0x69956135febada11, 0xa59bc234db398c25, 0x43fab9837e699095, 0xcf02b2c21207ef2e, 0x94f967e45e03f4bb, 0x8161afb94b44f57d, 0x1d1be0eebac278f5, 0xa1ba1ba79e1632dc, 0x6462d92a69731732, 0xca28a291859bbf93, 0x7d7b8f7503cfdcfe, 0xfcb2cb35e702af78, 0x5cda735244c3d43e, 0x9defbf01b061adab, 0x3a0888136afa64a7, 0xc56baec21c7a1916, 0x88aaa1845b8fdd0, 0xf6c69a72a3989f5b, 0x8aad549e57273d45, 0x9a3c2087a63f6399, 0x36ac54e2f678864b, 0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd, 0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5, 0x969eb7c47859e743, 0x9f644ae5a4b1b325, 0xbc4665b596706114, 0x873d5d9f0dde1fee, 0xeb57ff22fc0c7959, 0xa90cb506d155a7ea, 0x9316ff75dd87cbd8, 0x9a7f12442d588f2, 0xb7dcbf5354e9bece, 0xc11ed6d538aeb2f, 0xe5d3ef282a242e81, 0x8f1668c8a86da5fa, 0x8fa475791a569d10, 0xf96e017d694487bc, 0xb38d92d760ec4455, 0x37c981dcc395a9ac, 0xe070f78d3927556a, 0x85bbe253f47b1417, 0x8c469ab843b89562, 0x93956d7478ccec8e, 0xaf58416654a6babb, 0x387ac8d1970027b2, 0xdb2e51bfe9d0696a, 0x6997b05fcc0319e, 0x88fcf317f22241e2, 0x441fece3bdf81f03, 0xab3c2fddeeaad25a, 0xd527e81cad7626c3, 0xd60b3bd56a5586f1, 0x8a71e223d8d3b074, 0x85c7056562757456, 0xf6872d5667844e49, 0xa738c6bebb12d16c, 0xb428f8ac016561db, 0xd106f86e69d785c7, 0xe13336d701beba52, 0x82a45b450226b39c, 0xecc0024661173473, 0xa34d721642b06084, 0x27f002d7f95d0190, 0xcc20ce9bd35c78a5, 0x31ec038df7b441f4, 0xff290242c83396ce, 0x7e67047175a15271, 0x9f79a169bd203e41, 0xf0062c6e984d386, 0xc75809c42c684dd1, 0x52c07b78a3e60868, 0xf92e0c3537826145, 0xa7709a56ccdf8a82, 0x9bbcc7a142b17ccb, 0x88a66076400bb691, 0xc2abf989935ddbfe, 0x6acff893d00ea435, 0xf356f7ebf83552fe, 0x583f6b8c4124d43, 0x98165af37b2153de, 0xc3727a337a8b704a, 0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c, 0xeda2ee1c7064130c, 0x1162def06f79df73, 0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8, 0xb9a74a0637ce2ee1, 0x6d953e2bd7173692, 0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437, 0x910ab1d4db9914a0, 0x1d9c9892400a22a2, 0xb54d5e4a127f59c8, 0x2503beb6d00cab4b, 0xe2a0b5dc971f303a, 0x2e44ae64840fd61d, 0x8da471a9de737e24, 0x5ceaecfed289e5d2, 0xb10d8e1456105dad, 0x7425a83e872c5f47, 0xdd50f1996b947518, 0xd12f124e28f77719, 0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f, 0xace73cbfdc0bfb7b, 0x636cc64d1001550b, 0xd8210befd30efa5a, 0x3c47f7e05401aa4e, 0x8714a775e3e95c78, 0x65acfaec34810a71, 0xa8d9d1535ce3b396, 0x7f1839a741a14d0d, 0xd31045a8341ca07c, 0x1ede48111209a050, 0x83ea2b892091e44d, 0x934aed0aab460432, 0xa4e4b66b68b65d60, 0xf81da84d5617853f, 0xce1de40642e3f4b9, 0x36251260ab9d668e, 0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019, 0xa1075a24e4421730, 0xb24cf65b8612f81f, 0xc94930ae1d529cfc, 0xdee033f26797b627, 0xfb9b7cd9a4a7443c, 0x169840ef017da3b1, 0x9d412e0806e88aa5, 0x8e1f289560ee864e, 0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2, 0xf5b5d7ec8acb58a2, 0xae10af696774b1db, 0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29, 0xbff610b0cc6edd3f, 0x17fd090a58d32af3, 0xeff394dcff8a948e, 0xddfc4b4cef07f5b0, 0x95f83d0a1fb69cd9, 0x4abdaf101564f98e, 0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1, 0xea53df5fd18d5513, 0x84c86189216dc5ed, 0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4, 0xb7118682dbb66a77, 0x3fbc8c33221dc2a1, 0xe4d5e82392a40515, 0xfabaf3feaa5334a, 0x8f05b1163ba6832d, 0x29cb4d87f2a7400e, 0xb2c71d5bca9023f8, 0x743e20e9ef511012, 0xdf78e4b2bd342cf6, 0x914da9246b255416, 0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e, 0xae9672aba3d0c320, 0xa184ac2473b529b1, 0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e, 0x8865899617fb1871, 0x7e2fa67c7a658892, 0xaa7eebfb9df9de8d, 0xddbb901b98feeab7, 0xd51ea6fa85785631, 0x552a74227f3ea565, 0x8533285c936b35de, 0xd53a88958f87275f, 0xa67ff273b8460356, 0x8a892abaf368f137, 0xd01fef10a657842c, 0x2d2b7569b0432d85, 0x8213f56a67f6b29b, 0x9c3b29620e29fc73, 0xa298f2c501f45f42, 0x8349f3ba91b47b8f, 0xcb3f2f7642717713, 0x241c70a936219a73, 0xfe0efb53d30dd4d7, 0xed238cd383aa0110, 0x9ec95d1463e8a506, 0xf4363804324a40aa, 0xc67bb4597ce2ce48, 0xb143c6053edcd0d5, 0xf81aa16fdc1b81da, 0xdd94b7868e94050a, 0x9b10a4e5e9913128, 0xca7cf2b4191c8326, 0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0, 0xf24a01a73cf2dccf, 0xbc633b39673c8cec, 0x976e41088617ca01, 0xd5be0503e085d813, 0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18, 0xec9c459d51852ba2, 0xddf8e7d60ed1219e, 0x93e1ab8252f33b45, 0xcabb90e5c942b503, 0xb8da1662e7b00a17, 0x3d6a751f3b936243, 0xe7109bfba19c0c9d, 0xcc512670a783ad4, 0x906a617d450187e2, 0x27fb2b80668b24c5, 0xb484f9dc9641e9da, 0xb1f9f660802dedf6, 0xe1a63853bbd26451, 0x5e7873f8a0396973, 0x8d07e33455637eb2, 0xdb0b487b6423e1e8, 0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62, 0xdc5c5301c56b75f7, 0x7641a140cc7810fb, 0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d, 0xac2820d9623bf429, 0x546345fa9fbdcd44, 0xd732290fbacaf133, 0xa97c177947ad4095, 0x867f59a9d4bed6c0, 0x49ed8eabcccc485d, 0xa81f301449ee8c70, 0x5c68f256bfff5a74, 0xd226fc195c6a2f8c, 0x73832eec6fff3111, 0x83585d8fd9c25db7, 0xc831fd53c5ff7eab, 0xa42e74f3d032f525, 0xba3e7ca8b77f5e55, 0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb, 0x80444b5e7aa7cf85, 0x7980d163cf5b81b3, 0xa0555e361951c366, 0xd7e105bcc332621f, 0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7, 0xfa856334878fc150, 0xb14f98f6f0feb951, 0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3, 0xc3b8358109e84f07, 0xa862f80ec4700c8, 0xf4a642e14c6262c8, 0xcd27bb612758c0fa, 0x98e7e9cccfbd7dbd, 0x8038d51cb897789c, 0xbf21e44003acdd2c, 0xe0470a63e6bd56c3, 0xeeea5d5004981478, 0x1858ccfce06cac74, 0x95527a5202df0ccb, 0xf37801e0c43ebc8, 0xbaa718e68396cffd, 0xd30560258f54e6ba, 0xe950df20247c83fd, 0x47c6b82ef32a2069, 0x91d28b7416cdd27e, 0x4cdc331d57fa5441, 0xb6472e511c81471d, 0xe0133fe4adf8e952, 0xe3d8f9e563a198e5, 0x58180fddd97723a6, 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648 +}; + +// lookup table for ascii values +ffc_internal bool FFC_SPACE_LUT[256] = { + 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 +}; + +ffc_internal ffc_inline bool ffc_is_space(char c) { + return FFC_SPACE_LUT[(uint8_t)c]; +} + +ffc_internal uint8_t FFC_CHAR_TO_DIGIT_LUT[] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, + 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255}; + +ffc_internal ffc_inline +uint8_t ffc_char_to_digit(char c) { + return FFC_CHAR_TO_DIGIT_LUT[(uint8_t)c]; +} + +// Indexed by a 'base' but offset by 2, first entry is base 2, 64 digits, checks out right? +ffc_internal size_t FFC_MAXDIGITS_OF_BASE_U64[] = { + 64, 41, 32, 28, 25, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 16, 16, + 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13}; + +ffc_internal ffc_inline size_t ffc_max_digits_u64(int base) { + return FFC_MAXDIGITS_OF_BASE_U64[base - 2]; +} + +ffc_internal ffc_inline bool ffc_is_integer(char c) { + // can be micro-optimized, but compilers are entirely able to optimize it well + return (unsigned)(c - '0') <= 9u; +} + + +ffc_internal uint64_t FFC_MIN_SAFE_OF_BASE_U64[] = { + 9223372036854775808ull, 12157665459056928801ull, 4611686018427387904, + 7450580596923828125, 4738381338321616896, 3909821048582988049, + 9223372036854775808ull, 12157665459056928801ull, 10000000000000000000ull, + 5559917313492231481, 2218611106740436992, 8650415919381337933, + 2177953337809371136, 6568408355712890625, 1152921504606846976, + 2862423051509815793, 6746640616477458432, 15181127029874798299ull, + 1638400000000000000, 3243919932521508681, 6221821273427820544, + 11592836324538749809ull, 876488338465357824, 1490116119384765625, + 2481152873203736576, 4052555153018976267, 6502111422497947648, + 10260628712958602189ull, 15943230000000000000ull, 787662783788549761, + 1152921504606846976, 1667889514952984961, 2386420683693101056, + 3379220508056640625, 4738381338321616896}; + +ffc_internal ffc_inline uint64_t ffc_min_safe_u64_of_base(int base) { + return FFC_MIN_SAFE_OF_BASE_U64[base - 2]; +} + +ffc_internal ffc_inline +bool ffc_strncasecmp3(char const *actual_mixedcase, char const *expected_lowercase, size_t char_width) { + uint64_t mask = 0; + if (char_width == 1) { mask = 0x2020202020202020; } + else if (char_width == 2) { mask = 0x0020002000200020; } + else if (char_width == 4) { mask = 0x0000002000000020; } + else { return false; } + + uint64_t val1 = 0; + uint64_t val2 = 0; + if (char_width == 1 || char_width == 2) { + memcpy(&val1, actual_mixedcase, 3 * char_width); + memcpy(&val2, expected_lowercase, 3 * char_width); + val1 |= mask; + val2 |= mask; + return val1 == val2; + } else if (char_width == 4) { + memcpy(&val1, actual_mixedcase, 2 * char_width); + memcpy(&val2, expected_lowercase, 2 * char_width); + val1 |= mask; + if (val1 != val2) { + return false; + } + return (actual_mixedcase[2] | 32) == (expected_lowercase[2]); + } else { + return false; + } +} + +ffc_internal ffc_inline +bool ffc_strncasecmp5(char *actual_mixedcase, char const *expected_lowercase, size_t char_width) { + uint64_t mask = 0; + uint64_t val1 = 0; + uint64_t val2 = 0; + if (char_width == 1) { + mask = 0x2020202020202020; + memcpy(&val1, actual_mixedcase, 5 * char_width); + memcpy(&val2, expected_lowercase, 5 * char_width); + val1 |= mask; + val2 |= mask; + return val1 == val2; + } else if (char_width == 2) { + mask = 0x0020002000200020; + memcpy(&val1, actual_mixedcase, 4 * char_width); + memcpy(&val2, expected_lowercase, 4 * char_width); + val1 |= mask; + if (val1 != val2) { + return false; + } + return (actual_mixedcase[4] | 32) == (expected_lowercase[4]); + } else if (char_width == 4) { + mask = 0x0000002000000020; + memcpy(&val1, actual_mixedcase, 2 * char_width); + memcpy(&val2, expected_lowercase, 2 * char_width); + val1 |= mask; + if (val1 != val2) { + return false; + } + memcpy(&val1, actual_mixedcase + 2, 2 * char_width); + memcpy(&val2, expected_lowercase + 2, 2 * char_width); + val1 |= mask; + if (val1 != val2) { + return false; + } + return (actual_mixedcase[4] | 32) == (expected_lowercase[4]); + } + return false; +} + +/** + * Returns true if the floating-pointing rounding mode is to 'nearest'. + * It is the default on most system. This function is meant to be inexpensive. + * Credit : @mwalcott3 + */ + +ffc_internal ffc_inline +bool ffc_rounds_to_nearest(void) { +#if defined(FFC_ROUNDS_TO_NEAREST) + // We're being compiled under a mode where IEEE 754 round-to-nearest mode is + // guaranteed. + // We can simply return true; this is an optimization that eliminates the + // volatile float FCMP chain below. + return true; +#endif + // 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 + // return fegetround() == FE_TONEAREST; + // However, it is expected to be much faster than the fegetround() + // function call. + // + // The volatile keyword 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::min(), any + // small value so that 1 + x should round to 1 would do (after accounting for + // excess precision, as in 387 instructions). + static float volatile fmin = FLT_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 FFC_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 FFC_VISUAL_STUDIO +#pragma warning(pop) +#elif defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +} + +ffc_internal ffc_inline +// packs up an adjusted_mantissa into a double +void ffc_am_to_float(bool negative, ffc_adjusted_mantissa am, ffc_value* value, ffc_value_kind vk) { + if (vk == FFC_VALUE_KIND_FLOAT) { + uint32_t word = (uint32_t)am.mantissa; + word = word | (uint32_t)(am.power2) << ffc_const(vk, MANTISSA_EXPLICIT_BITS); + word = word | (uint32_t)(negative) << ffc_const(vk, SIGN_INDEX); + memcpy(value, &word, sizeof(uint32_t)); + } else { + uint64_t word = am.mantissa; + word = word | (uint64_t)(am.power2) << ffc_const(vk, MANTISSA_EXPLICIT_BITS); + word = word | (uint64_t)(negative) << ffc_const(vk, SIGN_INDEX); + memcpy(value, &word, sizeof(uint64_t)); + } +} + +static const double FFC_DOUBLE_POWERS_OF_TEN[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 +}; + +static const float FFC_FLOAT_POWERS_OF_TEN[] = { + 1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, + 1e6f, 1e7f, 1e8f, 1e9f, 1e10f +}; + +// Largest integer value v so that (5**index * v) <= 1<<53. +// 0x20000000000000 == 1 << 53 +#define FFC_55555 (5LL * 5LL * 5LL * 5LL * 5LL) +static const uint64_t FFC_DOUBLE_MAX_MANTISSA[] = { + (uint64_t)0x20000000000000L, + (uint64_t)0x20000000000000L / 5, + (uint64_t)0x20000000000000L / (5 * 5), + (uint64_t)0x20000000000000L / (5 * 5 * 5), + (uint64_t)0x20000000000000L / (5 * 5 * 5 * 5), + (uint64_t)0x20000000000000L / (FFC_55555), + (uint64_t)0x20000000000000L / (FFC_55555 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * 5 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * 5 * 5 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * 5 * 5 * 5 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * 5 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * 5 * 5 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * 5 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * 5 * 5 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * 5 * 5 * 5 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * FFC_55555), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * FFC_55555 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * FFC_55555 * 5 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * FFC_55555 * 5 * 5 * 5), + (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * FFC_55555 * 5 * 5 * 5 * 5)}; + +// Largest integer value v so that (5**index * v) <= 1<<24. +// 0x1000000 == 1<<24 +static const uint64_t FFC_FLOAT_MAX_MANTISSA[] = { + 0x1000000, + 0x1000000 / 5, + 0x1000000 / (5 * 5), + 0x1000000 / (5 * 5 * 5), + 0x1000000 / (5 * 5 * 5 * 5), + 0x1000000 / (FFC_55555), + 0x1000000 / (FFC_55555 * 5), + 0x1000000 / (FFC_55555 * 5 * 5), + 0x1000000 / (FFC_55555 * 5 * 5 * 5), + 0x1000000 / (FFC_55555 * 5 * 5 * 5 * 5), + 0x1000000 / (FFC_55555 * FFC_55555), + 0x1000000 / (FFC_55555 * FFC_55555 * 5)}; + +#ifndef FFC_ASSERT +#define FFC_ASSERT(x) \ + { ((void)(x)); } +#endif + +#ifndef FFC_DEBUG_ASSERT +#define FFC_DEBUG_ASSERT(x) \ + { ((void)(x)); } +#endif + +#endif // FFC_COMMON_H + +#ifndef FFC_PARSE_H +#define FFC_PARSE_H + +#include + +/* section: read digits */ + +ffc_internal ffc_inline +uint64_t ffc_byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 | + (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 | + (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 | + (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56; +} + +ffc_internal ffc_inline +uint32_t ffc_byteswap_32(uint32_t val) { + return (val >> 24) | ((val >> 8) & 0x0000FF00u) | ((val << 8) & 0x00FF0000u) | + (val << 24); +} + +ffc_inline ffc_internal +uint64_t ffc_read8_to_u64(char const *chars) { + uint64_t val; + memcpy(&val, chars, sizeof(uint64_t)); +#if FFC_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = ffc_byteswap(val); +#endif + return val; +} + +// Read 4 UC into a u32. Truncates UC if not char. +ffc_internal ffc_inline uint32_t +ffc_read4_to_u32(char const *chars) { + uint32_t val; + memcpy(&val, chars, sizeof(uint32_t)); +#if FFC_IS_BIG_ENDIAN == 1 + val = ffc_byteswap_32(val); +#endif + return val; +} + +#ifdef FFC_SSE2 + +ffc_internal ffc_inline +uint64_t ffc_simd_read8_to_u64_simdreg(__m128i const data) { + FFC_SIMD_DISABLE_WARNINGS + __m128i const packed = _mm_packus_epi16(data, data); +#ifdef FFC_64BIT + return (uint64_t)_mm_cvtsi128_si64(packed); +#else + uint64_t value; + // Visual Studio + older versions of GCC don't support _mm_storeu_si64 + _mm_storel_epi64((__m128i*)&value, packed); + return value; +#endif + FFC_SIMD_RESTORE_WARNINGS +} + +ffc_internal ffc_inline +uint64_t ffc_simd_read8_to_u64(uint16_t const *chars) { + FFC_SIMD_DISABLE_WARNINGS + return ffc_simd_read8_to_u64_simdreg( + _mm_loadu_si128((const __m128i*)chars)); + FFC_SIMD_RESTORE_WARNINGS +} + +#elif defined(FFC_NEON) + +ffc_internal ffc_inline +uint64_t ffc_simd_read8_to_u64_simdreg(uint16x8_t const data) { + FFC_SIMD_DISABLE_WARNINGS + uint8x8_t utf8_packed = vmovn_u16(data); + return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0); + FFC_SIMD_RESTORE_WARNINGS +} + +ffc_internal ffc_inline +uint64_t ffc_simd_read8_to_u64(uint16_t const *chars) { + FFC_SIMD_DISABLE_WARNINGS + return ffc_simd_read8_to_u64_simdreg(vld1q_u16(chars)); + FFC_SIMD_RESTORE_WARNINGS +} + +#endif // FFC_SSE2 + +// credit @aqrit +ffc_internal ffc_inline uint32_t +ffc_parse_eight_digits_unrolled_swar(uint64_t val) { + uint64_t const mask = 0x000000FF000000FF; + uint64_t const mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + uint64_t const mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return (uint32_t)val; +} + +// Call this if chars are definitely 8 digits. +ffc_internal ffc_inline +uint32_t ffc_parse_eight_digits_unrolled(char const *chars) { + return ffc_parse_eight_digits_unrolled_swar(ffc_read8_to_u64(chars)); // truncation okay +} + +// credit @aqrit +ffc_internal ffc_inline bool +ffc_is_made_of_eight_digits_fast(uint64_t val) { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +ffc_internal ffc_inline bool +ffc_is_made_of_four_digits_fast(uint32_t val) { + return !((((val + 0x46464646) | (val - 0x30303030)) & 0x80808080)); +} + +ffc_internal ffc_inline +uint32_t ffc_parse_four_digits_unrolled(uint32_t val) { + val -= 0x30303030; + val = (val * 10) + (val >> 8); + return (((val & 0x00FF00FF) * 0x00640001) >> 16) & 0xFFFF; +} + +#ifdef FFC_HAS_SIMD + +// Call this if chars might not be 8 digits. +// Using this style (instead of is_made_of_eight_digits_fast() then +// parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice. +ffc_internal ffc_inline +bool ffc_simd_parse_if_eight_digits_unrolled_simd(uint16_t const *chars, uint64_t* i) { +#ifdef FFC_SSE2 + FFC_SIMD_DISABLE_WARNINGS + __m128i const data = + _mm_loadu_si128((__m128i const *)chars); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + __m128i const t0 = _mm_add_epi16(data, _mm_set1_epi16(32720)); + __m128i const t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759)); + + if (_mm_movemask_epi8(t1) == 0) { + *i = *i * 100000000 + ffc_parse_eight_digits_unrolled_swar(ffc_simd_read8_to_u64_simdreg(data)); + return true; + } else + return false; + FFC_SIMD_RESTORE_WARNINGS +#elif defined(FFC_NEON) + FFC_SIMD_DISABLE_WARNINGS + uint16x8_t const data = vld1q_u16(chars); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + uint16x8_t const t0 = vsubq_u16(data, vmovq_n_u16('0')); + uint16x8_t const mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1)); + + if (vminvq_u16(mask) == 0xFFFF) { + *i = *i * 100000000 + ffc_parse_eight_digits_unrolled_swar(ffc_simd_read8_to_u64_simdreg(data)); + return true; + } else + return false; + FFC_SIMD_RESTORE_WARNINGS +#else + (void)chars; + (void)i; + return false; +#endif // FFC_SSE2 +} + +#endif // FFC_HAS_SIMD + +// Compute acc*10 + d_expr. +// +// On AArch64, Clang emits `smaddl` (3-cycle latency) for `acc*10 + d`, which +// sits on the digit-accumulation critical path. Forcing the `add + lsl` form +// via inline asm shortens it and is worth ~+9% on Clang/AArch64. +// +// The asm is intentionally narrow: it is only correct/beneficial on AArch64, +// and *only* for Clang. GCC already strength-reduces `acc*10` to shift-adds +// and schedules them optimally; routing GCC through this asm measurably +// regressed it (canada -5.3%). Every other compiler therefore uses the plain +// expression below, which is also what makes this safe to leave unconditional +// at the call sites. +#if defined(__aarch64__) && defined(__clang__) +ffc_internal ffc_inline uint64_t +ffc_digit_acc10(uint64_t acc, uint64_t d) { + uint64_t result; + __asm__("add %0, %2, %2, lsl #2\n\t" + "add %0, %1, %0, lsl #1" + : "=&r"(result) : "r"(d), "r"(acc)); + return result; +} +#define FFC_DIGIT_ACC10(acc, d_expr) ffc_digit_acc10((acc), (uint64_t)(d_expr)) +#else +#define FFC_DIGIT_ACC10(acc, d_expr) ((acc) * 10 + (uint64_t)(d_expr)) +#endif + +ffc_internal ffc_inline void +ffc_loop_parse_if_eight_digits(char const **p, char const *const pend, + uint64_t* i) { +#if defined(__aarch64__) && defined(__clang__) + // Clang/AArch64: manual 2x unroll converts the hot path from a while loop + // to a single linear block for typical float fractions (<=16 digits). + // Eliminating the back-edge allows Clang to keep SWAR constants in registers + // rather than rematerializing them on each iteration; also, the 8-digit + // block becomes an if (no back-edge) so constants are always in a straight- + // line context. GCC auto-unrolls this naturally so we keep its while loop. + while (pend - *p >= 16) { + uint64_t val1 = ffc_read8_to_u64(*p); + if (!ffc_is_made_of_eight_digits_fast(val1)) { break; } + uint64_t val2 = ffc_read8_to_u64(*p + 8); + if (!ffc_is_made_of_eight_digits_fast(val2)) { + *i = (*i * 100000000) + ffc_parse_eight_digits_unrolled_swar(val1); // in rare cases overflows, ok + *p += 8; + break; + } + uint64_t s1 = ffc_parse_eight_digits_unrolled_swar(val1); + uint64_t s2 = ffc_parse_eight_digits_unrolled_swar(val2); + *i = (*i * 100000000ULL + s1) * 100000000ULL + s2; // in rare cases overflows, ok + *p += 16; + } + if (pend - *p >= 8) { + uint64_t val = ffc_read8_to_u64(*p); + if (ffc_is_made_of_eight_digits_fast(val)) { + *i = (*i * 100000000) + ffc_parse_eight_digits_unrolled_swar(val); // in rare cases overflows, ok + *p += 8; + } + } +#else + // GCC and other compilers: original while loop that GCC auto-unrolls well. + while (pend - *p >= 8) { + uint64_t val = ffc_read8_to_u64(*p); + if (!ffc_is_made_of_eight_digits_fast(val)) { break; } + *i = (*i * 100000000) + ffc_parse_eight_digits_unrolled_swar(val); // in rare cases, this will overflow, but that's ok + *p += 8; + } +#endif + // 4-digit follow-up: handles sub-8 remainders (e.g. 7-digit fractions) + // without falling all the way to byte-by-byte for the first 4 digits. + if (pend - *p >= 4) { + uint32_t val4 = ffc_read4_to_u32(*p); + if (ffc_is_made_of_four_digits_fast(val4)) { + *i = (*i * 10000) + ffc_parse_four_digits_unrolled(val4); + *p += 4; + } + } +} + +/* section end: read digits */ + +/* section: parse */ + +ffc_parse_options ffc_parse_options_default(void) { + ffc_parse_options options; + options.format = FFC_PRESET_GENERAL; + options.decimal_point = '.'; + return options; +} + +typedef struct ffc_parsed { + int64_t exponent; + uint64_t mantissa; + /* Populated on error; indicates where parsing failed */ + char const *lastmatch; + bool negative; + bool valid; + bool too_many_digits; + char* int_part_start; + size_t int_part_len; + char* fraction_part_start; + size_t fraction_part_len; + + ffc_parse_outcome outcome; +} ffc_parsed; + +ffc_internal ffc_inline +ffc_parsed ffc_report_parse_error(char const *p, ffc_parse_outcome outcome) { + ffc_parsed answer; + answer.valid = false; + answer.lastmatch = p; + answer.outcome = outcome; + return answer; +} + +ffc_internal ffc_inline +ffc_parsed ffc_parse_number_string( + char const *p, + // CONTRACT: p < pend + char const *pend, + ffc_parse_options const options, + // explicitly passed to encourage optimizer to specialize + bool const basic_json_fmt +) { + ffc_format fmt = options.format; + char decimal_point = options.decimal_point; + + FFC_DEBUG_ASSERT(fmt != 0); + + ffc_parsed answer = {0}; + answer.negative = (*p == '-'); + // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + // so we only allow it if we've been told to, and are not in json mode + bool allow_leading_plus = fmt & FFC_FORMAT_FLAG_ALLOW_LEADING_PLUS; + if ((*p == '-') || (uint64_t)(allow_leading_plus && !basic_json_fmt && *p == '+')) { + ++p; + if (p == pend) { + return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_MISSING_INTEGER_OR_DOT_AFTER_SIGN); + } + if (basic_json_fmt) { + if (!ffc_is_integer(*p)) { // a sign must be followed by an integer + return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_JSON_MISSING_INTEGER_AFTER_SIGN); + } + } else { + // a sign must be followed by an integer or the dot + if (!ffc_is_integer(*p) && (*p != decimal_point)) { + return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_MISSING_INTEGER_OR_DOT_AFTER_SIGN); + } + } + } + + // phew, we've found the digits + char const *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + // Integer scan: the first 5 digits are read straight-line, longer runs + // continue in the while loop. The common 1-5 digit case stays branch-light. + if ((p != pend) && ffc_is_integer(*p)) { + i = (uint64_t)(*p++ - '0'); + if ((p != pend) && ffc_is_integer(*p)) { + i = FFC_DIGIT_ACC10(i, *p++ - '0'); + if ((p != pend) && ffc_is_integer(*p)) { + i = FFC_DIGIT_ACC10(i, *p++ - '0'); + if ((p != pend) && ffc_is_integer(*p)) { + i = FFC_DIGIT_ACC10(i, *p++ - '0'); + if ((p != pend) && ffc_is_integer(*p)) { + i = FFC_DIGIT_ACC10(i, *p++ - '0'); + while ((p != pend) && ffc_is_integer(*p)) { + i = FFC_DIGIT_ACC10(i, *p - '0'); // might overflow, handled later + ++p; + } + } + } + } + } + } + + char const *const end_of_integer_part = p; + + int64_t digit_count = (int64_t)(end_of_integer_part - start_digits); + answer.int_part_start = (char*)start_digits; + answer.int_part_len = (size_t)(digit_count); + + if (basic_json_fmt) { + // at least 1 digit in integer part + if (digit_count == 0) { + return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_JSON_NO_DIGITS_IN_INTEGER_PART); + } + // no leading zeros + if ((start_digits[0] == '0' && digit_count > 1)) { + return ffc_report_parse_error(start_digits, FFC_PARSE_OUTCOME_JSON_LEADING_ZEROS_IN_INTEGER_PART); + } + } + + int64_t exponent = 0; + bool const has_decimal_point = (p != pend) && (*p == decimal_point); + + // Fraction-part bounds, kept as locals so the too_many_digits re-scan below + // can reuse them without reloading the answer.fraction_part_* fields. + char const *before = NULL; + char const *frac_end_local = NULL; + + /* post-decimal exponential part (calculates a negative exponent) */ + if (has_decimal_point) { + ++p; + before = p; + // can occur at most twice without overflowing, but let it occur more, since + // for integers with many digits, digit parsing is the primary bottleneck. + ffc_loop_parse_if_eight_digits(&p, pend, &i); + + // manual unroll for the 1-3 digit case + if (p != pend && ffc_is_integer(*p)) { + i = FFC_DIGIT_ACC10(i, (uint8_t)(*p++ - (char)('0'))); // in rare cases overflows, ok + if (p != pend && ffc_is_integer(*p)) { + i = FFC_DIGIT_ACC10(i, (uint8_t)(*p++ - (char)('0'))); + if (p != pend && ffc_is_integer(*p)) { + i = FFC_DIGIT_ACC10(i, (uint8_t)(*p++ - (char)('0'))); + } + } + } + + // pre: i = 123, digit_count = 3 + // 123.456 + // ^ pend + // ^ p + // ^ before + // exponent = -3 + // i = 123456 + // digit_count = 3 - (-3) = 6 + exponent = before - p; + frac_end_local = p; // capture before p advances into explicit exponent + answer.fraction_part_start = (char*)before; + answer.fraction_part_len = (size_t)(p - before); + digit_count -= exponent; + } + if (basic_json_fmt) { + // at least 1 digit in fractional part + if (has_decimal_point && exponent == 0) { + return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_JSON_NO_DIGITS_IN_FRACTIONAL_PART); + } + } else if (digit_count == 0) { // we must have encountered at least one integer! + return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_NO_DIGITS_IN_MANTISSA); + } + + /* explicit exponential part */ + int64_t exp_number = 0; + if (((uint64_t)(fmt & FFC_FORMAT_FLAG_SCIENTIFIC) && (p != pend) && + (('e' == *p) || ('E' == *p))) || + ((uint64_t)(fmt & FFC_FORMAT_FLAG_BASIC_FORTRAN) && (p != pend) && + (('+' == *p) || ('-' == *p) || ('d' == *p) || + ('D' == *p)))) { + char const *location_of_e = p; + if (('e' == *p) || ('E' == *p) || ('d' == *p) || + ('D' == *p)) { + ++p; + } + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !ffc_is_integer(*p)) { + if (basic_json_fmt || !(uint64_t)(fmt & FFC_FORMAT_FLAG_FIXED)) { + // The exponential part is invalid for scientific notation, so it must + // be a trailing token for fixed notation. However, fixed notation is + // disabled, so report a scientific notation error. JSON mode is strict + // for the scientific form (exp = e [ minus / plus ] 1*DIGIT in RFC + // 8259) so we also report the error, even though FIXED is part of + // FFC_PRESET_JSON. + return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_MISSING_EXPONENTIAL_PART); + } + // Otherwise (fixed-tolerant, non-JSON), we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && ffc_is_integer(*p)) { + uint8_t digit = (uint8_t)(*p - '0'); + if (exp_number < 0x10000000) { + exp_number = FFC_DIGIT_ACC10(exp_number, digit); + } + ++p; + } + if (neg_exp) { + exp_number = -exp_number; + } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if ((uint64_t)(fmt & FFC_FORMAT_FLAG_SCIENTIFIC) && + !(uint64_t)(fmt & FFC_FORMAT_FLAG_FIXED)) { + return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_MISSING_EXPONENTIAL_PART); + } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + ffc_debug("high digit_count %lld\n", digit_count); + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + char const *start = start_digits; + while ((start != pend) && (*start == '0' || *start == decimal_point)) { + if (*start == '0') { + digit_count--; + } + start++; + } + + if (digit_count > 19) { + answer.too_many_digits = true; + // Re-scan the digits into i, this time stopping before overflow. Reads + // from the local digit-range pointers (start_digits, end_of_integer_part, + // before, frac_end_local) rather than the answer struct fields. + i = 0; + p = (char*)start_digits; + char const *int_end = (char*)end_of_integer_part; + uint64_t const minimal_nineteen_digit_integer = 1000000000000000000; + while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { + i = i * 10 + (uint64_t)(*p - '0'); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integer + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p = (char*)before; + char const *frac_end = (char*)frac_end_local; + while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + (uint64_t)(*p - '0'); + ++p; + } + exponent = before - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + +/** + * Special case +inf, -inf, nan, infinity, -infinity. + * The case comparisons could be made much faster given that we know that the + * strings a null-free and fixed. + **/ +ffc_internal ffc_inline +ffc_result ffc_parse_infnan( + char *first, char *last, + ffc_value *value, + ffc_value_kind vk, + ffc_format fmt +) { + ffc_debug("parse_infnan\n"); + ffc_result answer; + answer.ptr = first; + answer.outcome = FFC_OUTCOME_OK; // be optimistic + // assume first < last, so dereference without checks; + bool const minus_sign = (*first == '-'); + // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + if ((*first == '-') || + ((uint64_t)(fmt & FFC_FORMAT_FLAG_ALLOW_LEADING_PLUS) && + (*first == '+'))) { + ++first; + } + if (last - first >= 3) { + if (ffc_strncasecmp3(first, "nan", 1)) { + answer.ptr = (first += 3); + + // The macro casts the literal AFTER applying the negative sign. This is ok: + // -NAN is a double negative NaN, and (float)(-NAN) correctly produces a negative float NaN. + // Same for infinity — (float)(-INFINITY) gives negative float infinity + ffc_set_value(value, vk, minus_sign ? -NAN : NAN); + // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, + // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). + if (first != last && *first == '(') { + for (char *ptr = first + 1; ptr != last; ++ptr) { + if (*ptr == ')') { + answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) + break; + } else if (!(('a' <= *ptr && *ptr <= 'z') || + ('A' <= *ptr && *ptr <= 'Z') || + ('0' <= *ptr && *ptr <= '9') || *ptr == '_')) + break; // forbidden char, not nan(n-char-seq-opt) + } + } + return answer; + } + if (ffc_strncasecmp3(first, "infinity", 1)) { + if ((last - first >= 8) && + ffc_strncasecmp5(first + 3, (char*)&"infinity"[3], 1)) { + answer.ptr = first + 8; + } else { + answer.ptr = first + 3; + } + ffc_set_value(value, vk, minus_sign ? -INFINITY : INFINITY); + return answer; + } + } + answer.outcome = FFC_OUTCOME_INVALID_INPUT; + return answer; +} + +ffc_internal ffc_inline +ffc_result ffc_parse_int_string( + char const *p, + char const *pend, + ffc_int_value *value, + ffc_int_kind ik, + ffc_parse_options options, + int const base + ) { + ffc_debug("input '%.*s'... ", (int)(pend - p), p); + ffc_format const fmt = options.format; + + if ((uint64_t)(fmt & FFC_FORMAT_FLAG_SKIP_WHITE_SPACE)) { + while ((p != pend) && ffc_is_space(*p)) { + p++; + } + } + + if (p == pend || base < 2 || base > 36) { + ffc_result invalid_input_result; + invalid_input_result.ptr = (char*)p; + invalid_input_result.outcome = FFC_OUTCOME_INVALID_INPUT; + return invalid_input_result; + } + + ffc_result answer; + char const *const first = p; + + bool const negative = (*p == (char)('-')); + + if (!ffc_int_kind_is_signed(ik) && negative) { + answer.outcome = FFC_OUTCOME_INVALID_INPUT; + answer.ptr = (char*)first; + return answer; + } + if ((*p == (char)('-')) || + ((uint64_t)(fmt & FFC_FORMAT_FLAG_ALLOW_LEADING_PLUS) && (*p == (char)('+')))) { + ++p; + } + + char const *const start_num = p; + + while (p != pend && *p == (char)('0')) { + ++p; + } + + bool const has_leading_zeros = p > start_num; + + char const *const start_digits = p; + + uint64_t i = 0; + if (base == 10) { + ffc_loop_parse_if_eight_digits(&p, pend, &i); // use SIMD if possible + } + while (p != pend) { + uint8_t digit = ffc_char_to_digit(*p); + if (digit >= base) { + break; + } + i = (uint64_t)(base) * i + digit; // might overflow, check this later + p++; + } + + size_t digit_count = (size_t)(p - start_digits); + + if (digit_count == 0) { + if (has_leading_zeros) { + value->u64 = 0; // Must zero the largest variant! + answer.outcome = FFC_OUTCOME_OK; + answer.ptr = p; + } else { + answer.outcome = FFC_OUTCOME_INVALID_INPUT; + answer.ptr = first; + } + return answer; + } + + answer.ptr = p; + + // check u64 overflow + size_t max_digits = ffc_max_digits_u64(base); + ffc_debug("digit_count %d, max_digits: %d\n", digit_count, max_digits); + if (digit_count > max_digits) { + answer.outcome = FFC_OUTCOME_OUT_OF_RANGE; + return answer; + } + // this check can be eliminated for all other types, but they will all require + // a max_digits(base) equivalent + if (digit_count == max_digits && i < ffc_min_safe_u64_of_base(base)) { + answer.outcome = FFC_OUTCOME_OUT_OF_RANGE; + return answer; + } + + ffc_debug("i is %lld, ik is %s\n", i, (ik == FFC_INT_KIND_U64 ? "u64" : + ik == FFC_INT_KIND_S64 ? "s64" : + ik == FFC_INT_KIND_U32 ? "u32" : + ik == FFC_INT_KIND_S32 ? "s32" : "?")); + // check other types overflow + if (ik != FFC_INT_KIND_U64) { + // Allow 1 greater magnitude when negative + if (i > ffc_int_value_max(ik) + (uint64_t)(negative)) { + answer.outcome = FFC_OUTCOME_OUT_OF_RANGE; + return answer; + } + } + + // All signed conversion goes through unsigned arithmetic to avoid UB + if (negative) { + uint64_t neg_i = ~i + 1; // This is the two's complement negation + // write into the signed slot via union + switch (ik) { + case FFC_INT_KIND_S64: value->s64 = (int64_t)neg_i; break; // implementation-defined, but works everywhere + case FFC_INT_KIND_S32: value->s32 = (int32_t)(int64_t)neg_i; break; + // unsigned kinds can't be negative — guarded by the range check above + // case FFC_INT_KIND_S16: value.i16 = (int16_t)(int64_t)neg_i; break; + // case FFC_INT_KIND_S8: value.i8 = (int8_t)(int64_t)neg_i; break; + } + } else { + switch (ik) { + case FFC_INT_KIND_S64: value->s64 = (int64_t)i; break; + case FFC_INT_KIND_S32: value->s32 = (int32_t)(int64_t)i; break; + case FFC_INT_KIND_U64: value->u64 = i; break; + case FFC_INT_KIND_U32: value->u32 = (uint32_t)i; break; + } + } + + answer.outcome = FFC_OUTCOME_OK; + return answer; +} + +#ifdef FFC_DEBUG + +#include +ffc_internal ffc_inline +void ffc_dump_parsed(ffc_parsed const p) { + (void)p; + ffc_debug("mantissa: %llu\n", (unsigned long long)p.mantissa); + ffc_debug("exponent: %lld\n", (long long)p.exponent); + ffc_debug("negative: %d\n", p.negative); + ffc_debug("valid: %d\n", p.valid); + ffc_debug("too_many_digits: %d\n", p.too_many_digits); + ffc_debug("int_part_len: %zu\n", p.int_part_len); + ffc_debug("fraction_part_len: %zu\n", p.fraction_part_len); +} + +#endif + +/* section end: parse */ + +#endif // FFC_PARSE_H +#ifndef FFC_DIGIT_COMPARISON_H +#define FFC_DIGIT_COMPARISON_H + +#ifndef FFC_BIGINT_H +#define FFC_BIGINT_H + + +// rust style `try!()` macro, or `?` operator +#define FFC_TRY(x) \ + { \ + if (!(x)) \ + return false; \ + } + +// the limb width: we want efficient multiplication of double the bits in +// limb, or for 64-bit limbs, at least 64-bit multiplication where we can +// extract the high and low parts efficiently. this is every 64-bit +// architecture except for sparc, which emulates 128-bit multiplication. +// we might have platforms where `CHAR_BIT` is not 8, so let's avoid +// doing `8 * sizeof(limb)`. +#if defined(FFC_64BIT) && !defined(__sparc) +#define FFC_64BIT_LIMB 1 +typedef uint64_t ffc_bigint_limb; +#define FFC_LIMB_BITS 64 +#else +#define FFC_32BIT_LIMB +typedef uint32_t ffc_bigint_limb; +#define FFC_LIMB_BITS 32 +#endif + +typedef struct { ffc_bigint_limb* ptr; size_t len; } ffc_bigint_limb_span; + +ffc_internal ffc_inline +ffc_bigint_limb ffc_limb_span_index(ffc_bigint_limb_span limb_span, size_t index) { + FFC_DEBUG_ASSERT(index < limb_span.len); + return limb_span.ptr[index]; +} +#define ffc_span_index(span, index) ffc_limb_span_index(span, index) + +// number of bits in a bigint. this needs to be at least the number +// of bits required to store the largest bigint, which is +// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or +// ~3600 bits, so we round to 4000. +#define FFC_BIGINT_BITS 4000 + +// vector-like type that is allocated on the stack. the entire +// buffer is pre-allocated, and only the length changes. + +// SV_LIMB_COUNT should be 125 or 32-bit systems or 62 for 64-bit systems +#define SV_LIMB_COUNT FFC_BIGINT_BITS / FFC_LIMB_BITS + +typedef struct ffc_sv { + ffc_bigint_limb data[SV_LIMB_COUNT]; + // we never need more than 150 limbs + uint16_t len; +} ffc_sv; + +// add items to the vector, from a span, without bounds checking +ffc_internal ffc_inline +void ffc_sv_extend_unchecked(ffc_sv* sv, ffc_bigint_limb_span s) { + ffc_bigint_limb *ptr = sv->data + sv->len; + + size_t s_bytes = s.len * sizeof(ffc_bigint_limb); + memcpy(ptr, s.ptr, s_bytes); + sv->len += s.len; +} + +// try to add items to the vector, returning if items were added +ffc_internal ffc_inline +bool ffc_sv_try_extend(ffc_sv* sv, ffc_bigint_limb_span s) { + if (sv->len + s.len <= SV_LIMB_COUNT) { + ffc_sv_extend_unchecked(sv, s); + return true; + } else { + return false; + } +} + +// create from existing limb span. +ffc_internal ffc_inline +ffc_sv ffc_sv_create(ffc_bigint_limb_span s) { + ffc_sv new_one; + new_one.len = 0; + ffc_sv_try_extend(&new_one, s); + return new_one; +} + +ffc_internal ffc_inline +ffc_bigint_limb ffc_sv_index(ffc_sv sv, size_t index) { + FFC_DEBUG_ASSERT(index < sv.len); + return sv.data[index]; +} + +// index from the end of the container +ffc_internal ffc_inline +ffc_bigint_limb ffc_sv_rindex(ffc_sv sv, size_t index) { + FFC_DEBUG_ASSERT(index < sv.len); + size_t rindex = sv.len - index - 1; + return sv.data[rindex]; +} + +// append item to vector, without bounds checking +ffc_internal ffc_inline +void ffc_sv_push_unchecked(ffc_sv* sv, ffc_bigint_limb value) { + sv->data[sv->len] = value; + sv->len++; +} + +// append item to vector, returning if item was added +ffc_internal ffc_inline +bool ffc_sv_try_push(ffc_sv* sv, ffc_bigint_limb value) { + if (sv->len < SV_LIMB_COUNT) { + ffc_sv_push_unchecked(sv, value); + return true; + } else { + return false; + } +} + +// try to reserve new_len limbs, filling with limbs +// FF_DIVERGE: We remove some extra helpers and simply fill with zeros +ffc_internal ffc_inline +bool ffc_sv_try_reserve(ffc_sv* sv, size_t new_len) { + if (new_len > SV_LIMB_COUNT) { + return false; + } else { + if (new_len > sv->len) { + size_t fill_count = new_len - sv->len; + ffc_bigint_limb *first = sv->data + sv->len; + ffc_bigint_limb *last = first + fill_count; + for (ffc_bigint_limb* p = first; p < last; p++) { + *p = 0; + } + sv->len = (uint16_t)new_len; + } else { + sv->len = (uint16_t)new_len; + } + return true; + } +} + +// check if any limbs are non-zero after the given index. +ffc_internal ffc_inline +bool ffc_sv_exists_nonzero_after(ffc_sv sv, size_t index) { + while (index < sv.len) { + if (ffc_sv_rindex(sv, index) != 0) { + return true; + } + index++; + } + return false; +} + +// normalize the big integer, so most-significant zero limbs are removed. +ffc_internal ffc_inline +void ffc_sv_normalize(ffc_sv* sv) { + while (sv->len > 0 && ffc_sv_rindex(*sv, 0) == 0) { + sv->len--; + } +} + +ffc_internal ffc_inline +uint64_t ffc_uint64_hi64_1(uint64_t r0, bool* truncated) { + FFC_DEBUG_ASSERT(r0 != 0); + *truncated = false; + int shl = (int)ffc_count_leading_zeroes(r0); + return r0 << shl; +} + +ffc_internal ffc_inline +uint64_t ffc_uint64_hi64_2(uint64_t r0, uint64_t r1, bool* truncated) { + FFC_DEBUG_ASSERT(r0 != 0); + int shl = (int)ffc_count_leading_zeroes(r0); + if (shl == 0) { + *truncated = r1 != 0; + return r0; + } else { + int shr = 64 - shl; + *truncated = (r1 << shl) != 0; + return (r0 << shl) | (r1 >> shr); + } +} + +ffc_internal ffc_inline +uint64_t ffc_uint32_hi64_1(uint32_t r0, bool* truncated) { + return ffc_uint64_hi64_1(r0, truncated); +} + +ffc_internal ffc_inline +uint64_t ffc_uint32_hi64_2(uint32_t r0, uint32_t r1, bool* truncated) { + uint64_t x0 = r0; + uint64_t x1 = r1; + return ffc_uint64_hi64_1((x0 << 32) | x1, truncated); +} + +ffc_internal ffc_inline +uint64_t ffc_uint32_hi64_3(uint32_t r0, uint32_t r1, uint32_t r2, bool* truncated) { + uint64_t x0 = r0; + uint64_t x1 = r1; + uint64_t x2 = r2; + return ffc_uint64_hi64_2(x0, (x1 << 32) | x2, truncated); +} + +// add two small integers, checking for overflow. +// we want an efficient operation. for msvc, where +// we don't have built-in intrinsics, this is still +// pretty fast. +ffc_internal ffc_inline +ffc_bigint_limb ffc_bigint_scalar_add(ffc_bigint_limb x, ffc_bigint_limb y, bool* overflow) { + ffc_bigint_limb z; +// gcc and clang +#if defined(__has_builtin) +#if __has_builtin(__builtin_add_overflow) + *overflow = __builtin_add_overflow(x, y, &z); + return z; +#endif +#endif + + // generic, this still optimizes correctly on MSVC. + z = x + y; + *overflow = z < x; + return z; +} + +// multiply two small integers, getting both the high and low bits. +ffc_inline ffc_internal +ffc_bigint_limb ffc_bigint_scalar_mul(ffc_bigint_limb x, ffc_bigint_limb y, ffc_bigint_limb* carry) { +#ifdef FFC_64BIT_LIMB +#if defined(__SIZEOF_INT128__) + // GCC and clang both define it as an extension. + __uint128_t z = (__uint128_t)(x) * (__uint128_t)(y) + (__uint128_t)(*carry); + *carry = (ffc_bigint_limb)(z >> FFC_LIMB_BITS); + return (ffc_bigint_limb)(z); +#else + // fallback, no native 128-bit integer multiplication with carry. + // on msvc, this optimizes identically, somehow. + ffc_u128 z = ffc_full_multiplication(x, y); + bool overflow; + z.low = ffc_bigint_scalar_add(z.low, *carry, &overflow); + z.high += (uint64_t)(overflow); // cannot overflow + *carry = z.high; + return z.low; +#endif +#else + uint64_t z = (uint64_t)(x) * (uint64_t)(y) + (uint64_t)(*carry); + *carry = (ffc_bigint_limb)(z >> FFC_LIMB_BITS); + return (ffc_bigint_limb)(z); +#endif +} + +// add scalar value to bigint starting from offset. +// used in grade school multiplication +ffc_internal ffc_inline +bool ffc_bigint_small_add_from(ffc_sv* sv, ffc_bigint_limb y, size_t start) { + size_t index = start; + ffc_bigint_limb carry = y; + bool overflow; + while (carry != 0 && index < sv->len) { + sv->data[index] = ffc_bigint_scalar_add(sv->data[index], carry, &overflow); + carry = (ffc_bigint_limb)(overflow); + index += 1; + } + if (carry != 0) { + FFC_TRY(ffc_sv_try_push(sv, carry)); + } + return true; +} + +// add scalar value to bigint. +ffc_internal ffc_inline +bool ffc_bigint_small_add(ffc_sv* sv, ffc_bigint_limb y) { + return ffc_bigint_small_add_from(sv, y, 0); +} + +// multiply bigint by scalar value. +ffc_internal +bool ffc_bigint_small_mul(ffc_sv* sv, ffc_bigint_limb y) { + ffc_bigint_limb carry = 0; + for (size_t index = 0; index < sv->len; index++) { + sv->data[index] = ffc_bigint_scalar_mul(sv->data[index], y, &carry); + } + if (carry != 0) { + FFC_TRY(ffc_sv_try_push(sv, carry)); + } + return true; +} + +// add bigint to bigint starting from index. +// used in grade school multiplication +ffc_internal +bool ffc_bigint_large_add_from(ffc_sv* x, ffc_bigint_limb_span y, size_t start) { + // the effective x buffer is from `xstart..x.len()`, so exit early + // if we can't get that current range. + + // FFC_DIVERGE: We are calling our try_reserve instead of the o.g. try_resize + if (x->len < start || y.len > x->len - start) { + FFC_TRY(ffc_sv_try_reserve(x, y.len + start)); + } + + bool carry = false; + for (size_t index = 0; index < y.len; index++) { + ffc_bigint_limb xi = x->data[index + start]; + ffc_bigint_limb yi = ffc_span_index(y, index); + bool c1 = false; + bool c2 = false; + xi = ffc_bigint_scalar_add(xi, yi, &c1); + if (carry) { + xi = ffc_bigint_scalar_add(xi, 1, &c2); + } + x->data[index + start] = xi; + carry = c1 | c2; + } + + // handle overflow + if (carry) { + FFC_TRY(ffc_bigint_small_add_from(x, 1, y.len + start)); + } + return true; +} + +// add bigint to bigint. +ffc_internal ffc_inline +bool ffc_sv_large_add_from_zero(ffc_sv* x, ffc_bigint_limb_span y) { + return ffc_bigint_large_add_from(x, y, 0); +} + +// grade-school multiplication algorithm +ffc_internal +bool ffc_bigint_long_mul(ffc_sv* x, ffc_bigint_limb_span y) { + ffc_bigint_limb_span xs; + xs.ptr = x->data; + xs.len = x->len; + + // full copy of x into z + ffc_sv z = ffc_sv_create(xs); + + ffc_bigint_limb_span zs; + zs.ptr = z.data; + zs.len = z.len; + + if (y.len != 0) { + ffc_bigint_limb y0 = ffc_span_index(y, 0); + FFC_TRY(ffc_bigint_small_mul(x, y0)); + for (size_t index = 1; index < y.len; index++) { + + ffc_bigint_limb yi = ffc_span_index(y, index); + ffc_sv zi; // re-use the same buffer throughout + + if (yi != 0) { + zi.len = 0; + FFC_TRY(ffc_sv_try_extend(&zi, zs)); + FFC_TRY(ffc_bigint_small_mul(&zi, yi)); + ffc_bigint_limb_span zis; + zis.ptr = zi.data; + zis.len = zi.len; + FFC_TRY(ffc_bigint_large_add_from(x, zis, index)); + } + } + } + + ffc_sv_normalize(x); + return true; +} + +// grade-school multiplication algorithm +ffc_internal ffc_inline +bool ffc_bigint_large_mul(ffc_sv* x, ffc_bigint_limb_span y) { + if (y.len == 1) { + FFC_TRY(ffc_bigint_small_mul(x, ffc_span_index(y,0))); + } else { + FFC_TRY(ffc_bigint_long_mul(x, y)); + } + return true; +} + +static const uint32_t pow5_tables_large_step = 135; +static const uint64_t pow5_tables_small_powers[] = { + 1ULL, + 5ULL, + 25ULL, + 125ULL, + 625ULL, + 3125ULL, + 15625ULL, + 78125ULL, + 390625ULL, + 1953125ULL, + 9765625ULL, + 48828125ULL, + 244140625ULL, + 1220703125ULL, + 6103515625ULL, + 30517578125ULL, + 152587890625ULL, + 762939453125ULL, + 3814697265625ULL, + 19073486328125ULL, + 95367431640625ULL, + 476837158203125ULL, + 2384185791015625ULL, + 11920928955078125ULL, + 59604644775390625ULL, + 298023223876953125ULL, + 1490116119384765625ULL, + 7450580596923828125ULL, +}; +#ifdef FFC_64BIT_LIMB + static const ffc_bigint_limb ffc_large_power_of_5[] = { + 1414648277510068013ULL, 9180637584431281687ULL, 4539964771860779200ULL, + 10482974169319127550ULL, 198276706040285095ULL}; +#else + static const ffc_bigint_limb ffc_large_power_of_5[] = { + 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, + 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; +#endif + +// big integer type. implements a small subset of big integer +// arithmetic, using simple algorithms since asymptotically +// faster algorithms are slower for a small number of limbs. +// all operations assume the big-integer is normalized. +typedef struct ffc_bigint { + // storage of the limbs, in little-endian order. + ffc_sv vec; +} ffc_bigint; + +ffc_inline ffc_internal +ffc_bigint ffc_bigint_empty(void) { + ffc_sv sv; + sv.len = 0; + + ffc_bigint sv_bigint; + sv_bigint.vec = sv; + return sv_bigint; +} + +ffc_inline ffc_internal +ffc_bigint ffc_bigint_make(uint64_t value) { + ffc_sv sv; + sv.len = 0; +#ifdef FFC_64BIT_LIMB + ffc_sv_push_unchecked(&sv, value); +#else + ffc_sv_push_unchecked(&sv, (uint32_t)(value)); + ffc_sv_push_unchecked(&sv, (uint32_t)(value >> 32)); +#endif + ffc_sv_normalize(&sv); + + ffc_bigint sv_bigint; + sv_bigint.vec = sv; + return sv_bigint; +} + +// get the high 64 bits from the vector, and if bits were truncated. +// this is to get the significant digits for the float. +ffc_inline ffc_internal +uint64_t ffc_bigint_hi64(ffc_bigint me, bool* truncated) { + ffc_sv vec = me.vec; +#ifdef FFC_64BIT_LIMB + if (vec.len == 0) { + *truncated = false; + return 0; + } else if (vec.len == 1) { + return ffc_uint64_hi64_1(ffc_sv_rindex(vec,0), truncated); + } else { + uint64_t result = ffc_uint64_hi64_2(ffc_sv_rindex(vec, 0), ffc_sv_rindex(vec, 1), truncated); + *truncated |= ffc_sv_exists_nonzero_after(vec, 2); + return result; + } +#else + if (vec.len == 0) { + *truncated = false; + return 0; + } else if (vec.len == 1) { + return ffc_uint32_hi64_1(ffc_sv_rindex(vec,0), truncated); + } else if (vec.len == 2) { + return ffc_uint32_hi64_2(ffc_sv_rindex(vec,0), ffc_sv_rindex(vec,1), truncated); + } else { + uint64_t result = ffc_uint32_hi64_3( + ffc_sv_rindex(vec,0), + ffc_sv_rindex(vec,1), + ffc_sv_rindex(vec,2), + truncated + ); + *truncated |= ffc_sv_exists_nonzero_after(vec , 3); + return result; + } +#endif +} + +// compare two big integers, returning the large value. +// assumes both are normalized. if the return value is +// negative, other is larger, if the return value is +// positive, this is larger, otherwise they are equal. +// the limbs are stored in little-endian order, so we +// must compare the limbs in ever order. +ffc_internal ffc_inline +int ffc_bigint_compare(ffc_bigint me, ffc_bigint const *other) { + if (me.vec.len > other->vec.len) { + return 1; + } else if (me.vec.len < other->vec.len) { + return -1; + } else { + for (size_t index = me.vec.len; index > 0; index--) { + ffc_bigint_limb xi = ffc_sv_index(me.vec, index - 1); + ffc_bigint_limb yi = ffc_sv_index(other->vec, index - 1); + if (xi > yi) { + return 1; + } else if (xi < yi) { + return -1; + } + } + return 0; + } +} + +// shift left each limb n bits, carrying over to the new limb +// returns true if we were able to shift all the digits. +ffc_internal ffc_inline +bool ffc_bigint_shl_bits(ffc_bigint* me, size_t n) { + // Internally, for each item, we shift left by n, and add the previous + // right shifted limb-bits. + // For example, we transform (for u8) shifted left 2, to: + // b10100100 b01000010 + // b10 b10010001 b00001000 + FFC_DEBUG_ASSERT(n != 0); + FFC_DEBUG_ASSERT(n < sizeof(ffc_bigint_limb) * 8); + + size_t shl = n; + size_t shr = FFC_LIMB_BITS - shl; + ffc_bigint_limb prev = 0; + for (size_t index = 0; index < me->vec.len; index++) { + ffc_bigint_limb xi = ffc_sv_index(me->vec, index); + me->vec.data[index] = (xi << shl) | (prev >> shr); + prev = xi; + } + + ffc_bigint_limb carry = prev >> shr; + if (carry != 0) { + return ffc_sv_try_push(&me->vec, carry); + } + return true; +} + +// move the limbs left by `n` limbs. +ffc_internal ffc_inline +bool ffc_bigint_shl_limbs(ffc_bigint* me, size_t n) { + FFC_DEBUG_ASSERT(n != 0); + if (n + me->vec.len > SV_LIMB_COUNT) { + return false; + } else if (me->vec.len != 0) { + // move limbs + ffc_bigint_limb *dst = me->vec.data + n; + ffc_bigint_limb const *src = me->vec.data; + // std::copy_backward(src, src + vec.len(), dst + vec.len()); + // memmove to handle the overlap + memmove(dst, src, me->vec.len * sizeof(ffc_bigint_limb)); + + // fill in empty limbs + ffc_bigint_limb *first = me->vec.data; + // ffc_bigint_limb *last = first + n; + // ::std::fill(first, last, 0); + memset(first, 0, n * sizeof(ffc_bigint_limb)); + me->vec.len += n; + return true; + } else { + return true; + } +} + +// move the limbs left by `n` bits. +ffc_internal ffc_inline +bool ffc_bigint_shl(ffc_bigint* me, size_t n) { + size_t rem = n % FFC_LIMB_BITS; + size_t div = n / FFC_LIMB_BITS; + if (rem != 0) { + FFC_TRY(ffc_bigint_shl_bits(me, rem)); + } + if (div != 0) { + FFC_TRY(ffc_bigint_shl_limbs(me, div)); + } + return true; +} + +// get the number of leading zeros in the bigint. +ffc_internal ffc_inline +int ffc_bigint_ctlz(ffc_bigint me) { + if (me.vec.len == 0) { + return 0; + } else { +#ifdef FFC_64BIT_LIMB + return (int)ffc_count_leading_zeroes(ffc_sv_rindex(me.vec, 0)); +#else + // no use defining a specialized count_leading_zeros for a 32-bit type. + uint64_t r0 = ffc_sv_rindex(me.vec, 0); + return ffc_count_leading_zeroes(r0 << 32); +#endif + } +} + +// get the number of bits in the bigint. +ffc_internal ffc_inline +int ffc_bigint_bit_length(ffc_bigint me) { + int lz = ffc_bigint_ctlz(me); + return (int)(FFC_LIMB_BITS * me.vec.len) - lz; +} + +ffc_internal ffc_inline +bool ffc_bigint_mul(ffc_bigint* me, ffc_bigint_limb y) { return ffc_bigint_small_mul(&me->vec, y); } + +ffc_internal ffc_inline +bool ffc_bigint_add(ffc_bigint* me, ffc_bigint_limb y) { return ffc_bigint_small_add(&me->vec, y); } + +// multiply as if by 2 raised to a power. +ffc_internal ffc_inline +bool ffc_bigint_pow2(ffc_bigint* me, uint32_t exp) { return ffc_bigint_shl(me, exp); } + +// multiply as if by 5 raised to a power. +ffc_internal ffc_inline +bool ffc_bigint_pow5(ffc_bigint* me, uint32_t exp) { + // multiply by a power of 5 + size_t large_length = sizeof(ffc_large_power_of_5) / sizeof(ffc_bigint_limb); + + ffc_bigint_limb_span large; + large.ptr = (ffc_bigint_limb*)ffc_large_power_of_5; + large.len = large_length; + + while (exp >= pow5_tables_large_step) { + FFC_TRY(ffc_bigint_large_mul(&me->vec, large)); + exp -= pow5_tables_large_step; + } +#ifdef FFC_64BIT_LIMB + uint32_t small_step = 27; + ffc_bigint_limb max_native = 7450580596923828125UL; +#else + uint32_t small_step = 13; + ffc_bigint_limb max_native = 1220703125U; +#endif + while (exp >= small_step) { + FFC_TRY(ffc_bigint_small_mul(&me->vec, max_native)); + exp -= small_step; + } + if (exp != 0) { + FFC_TRY( + ffc_bigint_small_mul(&me->vec, (ffc_bigint_limb)(pow5_tables_small_powers[exp])) + ); + } + + return true; +} + +// multiply as if by 10 raised to a power. +ffc_internal ffc_inline +bool ffc_bigint_pow10(ffc_bigint* me, uint32_t exp) { + FFC_TRY(ffc_bigint_pow5(me, exp)); + return ffc_bigint_pow2(me, exp); +} + +#undef ffc_span_index +#undef sv_rindex + +#endif // FFC_BIGINT_H + +// 1e0 to 1e19 +static const uint64_t ffc_powers_of_ten_uint64[] = {1UL, + 10UL, + 100UL, + 1000UL, + 10000UL, + 100000UL, + 1000000UL, + 10000000UL, + 100000000UL, + 1000000000UL, + 10000000000UL, + 100000000000UL, + 1000000000000UL, + 10000000000000UL, + 100000000000000UL, + 1000000000000000UL, + 10000000000000000UL, + 100000000000000000UL, + 1000000000000000000UL, + 10000000000000000000UL}; + +// calculate the exponent, in scientific notation, of the number. +// 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 +// to slow down performance for faster algorithms, and this is still fast. +ffc_inline ffc_internal int32_t +ffc_scientific_exponent(uint64_t mantissa, int32_t exponent) { + while (mantissa >= 10000) { + mantissa /= 10000; + exponent += 4; + } + while (mantissa >= 100) { + mantissa /= 100; + exponent += 2; + } + while (mantissa >= 10) { + mantissa /= 10; + exponent += 1; + } + return exponent; +} + +// this converts a native floating-point number to an extended-precision float. +ffc_internal ffc_inline ffc_adjusted_mantissa +ffc_to_extended(ffc_value value, ffc_value_kind vk) { + uint64_t const exponent_mask = ffc_const(vk, EXPONENT_MASK); + uint64_t const mantissa_mask = ffc_const(vk, MANTISSA_MASK); + uint64_t const hidden_bit_mask = ffc_const(vk, HIDDEN_BIT_MASK); + + // Finish converting this one + ffc_adjusted_mantissa am; + int32_t bias = ffc_const(vk, MANTISSA_EXPLICIT_BITS) - ffc_const(vk, MINIMUM_EXPONENT); + //ffc_int_value bits = ffc_get_value_bits(value, vk); + + switch (vk) { + case FFC_VALUE_KIND_DOUBLE: { + uint64_t bits = ffc_get_double_bits(value.d); + if ((bits & exponent_mask) == 0) { + // denormal + am.power2 = 1 - bias; + am.mantissa = bits & mantissa_mask; + } else { + // normal + am.power2 = (int32_t)((bits & exponent_mask) >> ffc_const(vk, MANTISSA_EXPLICIT_BITS)); + am.power2 -= bias; + am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; + } + break; + } + case FFC_VALUE_KIND_FLOAT: { + uint32_t bits = ffc_get_float_bits(value.f); + if ((bits & exponent_mask) == 0) { + // denormal + am.power2 = 1 - bias; + am.mantissa = bits & mantissa_mask; + } else { + // normal + am.power2 = (int32_t)((bits & exponent_mask) >> ffc_const(vk, MANTISSA_EXPLICIT_BITS)); + am.power2 -= bias; + am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; + } + break; + } + } + + return am; +} + +// get the extended precision value of the halfway point between b and b+u. +// we are given a native float that represents b, so we need to adjust it +// halfway between b and b+u. +ffc_internal ffc_inline +ffc_adjusted_mantissa ffc_to_extended_halfway(ffc_value value, ffc_value_kind vk) { + ffc_adjusted_mantissa am = ffc_to_extended(value, vk); + am.mantissa <<= 1; + am.mantissa += 1; + am.power2 -= 1; + return am; +} + +ffc_internal ffc_inline +void ffc_round_down_impl(ffc_adjusted_mantissa* am, int32_t shift) { + if (shift == 64) { + am->mantissa = 0; + } else { + am->mantissa >>= shift; + } + am->power2 += shift; +} + +// round nearest tie even, resolving ties using the truncated flag +ffc_internal ffc_inline +void ffc_round_nearest_tie_even_truncated(ffc_adjusted_mantissa *am, + int32_t shift, bool truncated) { + uint64_t const mask = (shift == 64) ? UINT64_MAX : ((uint64_t)(1) << shift) - 1; + uint64_t const halfway = (shift == 0) ? 0 : (uint64_t)(1) << (shift - 1); + uint64_t truncated_bits = am->mantissa & mask; + bool is_above = truncated_bits > halfway; + bool is_halfway = truncated_bits == halfway; + + // shift digits into position + if (shift == 64) { + am->mantissa = 0; + } else { + am->mantissa >>= shift; + } + am->power2 += shift; + + bool is_odd = (am->mantissa & 1) == 1; + am->mantissa += (uint64_t)(is_above || (is_halfway && truncated) || + (is_odd && is_halfway)); +} + +// round nearest tie even, resolving ties using a precomputed comparison result +ffc_internal ffc_inline +void ffc_round_nearest_tie_even_cmp(ffc_adjusted_mantissa *am, + int32_t shift, int ord) { + // shift digits into position + if (shift == 64) { + am->mantissa = 0; + } else { + am->mantissa >>= shift; + } + am->power2 += shift; + + bool is_odd = (am->mantissa & 1) == 1; + am->mantissa += (uint64_t)(ord > 0 || (ord == 0 && is_odd)); +} + +// Finalize rounding for a double after the shift callback has been applied. +// This is the common tail of ffc_round_double factored out to avoid +// duplicating the carry/infinite checks at every call site. +ffc_internal ffc_inline +void ffc_round_finish(ffc_adjusted_mantissa *am, ffc_value_kind vk) { + // check for carry + if (am->mantissa >= ((uint64_t)(2) << ffc_const(vk, MANTISSA_EXPLICIT_BITS))) { + am->mantissa = ((uint64_t)(1) << ffc_const(vk, MANTISSA_EXPLICIT_BITS)); + am->power2++; + } + + // check for infinite: we could have carried to an infinite power + am->mantissa &= ~((uint64_t)(1) << ffc_const(vk, MANTISSA_EXPLICIT_BITS)); + if (am->power2 >= ffc_const(vk, INFINITE_POWER)) { + am->power2 = ffc_const(vk, INFINITE_POWER); + am->mantissa = 0; + } +} + +// Common structure for round_double variants +#define ffc_round_core(sub_routine_call, vk) \ + do { \ + int32_t mantissa_shift = 64 - ffc_const(vk, MANTISSA_EXPLICIT_BITS) - 1; \ + if (-am->power2 >= mantissa_shift) { \ + int32_t _shift = -am->power2 + 1; \ + if (_shift > 64) _shift = 64; \ + sub_routine_call; \ + am->power2 = (am->mantissa < ((uint64_t)(1) << ffc_const(vk, MANTISSA_EXPLICIT_BITS))) \ + ? 0 \ + : 1; \ + return; \ + } \ + { int32_t _shift = mantissa_shift; sub_routine_call; } \ + ffc_round_finish(am, vk); \ + } while (0) + +// round down variant of round_double +ffc_internal ffc_inline +void ffc_round_down(ffc_adjusted_mantissa *am, ffc_value_kind vk) { + ffc_round_core(ffc_round_down_impl(am, _shift), vk); +} + +// tie-even (truncated) variant of round_double +ffc_internal ffc_inline +void ffc_round_tie_even_truncated(ffc_adjusted_mantissa *am, + bool truncated, ffc_value_kind vk) { + ffc_round_core(ffc_round_nearest_tie_even_truncated(am, _shift, truncated), vk); +} + +// tie-even (comparison) variant of round_double +ffc_internal ffc_inline +void ffc_round_double_tie_even_cmp(ffc_adjusted_mantissa *am, int ord, ffc_value_kind vk) { + ffc_round_core(ffc_round_nearest_tie_even_cmp(am, _shift, ord), vk); +} + +/* 1-byte chars (char, uint8_t) */ +#define FFC_INT_CMP_ZEROS_1 0x3030303030303030ULL +#define FFC_INT_CMP_LEN_1 8 + +/* 2-byte chars (uint16_t, wchar_t on Windows) */ +#define FFC_INT_CMP_ZEROS_2 0x0030003000300030ULL +#define FFC_INT_CMP_LEN_2 4 + +/* 4-byte chars (uint32_t, wchar_t on Linux) */ +#define FFC_INT_CMP_ZEROS_4 0x0000003000000030ULL +#define FFC_INT_CMP_LEN_4 2 + +ffc_internal ffc_inline +bool ffc_char_eq_zero(char const *p, size_t char_width) { + switch (char_width) { + case 1: return *p == '0'; + case 2: return *(uint16_t const *)p == 0x0030; + case 4: return *(uint32_t const *)p == 0x00000030; + default: return false; + } +} + +ffc_internal ffc_inline +void ffc_skip_zeros(char **first, char *last, size_t char_width) { + size_t cmp_len; + uint64_t cmp_mask; + switch (char_width) { + case 1: + cmp_len = FFC_INT_CMP_LEN_1; + cmp_mask = FFC_INT_CMP_ZEROS_1; + break; + case 2: + cmp_len = FFC_INT_CMP_LEN_2; + cmp_mask = FFC_INT_CMP_ZEROS_2; + break; + case 4: + cmp_len = FFC_INT_CMP_LEN_4; + cmp_mask = FFC_INT_CMP_ZEROS_4; + break; + default: + FFC_DEBUG_ASSERT(0); + return; + } + + uint64_t val; + while ((size_t)(last - *first) >= (cmp_len * char_width)) { + memcpy(&val, *first, sizeof(uint64_t)); + if (val != cmp_mask) { + break; + } + *first += cmp_len * char_width; + } + while (*first != last) { + if (!ffc_char_eq_zero(*first, char_width)) { + break; + } + *first += char_width; + } +} + +// determine if any non-zero digits were truncated. +// all characters must be valid digits. +ffc_internal ffc_inline +bool ffc_is_truncated(char const *first, char const *last, size_t char_width) { + size_t cmp_len; + uint64_t cmp_mask; + switch (char_width) { + case 1: + cmp_len = FFC_INT_CMP_LEN_1; + cmp_mask = FFC_INT_CMP_ZEROS_1; + break; + case 2: + cmp_len = FFC_INT_CMP_LEN_2; + cmp_mask = FFC_INT_CMP_ZEROS_2; + break; + case 4: + cmp_len = FFC_INT_CMP_LEN_4; + cmp_mask = FFC_INT_CMP_ZEROS_4; + break; + default: + FFC_DEBUG_ASSERT(0); + return 0; + } + // do 8-bit optimizations, can just compare to 8 literal 0s. + uint64_t val; + while ((size_t)(last - first) >= cmp_len) { + memcpy(&val, first, sizeof(uint64_t)); + if (val != cmp_mask) { + return true; + } + first += cmp_len * char_width; + } + while (first != last) { + if (!ffc_char_eq_zero(first, char_width)) { + return true; + } + first += char_width; + } + return false; +} + +ffc_internal ffc_inline +void ffc_parse_eight_digits(char **p, ffc_bigint_limb *value, size_t *counter, size_t *count) { + *value = *value * 100000000 + ffc_parse_eight_digits_unrolled(*p); + *p += 8; + *counter += 8; + *count += 8; +} + +ffc_internal ffc_inline +void ffc_parse_one_digit(char **p, ffc_bigint_limb *value, size_t *counter, size_t *count) { + *value = *value * 10 + (ffc_bigint_limb)(**p - '0'); + *p += 1; + *counter += 1; + *count += 1; +} + +ffc_internal ffc_inline +void ffc_add_native(ffc_bigint *big, ffc_bigint_limb power, ffc_bigint_limb value) { + ffc_bigint_mul(big, power); + ffc_bigint_add(big, value); +} + +ffc_internal ffc_inline +void ffc_round_up_bigint(ffc_bigint *big, size_t *count) { + // need to round-up the digits, but need to avoid rounding + // ....9999 to ...10000, which could cause a false halfway point. + ffc_add_native(big, 10, 1); + (*count)++; +} + +// parse the significant digits into a big integer +ffc_internal ffc_inline +void ffc_parse_mantissa(ffc_bigint *result, ffc_parsed num, + size_t max_digits, size_t *const digits) { + // 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 + // scalar value (9 or 19 digits) for each step. + size_t counter = 0; + *digits = 0; + ffc_bigint_limb value = 0; +#ifdef FFC_64BIT_LIMB + size_t step = 19; +#else + size_t step = 9; +#endif + + // process all integer digits. + char *p = num.int_part_start; + char *pend = p + num.int_part_len; + ffc_skip_zeros(&p, pend, 1); + // process all digits, in increments of step per loop + while (p != pend) { + while ((pend - p >= 8) && (step - counter >= 8) && + (max_digits - *digits >= 8)) { + ffc_parse_eight_digits(&p, &value, &counter, digits); + } + while (counter < step && p != pend && *digits < max_digits) { + ffc_parse_one_digit(&p, &value, &counter, digits); + } + if (*digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + ffc_add_native(result, (ffc_bigint_limb)(ffc_powers_of_ten_uint64[counter]), value); + bool truncated = ffc_is_truncated(p, pend, 1); + if (num.fraction_part_start != NULL) { + truncated |= ffc_is_truncated(num.fraction_part_start, num.fraction_part_start + num.fraction_part_len, 1); + } + if (truncated) { + ffc_round_up_bigint(result, digits); + } + return; + } else { + ffc_add_native(result, (ffc_bigint_limb)(ffc_powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + + // add our fraction digits, if they're available. + if (num.fraction_part_start != NULL) { + p = num.fraction_part_start; + pend = p + num.fraction_part_len; + if (*digits == 0) { + ffc_skip_zeros(&p, pend, 1); + } + // process all digits, in increments of step per loop + while (p != pend) { + while ((pend - p >= 8) && (step - counter >= 8) && + (max_digits - *digits >= 8)) { + ffc_parse_eight_digits(&p, &value, &counter, digits); + } + while (counter < step && p != pend && *digits < max_digits) { + ffc_parse_one_digit(&p, &value, &counter, digits); + } + if (*digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + ffc_add_native(result, (ffc_bigint_limb)(ffc_powers_of_ten_uint64[counter]), value); + bool truncated = ffc_is_truncated(p, pend, 1); + if (truncated) { + ffc_round_up_bigint(result, digits); + } + return; + } else { + ffc_add_native(result, (ffc_bigint_limb)(ffc_powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + } + + if (counter != 0) { + ffc_add_native(result, (ffc_bigint_limb)(ffc_powers_of_ten_uint64[counter]), value); + } +} + +ffc_internal ffc_inline +ffc_adjusted_mantissa ffc_positive_digit_comp(ffc_bigint *bigmant, int32_t exponent, ffc_value_kind vk) { + for (int i = 0; i < bigmant->vec.len; i++) { + ffc_debug("limb %d: %lld\n", i, bigmant->vec.data[i]); + } + FFC_ASSERT(ffc_bigint_pow10(bigmant, (uint32_t)(exponent))); + ffc_adjusted_mantissa answer; + bool truncated; + answer.mantissa = ffc_bigint_hi64(*bigmant, &truncated); + int bias = ffc_const(vk, MANTISSA_EXPLICIT_BITS) - ffc_const(vk, MINIMUM_EXPONENT); + answer.power2 = ffc_bigint_bit_length(*bigmant) - 64 + bias; + + ffc_round_tie_even_truncated(&answer, truncated, vk); + + return answer; +} + +// the scaling here is quite simple: we have, for the real digits `m * 10^e`, +// and for the theoretical digits `n * 2^f`. Since `e` is always negative, +// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. +// we then need to scale by `2^(f- e)`, and then the two significant digits +// are of the same magnitude. +ffc_internal ffc_inline +ffc_adjusted_mantissa ffc_negative_digit_comp( + ffc_bigint *bigmant, ffc_adjusted_mantissa am, int32_t exponent, ffc_value_kind vk) { + ffc_bigint *real_digits = bigmant; + int32_t real_exp = exponent; + + // get the value of `b`, rounded down, and get a bigint representation of b+h + ffc_adjusted_mantissa am_b = am; + ffc_round_down(&am_b, vk); + ffc_value b; + ffc_am_to_float(false, am_b, &b, vk); + + ffc_adjusted_mantissa theor = ffc_to_extended_halfway(b, vk); + ffc_bigint theor_digits = ffc_bigint_make(theor.mantissa); + int32_t theor_exp = theor.power2; + + // scale real digits and theor digits to be same power. + int32_t pow2_exp = theor_exp - real_exp; + uint32_t pow5_exp = (uint32_t)(-real_exp); + if (pow5_exp != 0) { + FFC_ASSERT(ffc_bigint_pow5(&theor_digits, pow5_exp)); + } + if (pow2_exp > 0) { + FFC_ASSERT(ffc_bigint_pow2(&theor_digits, (uint32_t)(pow2_exp))); + } else if (pow2_exp < 0) { + FFC_ASSERT(ffc_bigint_pow2(real_digits,(uint32_t)(-pow2_exp))); + } + + // compare digits, and use it to direct rounding + int ord = ffc_bigint_compare(*real_digits, &theor_digits); + ffc_adjusted_mantissa answer = am; + ffc_round_double_tie_even_cmp(&answer, ord, vk); + + return answer; +} + +// parse the significant digits as a big integer to unambiguously round +// the significant digits. here, we are trying to determine how to round +// an extended float representation close to `b+h`, halfway between `b` +// (the float rounded-down) and `b+u`, the next positive float. this +// algorithm is always correct, and uses one of two approaches. when +// the exponent is positive relative to the significant digits (such as +// 1234), we create a big-integer representation, get the high 64-bits, +// determine if any lower bits are truncated, and use that to direct +// rounding. in case of a negative exponent relative to the significant +// digits (such as 1.2345), we create a theoretical representation of +// `b` as a big-integer type, scaled to the same binary exponent as +// the actual digits. we then compare the big integer representations +// of both, and use that to direct rounding. +ffc_internal +ffc_adjusted_mantissa ffc_digit_comp(ffc_parsed num, ffc_adjusted_mantissa am, ffc_value_kind vk) { + ffc_debug("digit_comp\n"); + // remove the invalid exponent bias + am.power2 -= FFC_INVALID_AM_BIAS; + + int32_t sci_exp = + ffc_scientific_exponent(num.mantissa, (int32_t)(num.exponent)); + size_t max_digits = ffc_const(vk, MAX_DIGITS); + size_t digits = 0; + ffc_bigint bigmant = ffc_bigint_empty(); + ffc_parse_mantissa(&bigmant, num, max_digits, &digits); + // can't underflow, since digits is at most max_digits. + int32_t exponent = sci_exp + 1 - (int32_t)(digits); + if (exponent >= 0) { + return ffc_positive_digit_comp(&bigmant, exponent, vk); + } else { + return ffc_negative_digit_comp(&bigmant, am, exponent, vk); + } +} + +#endif // FFC_DIGIT_COMPARISON_H +#ifndef FFC_FORMAT_H +#define FFC_FORMAT_H + + +/* section: binary to decimal (fixed notation) + * + * Exact fixed-notation formatting, equivalent to printf("%.*f") on a libc + * that prints the exact binary value (glibc, musl, macOS). No floating-point + * arithmetic is used, so the result does not depend on the FPU rounding mode + * and is bit-for-bit reproducible across platforms. + * + * A double is m * 2^e with m < 2^53. To print `places` fraction digits we + * need N = round(m * 2^e * 10^places), then print N with the decimal point + * `places` digits from the right. + * + * e >= 0: the value is the integer m << e, every fraction digit is zero. + * e < 0: let k = -e. Only the first k fraction digits can be nonzero, so + * with p = min(places, k): N = round((m * 5^p) >> (k - p)) + * and the remaining places - p digits are zero. + * + * The shift-right is where rounding happens: the dropped bits give the exact + * half/sticky information for round-half-to-even. The worst case is + * m * 5^1074, about 2550 bits, which the 4000-bit ffc_bigint holds easily. + * The work is bounded by `places`, not by the exponent, so small numbers + * printed with few places stay cheap. + */ + +// Divide in place by a divisor below 2^32, returning the remainder. +// Each 64-bit limb is split into 32-bit halves so the only division needed +// is 64-by-32, which is native everywhere and never a compiler-rt libcall. +ffc_internal +uint32_t ffc_bigint_divmod_u32(ffc_bigint* me, uint32_t d) { + uint64_t rem = 0; + for (size_t index = me->vec.len; index > 0; index--) { + ffc_bigint_limb x = me->vec.data[index - 1]; +#ifdef FFC_64BIT_LIMB + uint64_t hi = (rem << 32) | (x >> 32); + uint64_t qhi = hi / d; + rem = hi % d; + uint64_t lo = (rem << 32) | (x & 0xFFFFFFFFu); + uint64_t qlo = lo / d; + rem = lo % d; + me->vec.data[index - 1] = (qhi << 32) | qlo; +#else + uint64_t cur = (rem << 32) | x; + me->vec.data[index - 1] = (ffc_bigint_limb)(cur / d); + rem = cur % d; +#endif + } + ffc_sv_normalize(&me->vec); + return (uint32_t)rem; +} + +// Test bit `n` (0 = least significant). +ffc_internal ffc_inline +bool ffc_bigint_test_bit(ffc_bigint const* me, size_t n) { + size_t limb = n / FFC_LIMB_BITS; + if (limb >= me->vec.len) { + return false; + } + return ((me->vec.data[limb] >> (n % FFC_LIMB_BITS)) & 1) != 0; +} + +// True if any of the bits [0, n) is set. +ffc_internal +bool ffc_bigint_any_bits_below(ffc_bigint const* me, size_t n) { + size_t whole = n / FFC_LIMB_BITS; + size_t rem = n % FFC_LIMB_BITS; + if (whole > me->vec.len) { + whole = me->vec.len; + rem = 0; + } + for (size_t index = 0; index < whole; index++) { + if (me->vec.data[index] != 0) { + return true; + } + } + if (rem != 0 && whole < me->vec.len) { + ffc_bigint_limb mask = (((ffc_bigint_limb)1) << rem) - 1; + return (me->vec.data[whole] & mask) != 0; + } + return false; +} + +// Shift right by `n` bits, discarding the low bits. +ffc_internal +void ffc_bigint_shr(ffc_bigint* me, size_t n) { + size_t limbs = n / FFC_LIMB_BITS; + size_t bits = n % FFC_LIMB_BITS; + if (limbs >= me->vec.len) { + me->vec.len = 0; + return; + } + if (limbs != 0) { + size_t keep = me->vec.len - limbs; + memmove(me->vec.data, me->vec.data + limbs, keep * sizeof(ffc_bigint_limb)); + me->vec.len = (uint16_t)keep; + } + if (bits != 0) { + size_t last = me->vec.len - 1; + for (size_t index = 0; index < last; index++) { + me->vec.data[index] = (me->vec.data[index] >> bits) | + (me->vec.data[index + 1] << (FFC_LIMB_BITS - bits)); + } + me->vec.data[last] >>= bits; + } + ffc_sv_normalize(&me->vec); +} + +// Bounded output cursor. `len` always counts the full output; bytes past +// `cap` are dropped so the caller can size a buffer from the return value. +typedef struct ffc_writer { + char *buf; + size_t cap; + size_t len; +} ffc_writer; + +ffc_internal ffc_inline +void ffc_writer_put(ffc_writer* w, char c) { + if (w->len < w->cap) { + w->buf[w->len] = c; + } + w->len++; +} + +ffc_internal ffc_inline +void ffc_writer_put_str(ffc_writer* w, char const* s) { + while (*s) { + ffc_writer_put(w, *s++); + } +} + +ffc_internal ffc_inline +void ffc_writer_put_zeros(ffc_writer* w, size_t n) { + while (n--) { + ffc_writer_put(w, '0'); + } +} + +// Largest N we can produce is just under 2^53 * 5^1074, which has 767 +// decimal digits (768 after a round-up). Digits are pulled nine at a time, +// so round up to a multiple of nine with slack. +#define FFC_FORMAT_DIGIT_BUF 800 + +size_t ffc_format_double_fixed(char *buf, size_t cap, double value, int places) { + ffc_writer w; + w.buf = buf; + w.cap = cap; + w.len = 0; + + size_t p = places < 0 ? 6 : (size_t)places; + + uint64_t bits = ffc_get_double_bits(value); + bool negative = (bits >> FFC_DOUBLE_SIGN_INDEX) != 0; + int32_t biased_exp = (int32_t)((bits & FFC_DOUBLE_EXPONENT_MASK) >> FFC_DOUBLE_MANTISSA_EXPLICIT_BITS); + uint64_t m = bits & FFC_DOUBLE_MANTISSA_MASK; + + if (negative) { + ffc_writer_put(&w, '-'); + } + if (biased_exp == FFC_DOUBLE_INFINITE_POWER) { + ffc_writer_put_str(&w, m == 0 ? "inf" : "nan"); + return w.len; + } + + int32_t e; + if (biased_exp == 0) { + e = FFC_DOUBLE_MINIMUM_EXPONENT + 1 - FFC_DOUBLE_MANTISSA_EXPLICIT_BITS; // subnormal: -1074 + } else { + m |= FFC_DOUBLE_HIDDEN_BIT_MASK; + e = biased_exp + FFC_DOUBLE_MINIMUM_EXPONENT - FFC_DOUBLE_MANTISSA_EXPLICIT_BITS; + } + if (m == 0) { + e = 0; + } + // Strip trailing zero bits so k is as small as it can be. + while (e < 0 && (m & 1) == 0) { + m >>= 1; + e++; + } + + // N = big, holding p_eff fraction digits. + ffc_bigint big = ffc_bigint_make(m); + size_t p_eff = 0; + if (e >= 0) { + if (m != 0) { + FFC_ASSERT(ffc_bigint_shl(&big, (size_t)e)); + } + } else { + size_t k = (size_t)(-e); + p_eff = p < k ? p : k; + FFC_ASSERT(ffc_bigint_pow5(&big, (uint32_t)p_eff)); + size_t drop = k - p_eff; + if (drop != 0) { + bool half = ffc_bigint_test_bit(&big, drop - 1); + bool sticky = ffc_bigint_any_bits_below(&big, drop - 1); + ffc_bigint_shr(&big, drop); + bool odd = big.vec.len != 0 && (big.vec.data[0] & 1) != 0; + if (half && (sticky || odd)) { + FFC_ASSERT(ffc_bigint_add(&big, 1)); + } + } + } + + // Extract the decimal digits of N, least significant first. + char digits[FFC_FORMAT_DIGIT_BUF]; + size_t ndigits = 0; + while (big.vec.len != 0) { + uint32_t chunk = ffc_bigint_divmod_u32(&big, 1000000000u); + for (int i = 0; i < 9; i++) { + digits[ndigits++] = (char)('0' + chunk % 10); + chunk /= 10; + } + } + while (ndigits > 0 && digits[ndigits - 1] == '0') { + ndigits--; + } + + // Integer part: everything above the p_eff fraction digits. + if (ndigits > p_eff) { + for (size_t i = ndigits; i > p_eff; i--) { + ffc_writer_put(&w, digits[i - 1]); + } + } else { + ffc_writer_put(&w, '0'); + } + + if (p != 0) { + ffc_writer_put(&w, '.'); + size_t have = ndigits < p_eff ? ndigits : p_eff; + ffc_writer_put_zeros(&w, p_eff - have); + for (size_t i = have; i > 0; i--) { + ffc_writer_put(&w, digits[i - 1]); + } + ffc_writer_put_zeros(&w, p - p_eff); + } + return w.len; +} + +#undef FFC_FORMAT_DIGIT_BUF + +/* end section: binary to decimal */ + +#endif // FFC_FORMAT_H + +/* section: decimal to binary */ + +ffc_inline ffc_internal +ffc_u128 ffc_compute_product_approximation(int64_t q, uint64_t w, ffc_value_kind vk) { + // The required precision is mantissa_explicit_bits + 3 because + // 1. We need the implicit bit + // 2. We need an extra bit for rounding purposes + // 3. We might lose a bit due to the "upperbit" routine (result too small, + // requiring a shift) + uint64_t bit_precision = ffc_const(vk, MANTISSA_EXPLICIT_BITS) + 3; + uint64_t precision_mask = ((uint64_t)(0xFFFFFFFFFFFFFFFF) >> bit_precision); + + // Use FFC_DOUBLE_SMALLEST_POWER_OF_10 (-342) regardless of value kind + // to index into the shared 128 bit table + int const index = 2 * (int)(q - FFC_DOUBLE_SMALLEST_POWER_OF_10); + + // For small values of q, e.g., q in [0,27], the answer is always exact + // because ffc_mul_u64(w, powers_of_five[index]) gives the exact answer. + ffc_u128 firstproduct = ffc_mul_u64(w, FFC_POWERS_OF_FIVE[index]); + + if ((firstproduct.high & precision_mask) == precision_mask) { + // could further guard with (lower + w < lower) + // regarding the second product, we only need secondproduct.hi, but our + // expectation is that the compiler will optimize this extra work away if + // needed. + ffc_u128 secondproduct = ffc_mul_u64(w, FFC_POWERS_OF_FIVE[index + 1]); + + firstproduct.low += secondproduct.high; + if (secondproduct.high > firstproduct.low) { + firstproduct.high++; + } + } + return firstproduct; +} + +/** + * For q in (0,350), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * floor(p) + q + * where + * p = log(5**q)/log(2) = q * log(5)/log(2) + * + * For negative values of q in (-400,0), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * -ceil(p) + q + * where + * p = log(5**-q)/log(2) = -q * log(5)/log(2) + + * FF_DIVERGE: renamed from detail::power to b10_to_b2 + */ +ffc_internal ffc_inline int32_t ffc_b10_to_b2(int32_t q) { + return (((152170 + 65536) * q) >> 16) + 63; +} + +// Computes w * 10 ** q. +// The returned value should be a valid number that simply needs to be +// packed. However, in some very rare cases, the computation will fail. In such +// cases, we return an adjusted_mantissa with a negative power of 2: the caller +// should recompute in such cases. +ffc_inline ffc_internal +ffc_adjusted_mantissa ffc_compute_float(int64_t q, uint64_t w, ffc_value_kind vk) { + ffc_adjusted_mantissa answer; + if ((w == 0) || (q < ffc_const(vk, SMALLEST_POWER_OF_10))) { + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + if (q > ffc_const(vk, LARGEST_POWER_OF_10)) { + // we want to get infinity: + answer.power2 = ffc_const(vk, INFINITE_POWER); + answer.mantissa = 0; + return answer; + } + // At this point in time q is in [powers::smallest_power_of_five, + // powers::largest_power_of_five]. + + // We want the most significant bit of i to be 1. Shift if needed. + int32_t lz = (int32_t)ffc_count_leading_zeroes(w); + w <<= lz; + + ffc_u128 product = ffc_compute_product_approximation(q, w, vk); + + // The computed 'product' is always sufficient. + // Mathematical proof: + // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to + // appear) See script/mushtak_lemire.py + + // The "compute_product_approximation" function can be slightly slower than a + // branchless approach: value128 product = compute_product(q, w); but in + // practice, we can win big with the compute_product_approximation if its + // additional branch is easily predicted. Which is data specific. + int upperbit = (int)(product.high >> 63); + int shift = upperbit + 64 - ffc_const(vk, MANTISSA_EXPLICIT_BITS) - 3; + + answer.mantissa = product.high >> shift; + + // compute a biased-up power of 2 + answer.power2 = (int32_t)(ffc_b10_to_b2((int32_t)(q)) + upperbit - lz - ffc_const(vk, MINIMUM_EXPONENT)); + + if (answer.power2 <= 0) { // subnormal path + + if (-answer.power2 + 1 >= 64) { + // if we have more than 64 bits below the minimum exponent, you + // have a zero for sure. + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + // shift is safe because -answer.power2 + 1 < 64 + answer.mantissa >>= -answer.power2 + 1; + + // Thankfully, we can't have both "round-to-even" and subnormals because + // "round-to-even" only occurs for powers close to 0 in the 32-bit and + // and 64-bit case (with no more than 19 digits), so we round up. + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + + // weird scenario: + // Suppose we start with 2.2250738585072013e-308, we end up + // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + // subnormal, but we can only know this after rounding. + // So we only declare a subnormal if we are smaller than the threshold. + answer.power2 = + (answer.mantissa < ((uint64_t)(1) << ffc_const(vk, MANTISSA_EXPLICIT_BITS))) ? 0 : 1; + return answer; + } // subnormal + + // usually, we round *up*, but if we fall right in between and we have an + // even basis, we need to round down + // We are only concerned with the cases where 5**q fits in single 64-bit word. + + // 'extremely sparse low bits in our product' and + // 'q is within the round to even range' and + // 'mantissa lowest 2 bits are exactly 01' + if ((product.low <= 1) && (q >= ffc_const(vk, MIN_EXPONENT_ROUND_TO_EVEN)) && + (q <= ffc_const(vk, MAX_EXPONENT_ROUND_TO_EVEN)) && + ((answer.mantissa & 3) == 1)) { // we may fall between two floats! + // + // To be in-between two floats we need that in doing + // answer.mantissa = product.high >> (upperbit + 64 - + // binary::mantissa_explicit_bits() - 3); + // ... we dropped out only zeroes. But if this happened, then we can go + // back!!! + + // mask off last bit + if ((answer.mantissa << shift) == product.high) { + answer.mantissa &= ~(uint64_t)(1); + } + } + + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + if (answer.mantissa >= ((uint64_t)(2) << ffc_const(vk, MANTISSA_EXPLICIT_BITS))) { + answer.mantissa = ((uint64_t)(1) << ffc_const(vk, MANTISSA_EXPLICIT_BITS)); + answer.power2++; // undo previous addition + } + + // normalize to pos INF? + answer.mantissa &= ~((uint64_t)(1) << ffc_const(vk, MANTISSA_EXPLICIT_BITS)); + if (answer.power2 >= ffc_const(vk, INFINITE_POWER)) { // infinity + answer.power2 = ffc_const(vk, INFINITE_POWER); + answer.mantissa = 0; + } + return answer; +} + +// create an adjusted mantissa, biased by the invalid power2 +// for significant digits already multiplied by 10 ** q. +ffc_internal ffc_inline +ffc_adjusted_mantissa ffc_compute_error_scaled(int64_t q, uint64_t w, int lz, ffc_value_kind vk) { + int hilz = (int)(w >> 63) ^ 1; + ffc_adjusted_mantissa answer; + answer.mantissa = w << hilz; + int bias = ffc_const(vk, MANTISSA_EXPLICIT_BITS) - ffc_const(vk, MINIMUM_EXPONENT); + answer.power2 = (int32_t)(ffc_b10_to_b2((int32_t)(q)) + bias - hilz - lz - 62 + + FFC_INVALID_AM_BIAS); + return answer; +} + +// w * 10 ** q, without rounding the representation up. +// the power2 in the exponent will be adjusted by invalid_am_bias. +ffc_internal ffc_inline +ffc_adjusted_mantissa ffc_compute_error(int64_t q, uint64_t w, ffc_value_kind vk) { + int32_t lz = (int32_t)ffc_count_leading_zeroes(w); + w <<= lz; + ffc_u128 product = ffc_compute_product_approximation(q, w, vk); + return ffc_compute_error_scaled(q, product.high, lz, vk); +} + +/* end section: decimal to binary */ + +/* section: entrypoint */ + +ffc_internal ffc_inline +bool ffc_clinger_fast_path_impl(uint64_t mantissa, int64_t exponent, bool is_negative, + ffc_value *value, ffc_value_kind value_kind) { + bool is_double = value_kind == FFC_VALUE_KIND_DOUBLE; + // The implementation of the Clinger's fast path is convoluted because + // we want round-to-nearest in all cases, irrespective of the rounding mode + // selected on the thread. + // We proceed optimistically, assuming that detail::rounds_to_nearest() + // returns true. + // Single unsigned range check replaces two signed comparisons, matching + // fast_float's layout: (uint64_t)(e - MIN) <= (MAX - MIN) in one compare. + if ((uint64_t)((int64_t)exponent - (int64_t)ffc_const(value_kind, MIN_EXPONENT_FAST_PATH)) <= + (uint64_t)((int64_t)ffc_const(value_kind, MAX_EXPONENT_FAST_PATH) - (int64_t)ffc_const(value_kind, MIN_EXPONENT_FAST_PATH))) { + // 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 (ffc_rounds_to_nearest()) { + // We have that fegetround() == FE_TONEAREST. + // Next is Clinger's fast path. + if (mantissa <= ffc_const(value_kind, MAX_MANTISSA_FAST_PATH)) { + ffc_set_value(value, value_kind, mantissa); + + if (exponent < 0) { + if (is_double) { + value->d = value->d / FFC_DOUBLE_POWERS_OF_TEN[-exponent]; + } else { + value->f = value->f / FFC_FLOAT_POWERS_OF_TEN[-exponent]; + }; + } else { + if (is_double) { + value->d = value->d * FFC_DOUBLE_POWERS_OF_TEN[exponent]; + } else { + value->f = value->f * FFC_FLOAT_POWERS_OF_TEN[exponent]; + }; + } + if (is_negative) { + ffc_set_value(value, value_kind, -ffc_read_value(value, value_kind)); + } + return true; + } + } 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 (exponent >= 0 && + mantissa <= ffc_const(value_kind, MAX_MANTISSA)[exponent]) { +#if defined(__clang__) || defined(FFC_32BIT) + // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD + if (mantissa == 0) { + ffc_set_value(value, value_kind, is_negative ? -0. : 0.); + return true; + } +#endif + if (is_double) { + value->d = (double)mantissa * FFC_DOUBLE_POWERS_OF_TEN[exponent]; + } else { + value->f = (float)mantissa * FFC_FLOAT_POWERS_OF_TEN[exponent]; + } + if (is_negative) { + ffc_set_value(value, value_kind, -ffc_read_value(value, value_kind)); + } + return true; + } + } + } + return false; +} + +ffc_internal ffc_inline +ffc_result ffc_from_chars_advanced(ffc_parsed const pns, ffc_value* value, ffc_value_kind vk) { + ffc_result answer; + + answer.outcome = FFC_OUTCOME_OK; // be optimistic :') + answer.ptr = (char*)pns.lastmatch; + + if (!pns.too_many_digits && pns.exponent == 0 && + pns.mantissa <= ffc_const(vk, MAX_MANTISSA_FAST_PATH)) { +#if defined(__clang__) || defined(FFC_32BIT) + if (pns.mantissa == 0) { + ffc_set_value(value, vk, pns.negative ? -0. : 0.); + return answer; + } +#endif + ffc_set_value(value, vk, pns.mantissa); + if (pns.negative) { ffc_set_value(value, vk, -ffc_read_value(value, vk)); } + return answer; + } + + if (!pns.too_many_digits && + ffc_clinger_fast_path_impl(pns.mantissa, pns.exponent, pns.negative, value, vk)) { + ffc_debug("fast path hit"); + return answer; + } + + ffc_adjusted_mantissa am = ffc_compute_float(pns.exponent, pns.mantissa, vk); + ffc_debug("am.mantissa: %llu\n", am.mantissa); + ffc_debug("am.power2: %d\n", am.power2); + if (pns.too_many_digits && am.power2 >= 0) { + ffc_adjusted_mantissa am_plus_one = ffc_compute_float(pns.exponent, pns.mantissa + 1, vk); + bool equal = am.mantissa == am_plus_one.mantissa && am.power2 == am_plus_one.power2; + if (!equal) { + am = ffc_compute_error(pns.exponent, pns.mantissa, vk); + } + } + // If we called ffc_compute_float(pns.exponent, pns.mantissa) + // and we have an invalid power (am.power2 < 0), then we need to go the long + // way around again. This is very uncommon. + if (am.power2 < 0) { + am = ffc_digit_comp(pns, am, vk); + } + ffc_debug("am post mantissa: %llu\n", am.mantissa); + ffc_debug("am post power2: %d\n", am.power2); + ffc_am_to_float(pns.negative, am, value, vk); + + // Test for over/underflow. + if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || + am.power2 == ffc_const(vk, INFINITE_POWER)) { + answer.outcome = FFC_OUTCOME_OUT_OF_RANGE; + } + return answer; +} + +ffc_internal ffc_inline +ffc_result ffc_from_chars(char* first, char* last, ffc_parse_options options, ffc_value *value, ffc_value_kind vk) { + + // Alias for parity with cpp code, no feature macros to apply + ffc_format const fmt = options.format; + + ffc_result answer; + if ((uint64_t)(fmt & FFC_FORMAT_FLAG_SKIP_WHITE_SPACE)) { + while ((first != last) && ffc_is_space(*first)) { + first++; + } + } + if (first == last) { + answer.outcome = FFC_OUTCOME_INVALID_INPUT; + answer.ptr = first; + return answer; + } + uint64_t json_mode = (uint64_t)(fmt & FFC_FORMAT_FLAG_BASIC_JSON); + ffc_parsed pns = ffc_parse_number_string(first, last, options, json_mode); + + #ifdef FFC_DEBUG + ffc_dump_parsed(pns); + #endif + + if (!pns.valid) { + if ((uint64_t)(fmt & FFC_FORMAT_FLAG_NO_INFNAN)) { + answer.outcome = FFC_OUTCOME_INVALID_INPUT; + answer.ptr = first; + return answer; + } else { + return ffc_parse_infnan(first, last, value, vk, fmt); + } + } + + // call overload that takes parsed_number_string directly. + return ffc_from_chars_advanced(pns, value, vk); +} + +/* See FFC_API_INLINE in api.h: force-inlined in this TU, still exported. */ +FFC_DOUBLE_IMPL_INLINE ffc_result ffc_from_chars_double_options(const char *start, const char *end, double* out, ffc_parse_options options) { + // It would be UB to directly use *out as our ffc_value, even though its the same layout + ffc_value out_value = {0}; + + // The all-important call with a constant VALUE_KIND that should cascade in tons of inlining + ffc_result result = ffc_from_chars((char*)start, (char*)end, options, &out_value, FFC_VALUE_KIND_DOUBLE); + *out = out_value.d; + return result; +} +FFC_DOUBLE_IMPL_INLINE ffc_result ffc_from_chars_double(char const* first, char const* last, double* out) { + ffc_parse_options options = ffc_parse_options_default(); + return ffc_from_chars_double_options(first, last, out, options); +} +ffc_result ffc_parse_double(size_t len, const char *s, double *out) { + char *pend = (char*)(s + len); + return ffc_from_chars_double(s, pend, out); +} +double ffc_parse_double_simple(size_t len, const char *s, ffc_outcome *outcome) { + double out = 0.0; + ffc_result result = ffc_parse_double(len, s, &out); + if (outcome) { + *outcome = result.outcome; + } + return out; +} + +FFC_FLOAT_IMPL_INLINE ffc_result ffc_from_chars_float_options(const char *start, const char *end, float* out, ffc_parse_options options) { + ffc_value out_value = {0}; + ffc_result result = ffc_from_chars((char*)start, (char*)end, options, &out_value, FFC_VALUE_KIND_FLOAT); + *out = out_value.f; + return result; +} +FFC_FLOAT_IMPL_INLINE ffc_result ffc_from_chars_float(char const* first, char const* last, float* out) { + ffc_parse_options options = ffc_parse_options_default(); + return ffc_from_chars_float_options(first, last, out, options); +} +ffc_result ffc_parse_float(size_t len, const char *s, float *out) { + char *pend = (char*)(s + len); + return ffc_from_chars_float(s, pend, out); +} +float ffc_parse_float_simple(size_t len, const char *s, ffc_outcome *outcome) { + float out = 0.0; + ffc_result result = ffc_parse_float(len, s, &out); + if (outcome) { + *outcome = result.outcome; + } + return out; +} + +ffc_result ffc_from_chars_i64_options(const char *start, const char *end, int base, int64_t* out, ffc_parse_options options) { + ffc_int_value value_out = {0}; + ffc_result result = ffc_parse_int_string(start, end, &value_out, FFC_INT_KIND_S64, options, base); + *out = value_out.s64; + return result; +} +ffc_result ffc_from_chars_i64(char const* first, char const* last, int base, int64_t* out) { + ffc_parse_options options = ffc_parse_options_default(); + return ffc_from_chars_i64_options(first, last, base, out, options); +} +ffc_result ffc_from_chars_u64_options(const char *start, const char *end, int base, uint64_t* out, ffc_parse_options options) { + ffc_int_value value_out = {0}; + ffc_result result = ffc_parse_int_string(start, end, &value_out, FFC_INT_KIND_U64, options, base); + *out = value_out.u64; + return result; +} +ffc_result ffc_from_chars_u64(char const* first, char const* last, int base, uint64_t* out) { + ffc_parse_options options = ffc_parse_options_default(); + return ffc_from_chars_u64_options(first, last, base, out, options); +} +ffc_result ffc_from_chars_i32_options(const char *start, const char *end, int base, int32_t* out, ffc_parse_options options) { + ffc_int_value value_out = {0}; + ffc_result result = ffc_parse_int_string(start, end, &value_out, FFC_INT_KIND_S32, options, base); + *out = value_out.s32; + return result; +} +ffc_result ffc_from_chars_i32(char const* first, char const* last, int base, int32_t* out) { + ffc_parse_options options = ffc_parse_options_default(); + return ffc_from_chars_i32_options(first, last, base, out, options); +} +ffc_result ffc_from_chars_u32_options(const char *start, const char *end, int base, uint32_t* out, ffc_parse_options options) { + ffc_int_value value_out = {0}; + ffc_result result = ffc_parse_int_string(start, end, &value_out, FFC_INT_KIND_U32, options, base); + *out = value_out.u32; + return result; +} +ffc_result ffc_from_chars_u32(char const* first, char const* last, int base, uint32_t* out) { + ffc_parse_options options = ffc_parse_options_default(); + return ffc_from_chars_u32_options(first, last, base, out, options); +} + + +ffc_result ffc_parse_i64(size_t len, const char *input, int base, int64_t *out) { + char *pend = (char*)(input + len); + return ffc_from_chars_i64(input, pend, base, out); +} +ffc_result ffc_parse_u64(size_t len, const char *input, int base, uint64_t *out) { + char *pend = (char*)(input + len); + return ffc_from_chars_u64(input, pend, base, out); +} +ffc_result ffc_parse_i32(size_t len, const char *input, int base, int32_t *out) { + char *pend = (char*)(input + len); + return ffc_from_chars_i32(input, pend, base, out); +} +ffc_result ffc_parse_u32(size_t len, const char *input, int base, uint32_t *out) { + char *pend = (char*)(input + len); + return ffc_from_chars_u32(input, pend, base, out); +} + +int64_t ffc_parse_i64_simple(size_t len, const char *input, int base, ffc_outcome *outcome) { + int64_t out = 0; + ffc_result result = ffc_parse_i64(len, input, base, &out); + if (outcome) { + *outcome = result.outcome; + } + return out; +} +uint64_t ffc_parse_u64_simple(size_t len, const char *input, int base, ffc_outcome *outcome) { + uint64_t out = 0; + ffc_result result = ffc_parse_u64(len, input, base, &out); + if (outcome) { + *outcome = result.outcome; + } + return out; +} +int32_t ffc_parse_i32_simple(size_t len, const char *input, int base, ffc_outcome *outcome) { + int32_t out = 0; + ffc_result result = ffc_parse_i32(len, input, base, &out); + if (outcome) { + *outcome = result.outcome; + } + return out; +} +uint32_t ffc_parse_u32_simple(size_t len, const char *input, int base, ffc_outcome *outcome) { + uint32_t out = 0; + ffc_result result = ffc_parse_u32(len, input, base, &out); + if (outcome) { + *outcome = result.outcome; + } + return out; +} + +ffc_result ffc_parse_json_number(const char *start, const char *end, + ffc_json_number *out) { + ffc_result answer; + + if (start == end) { + answer.ptr = (char *)start; + answer.outcome = FFC_OUTCOME_INVALID_INPUT; + return answer; + } + + ffc_parse_options opts; + opts.format = FFC_PRESET_JSON; + opts.decimal_point = '.'; + + ffc_parsed pns = ffc_parse_number_string(start, end, opts, true); + + if (!pns.valid) { + answer.ptr = (char *)pns.lastmatch; + answer.outcome = FFC_OUTCOME_INVALID_INPUT; + return answer; + } + + // INT64 or DOUBLE? + // For an integer bytes consumed past the sign should be just digits + // If we see '.' then `fractional_part_start` is not NULL + // If we see e/E then consumed span is > int_part_len (e + $digit) + // If both above are true then we have a DOUBLE + size_t consumed = (size_t)(pns.lastmatch - start) - (pns.negative ? 1 : 0); + bool is_integer = (pns.fraction_part_start == NULL) && (consumed == pns.int_part_len); + + ffc_result r; + if (is_integer) { + ffc_int_value v = {0}; + r = ffc_parse_int_string(start, end, &v, FFC_INT_KIND_S64, opts, 10); + out->kind = FFC_JSON_NUM_KIND_INT64; + if (r.outcome == FFC_OUTCOME_OK) { + out->value.i64 = v.s64; + } + } else { + ffc_value v = {0}; + r = ffc_from_chars_advanced(pns, &v, FFC_VALUE_KIND_DOUBLE); + out->kind = FFC_JSON_NUM_KIND_DOUBLE; + if (r.outcome == FFC_OUTCOME_OK) { + out->value.f64 = v.d; + } + } + return r; +} + +#undef FFC_DOUBLE_SMALLEST_POWER_OF_10 +#undef FFC_DOUBLE_LARGEST_POWER_OF_10 +#undef FFC_DOUBLE_SIGN_INDEX +#undef FFC_DOUBLE_INFINITE_POWER +#undef FFC_DOUBLE_MANTISSA_EXPLICIT_BITS +#undef FFC_DOUBLE_MINIMUM_EXPONENT +#undef FFC_DOUBLE_MIN_EXPONENT_ROUND_TO_EVEN +#undef FFC_DOUBLE_MAX_EXPONENT_ROUND_TO_EVEN +#undef FFC_DOUBLE_MAX_EXPONENT_FAST_PATH +#undef FFC_DOUBLE_MAX_MANTISSA_FAST_PATH +#undef FFC_DOUBLE_EXPONENT_MASK +#undef FFC_DOUBLE_MANTISSA_MASK +#undef FFC_DOUBLE_HIDDEN_BIT_MASK +#undef FFC_DOUBLE_MAX_DIGITS + +#undef FFC_FLOAT_SMALLEST_POWER_OF_10 +#undef FFC_FLOAT_LARGEST_POWER_OF_10 +#undef FFC_FLOAT_SIGN_INDEX +#undef FFC_FLOAT_INFINITE_POWER +#undef FFC_FLOAT_MANTISSA_EXPLICIT_BITS +#undef FFC_FLOAT_MINIMUM_EXPONENT +#undef FFC_FLOAT_MIN_EXPONENT_ROUND_TO_EVEN +#undef FFC_FLOAT_MAX_EXPONENT_ROUND_TO_EVEN +#undef FFC_FLOAT_MAX_EXPONENT_FAST_PATH +#undef FFC_FLOAT_MAX_MANTISSA_FAST_PATH +#undef FFC_FLOAT_EXPONENT_MASK +#undef FFC_FLOAT_MANTISSA_MASK +#undef FFC_FLOAT_HIDDEN_BIT_MASK +#undef FFC_FLOAT_MAX_DIGITS + +#undef FFC_POWERS_OF_5_NUMBER_OF_ENTRIES + +#endif /* FFC_IMPL */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* FFC_H */ + diff --git a/tools/sync_ffc.sh b/tools/sync_ffc.sh new file mode 100755 index 0000000..bb70934 --- /dev/null +++ b/tools/sync_ffc.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Sync the vendored ffc.h (C99 port of fast_float) into third_party/ffc/ +# from a local checkout of upstream (https://github.com/kolemannix/ffc.h). +# +# Usage: tools/sync_ffc.sh [/path/to/ffc.h-checkout] +# Default: ~/ffc.h +# +# Single header; the three license texts come along. Upstream tags +# releases as vYY.MM.NN — check out a tag before syncing. + +set -euo pipefail + +FFC="${1:-$HOME/ffc.h}" + +if [[ ! -d "$FFC/.git" ]]; then + echo "error: $FFC is not a git repo" >&2 + echo " git clone https://github.com/kolemannix/ffc.h $FFC" >&2 + exit 1 +fi +if ! grep -q "ffc_from_chars_double_options" "$FFC/ffc.h"; then + echo "error: $FFC/ffc.h does not look like the amalgamated header" >&2 + exit 1 +fi + +repo_root="$(cd "$(dirname "$0")/.." && pwd)" +dest="$repo_root/third_party/ffc" + +echo "==> syncing vendored ffc.h from $FFC" +mkdir -p "$dest" +cp "$FFC/ffc.h" "$dest/ffc.h" +for license in LICENSE-APACHE LICENSE-MIT LICENSE-BOOST; do + [[ -f "$FFC/$license" ]] && cp "$FFC/$license" "$dest/$license" +done + +commit="$(cd "$FFC" && git rev-parse HEAD)" +short="$(cd "$FFC" && git rev-parse --short HEAD)" +tag="$(cd "$FFC" && git describe --tags --exact-match HEAD 2>/dev/null || git -C "$FFC" describe --tags --always)" +date_iso="$(cd "$FFC" && git show -s --format=%cs HEAD)" +version="$(grep -E '^#define FFC_VERSION_(YEAR|MONTH|BUILD)' "$FFC/ffc.h" | awk '{printf "%s.", $3}' | sed 's/\.$//')" + +cat > "$dest/README.vendor" < done. Commit: $commit" From 7ee8aed62e8a8e4a248e8d9e12c10a4c4063860e Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 20 Oct 2026 20:49:36 +0200 Subject: [PATCH 11/13] =?UTF-8?q?floatium=20v0.16.0=20=E2=80=94=20default?= =?UTF-8?q?=20backends=20are=20now=20zmij=20+=20ffc=20(the=20CPython=20can?= =?UTF-8?q?didate)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shipped wheel is now the exact pure-C configuration proposed for CPython: Żmij for every dtoa mode and ffc.h (C99 fast_float) for parsing, so every user exercises the candidate code. fmt_opt / fast_float / ryu_opt / wuffs stay available at build time and, in an "all" build, at install time. README benchmark table re-measured with the new defaults on the same release CPython 3.14.3 (repr 1.5–5.4x, fixed 1.1–4.3x, parse 1–4.1x). fmt_opt remains faster on f"{x:.4f}" for huge magnitudes (Ryu d2fixed); the default favours what CPython can take over the last nanoseconds. Co-Authored-By: Claude Fable 5.1 --- BENCHMARKS.md | 4 ++-- CMakeLists.txt | 23 +++++++++++--------- INTERNALS.md | 22 ++++++++++--------- README.md | 52 ++++++++++++++++++++++++-------------------- floatium/__init__.py | 7 +++--- pyproject.toml | 10 ++++----- 6 files changed, 65 insertions(+), 53 deletions(-) diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 2eedba0..bb1fcb6 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -7,8 +7,8 @@ backend (`fast_float`, `wuffs`, `ffc`, `stock`). ## Methodology -- **floatium:** 0.15.0 (fmt 12.2.0, fast_float v8.3.2), release build, - all backends compiled in. +- **floatium:** 0.16.0 (zmij a9d6363, ffc.h v26.09.01, fmt 12.2.0, + fast_float v8.3.2), release build, all backends compiled in. - **CPython:** 3.14.3. - **Date:** 2026-10-19. - **Script:** `FLOATIUM_AUTOPATCH=0 python -m bench.bench_backend_matrix`. diff --git a/CMakeLists.txt b/CMakeLists.txt index 53bcdfd..7ca126f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,16 +9,16 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_POSITION_INDEPENDENT_CODE ON) # --- Backend selection ------------------------------------------------------ -# Format backend: fmt_opt (default — fmt for repr/exp, Ryu d2fixed for fixed), +# Format backend: zmij (default — pure-C Żmij for every mode; one unmodified +# upstream library, the CPython-proposal backend), +# fmt_opt (fmt for repr/exp, Ryu d2fixed for fixed), # fmt (plain fmt for everything, for A/B measurement), # ryu_opt (pure-C: Ryu d2s for repr, d2exp for %e/%g, d2fixed # for %f — zero C++ in the format path), -# zmij (pure-C: Żmij for every mode — one unmodified -# upstream library; the CPython-proposal backend), # all. -# Parse backend: fast_float (default), wuffs (pure-C Eisel-Lemire + HPD), -# ffc (pure-C: ffc.h, the C99 port of fast_float — -# the CPython-proposal backend), +# Parse backend: ffc (default — pure-C ffc.h, the C99 port of +# fast_float; the CPython-proposal backend), +# fast_float (the C++ original), wuffs (pure-C Eisel-Lemire + HPD), # stock (libc strtod via floatium's wrapper), all. # # For a "stock CPython" format baseline, just `floatium.uninstall()` — @@ -29,8 +29,11 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # # With "all", every backend is compiled in and the active one is chosen at # runtime via env vars FLOATIUM_FORMAT_BACKEND / FLOATIUM_PARSE_BACKEND. -set(FLOATIUM_FORMAT_BACKEND "fmt_opt" CACHE STRING "Format backend: fmt_opt, fmt, ryu_opt, zmij, all") -set(FLOATIUM_PARSE_BACKEND "fast_float" CACHE STRING "Parse backend: fast_float, wuffs, ffc, stock, all") +# The defaults changed from fmt_opt + fast_float to zmij + ffc in 0.16.0: +# the shipped wheel is now the exact pure-C configuration proposed for +# CPython, so every user runs the candidate code. +set(FLOATIUM_FORMAT_BACKEND "zmij" CACHE STRING "Format backend: fmt_opt, fmt, ryu_opt, zmij, all") +set(FLOATIUM_PARSE_BACKEND "ffc" CACHE STRING "Parse backend: fast_float, wuffs, ffc, stock, all") set_property(CACHE FLOATIUM_FORMAT_BACKEND PROPERTY STRINGS fmt_opt fmt ryu_opt zmij all) set_property(CACHE FLOATIUM_PARSE_BACKEND PROPERTY STRINGS fast_float wuffs ffc stock all) @@ -151,11 +154,11 @@ endif() # wheel but still need a single default; prefer the optimized fmt-based one. set(FLOATIUM_DEFAULT_FORMAT "${FLOATIUM_FORMAT_BACKEND}") if(FLOATIUM_DEFAULT_FORMAT STREQUAL "all") - set(FLOATIUM_DEFAULT_FORMAT "fmt_opt") + set(FLOATIUM_DEFAULT_FORMAT "zmij") endif() set(FLOATIUM_DEFAULT_PARSE "${FLOATIUM_PARSE_BACKEND}") if(FLOATIUM_DEFAULT_PARSE STREQUAL "all") - set(FLOATIUM_DEFAULT_PARSE "fast_float") + set(FLOATIUM_DEFAULT_PARSE "ffc") endif() target_compile_definitions(_ext PRIVATE diff --git a/INTERNALS.md b/INTERNALS.md index a2f52d6..f89f2ec 100644 --- a/INTERNALS.md +++ b/INTERNALS.md @@ -9,12 +9,12 @@ see the [README](README.md) first. >>> import floatium >>> floatium.info() {'patched': True, - 'format_backend': 'fmt_opt', - 'parse_backend': 'fast_float', + 'format_backend': 'zmij', + 'parse_backend': 'ffc', 'available_format_backends': 'fmt,fmt_opt,ryu_opt,zmij', 'available_parse_backends': 'fast_float,wuffs,ffc,stock', - 'default_format_backend': 'fmt_opt', - 'default_parse_backend': 'fast_float'} + 'default_format_backend': 'zmij', + 'default_parse_backend': 'ffc'} ``` Fields: @@ -29,21 +29,23 @@ the package version. ## Backend matrix -All format backends produce bit-identical output. Pick one at build -time, or build all of them and switch at install time: +All format backends produce bit-identical output. The wheel ships the +`zmij` + `ffc` pair (the CPython candidate; the defaults were `fmt_opt` ++ `fast_float` before 0.16.0). Pick one at build time, or build all of +them and switch at install time: | Format backend | Algorithm | C++? | |----------------------|------------------------------------------------------------|------| -| `fmt_opt` (default) | `{fmt}` Dragonbox (modes 0/2) + Ryu `d2fixed` (mode 3) | yes | +| `fmt_opt` | `{fmt}` Dragonbox (modes 0/2) + Ryu `d2fixed` (mode 3) | yes | | `fmt` | `{fmt}` Dragonbox + `fmt::detail::format_float` | yes | | `ryu_opt` | Ryu `d2s` (mode 0) + `d2exp` (mode 2) + `d2fixed` (mode 3) | **no** | -| `zmij` | Żmij C port for every mode (shortest + fixed/scientific, bigint above 18 digits) | **no** | +| `zmij` (default) | Żmij C port for every mode (shortest + fixed/scientific, bigint above 18 digits) | **no** | | Parse backend | Algorithm | C++? | |----------------------|------------------------------------------------------------|------| -| `fast_float` (default) | Eisel–Lemire 64-bit + bignum fallback | yes | +| `fast_float` | Eisel–Lemire 64-bit + bignum fallback (C++ original) | yes | | `wuffs` | Wuffs `parse_number_f64` — Eisel–Lemire + HPD | **no** | -| `ffc` | ffc.h, the C99 port of fast_float — same pipeline, no C++ | **no** | +| `ffc` (default) | ffc.h, the C99 port of fast_float — same pipeline, no C++ | **no** | | `stock` | libc `strtod` via floatium's wrapper | no | For a true stock-CPython baseline on the format side, call diff --git a/README.md b/README.md index 4fd57af..92f16ed 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,13 @@ [![License](https://img.shields.io/pypi/l/floatium.svg)](https://github.com/eendebakpt/floatium/blob/main/LICENSE) Experimental drop-in replacement for CPython's float formatting and parsing, -backed by the [{fmt}](https://github.com/fmtlib/fmt) and -[fast_float](https://github.com/fastfloat/fast_float) C++ libraries. +backed by [Żmij](https://github.com/vitaut/zmij) and +[ffc.h](https://github.com/kolemannix/ffc.h) (the C99 port of fast_float) — +pure C, no C++ — in the exact configuration proposed for CPython itself. After `pip install floatium`, every subsequent Python process uses -`{fmt}`'s Dragonbox for `repr(float)` / `str(float)` / -`float.__format__` and `fast_float` for `float("...")`. Existing code, +Żmij for `repr(float)` / `str(float)` / `float.__format__` and ffc.h +for `float("...")`. Existing code, existing tests, existing output — just faster. Works with an unmodified stock CPython; no interpreter rebuild required. @@ -51,33 +52,36 @@ divergences against CPython 3.15's stdlib test suite). Numbers below are from a release CPython 3.14.3 build (`python -m bench.bench_ns_per_op`, median of fastest third of samples, -lower is better) using floatium's default `fmt_opt` format backend + -`fast_float` parse backend: +lower is better) using floatium's default `zmij` format backend + +`ffc` parse backend: | Corpus | Operation | Stock (ns) | floatium (ns) | Speedup | |-----------------|-----------------|-----------:|--------------:|--------:| -| random_uniform | `repr(x)` | 285 | 89 | 3.19× | -| random_uniform | `f"{x:.4f}"` | 119 | 99 | 1.20× | -| random_uniform | `float(s)` | 121 | 47 | 2.61× | -| random_bits | `repr(x)` | 818 | 129 | 6.33× | -| random_bits | `f"{x:.4f}"` | 1,935 | 193 | 10.01× | -| random_bits | `float(s)` | 276 | 67 | 4.12× | -| financial | `repr(x)` | 170 | 73 | 2.33× | -| financial | `f"{x:.4f}"` | 143 | 97 | 1.48× | +| random_uniform | `repr(x)` | 284 | 105 | 2.71× | +| random_uniform | `f"{x:.4f}"` | 120 | 111 | 1.07× | +| random_uniform | `float(s)` | 121 | 45 | 2.69× | +| random_bits | `repr(x)` | 817 | 151 | 5.40× | +| random_bits | `f"{x:.4f}"` | 1,933 | 453 | 4.27× | +| random_bits | `float(s)` | 277 | 68 | 4.07× | +| financial | `repr(x)` | 168 | 89 | 1.89× | +| financial | `f"{x:.4f}"` | 144 | 114 | 1.26× | | financial | `float(s)` | 37 | 38 | 0.97× | -| scientific | `repr(x)` | 636 | 127 | 5.01× | -| scientific | `f"{x:.4f}"` | 1,079 | 157 | 6.87× | -| scientific | `float(s)` | 211 | 63 | 3.36× | -| integer_valued | `repr(x)` | 143 | 83 | 1.73× | -| integer_valued | `f"{x:.4f}"` | 167 | 103 | 1.63× | -| integer_valued | `float(s)` | 43 | 43 | 0.99× | +| scientific | `repr(x)` | 639 | 147 | 4.34× | +| scientific | `f"{x:.4f}"` | 1,080 | 281 | 3.84× | +| scientific | `float(s)` | 213 | 64 | 3.33× | +| integer_valued | `repr(x)` | 143 | 92 | 1.54× | +| integer_valued | `f"{x:.4f}"` | 168 | 110 | 1.53× | +| integer_valued | `float(s)` | 43 | 45 | 0.97× | **Wins are largest on hard inputs.** `random_bits` and `scientific` corpora — values whose decimal expansion stresses dtoa's big-integer -path — see 6–10× on `repr` / `f"{x:.4f}"` and 3.4–4.1× on `float(s)`. +path — see 4–5.4× on `repr` / `f"{x:.4f}"` and 3.3–4.1× on `float(s)`. The +`fmt_opt` backend is faster still on `f"{x:.4f}"` for huge magnitudes +(10× via Ryu `d2fixed`, see [BENCHMARKS.md](BENCHMARKS.md)); the default +is `zmij` because one unmodified C library is what CPython can take. On `financial` and `integer_valued`, parse stays near 1× because the inputs are short, integer-valued strings where dtoa's fast path and -fast_float's Eisel–Lemire path both finish in tens of nanoseconds — +ffc.h's Eisel–Lemire path both finish in tens of nanoseconds — there's not enough work to amortize either parser's setup cost. The table above is the default configuration. For the full @@ -203,11 +207,13 @@ See [PARITY.md](PARITY.md) for the full list. Summary: subinterpreter starts. - **`float("1_000.5")` and `float("inf")` take the fallback path.** Both parse correctly (output is bit-identical to stock) but don't benefit - from `fast_float`'s speed. + from the parser's speed. ## License - floatium wrapper code: **MIT**. +- Żmij (vendored in `third_party/zmij/`): **MIT OR BSL-1.0**. +- ffc.h (vendored in `third_party/ffc/`): **Apache-2.0 OR MIT OR BSL-1.0**. - `{fmt}` (vendored in `third_party/fmt/`): **MIT**. - `fast_float` (vendored in `third_party/fast_float/`): **Apache-2.0 OR MIT OR Boost-1.0**. diff --git a/floatium/__init__.py b/floatium/__init__.py index e9e3905..d147796 100644 --- a/floatium/__init__.py +++ b/floatium/__init__.py @@ -1,7 +1,8 @@ """floatium — drop-in replacement for CPython float formatting/parsing. -Backs float repr/str/__format__/__new__ with the {fmt} and fast_float C++ -libraries (or a pure-C combination of Żmij + ffc.h) to demonstrate the +Backs float repr/str/__format__/__new__ with the Żmij and ffc.h C libraries +(the configuration proposed for CPython; {fmt}, fast_float, Ryu and Wuffs +backends are available as alternatives) to demonstrate the performance and correctness case for a forthcoming CPython PEP. Basic usage: @@ -34,7 +35,7 @@ "__version__", ] -__version__ = "0.15.0" +__version__ = "0.16.0" def install( diff --git a/pyproject.toml b/pyproject.toml index 8977c2c..7d6e597 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ build-backend = "scikit_build_core.build" [project] name = "floatium" -version = "0.15.0" -description = "Drop-in replacement for CPython float formatting/parsing, backed by {fmt} and fast_float. Demonstrator for a forthcoming CPython PEP." +version = "0.16.0" +description = "Drop-in replacement for CPython float formatting/parsing, backed by Żmij and ffc.h (pure C). Demonstrator for a forthcoming CPython PEP." readme = "README.md" license = { text = "MIT" } authors = [{ name = "Pieter Eendebak", email = "pieter.eendebak@gmail.com" }] @@ -22,7 +22,7 @@ classifiers = [ "Programming Language :: C++", "Topic :: Software Development :: Libraries :: Python Modules", ] -keywords = ["cpython", "float", "dtoa", "fmt", "fast_float", "repr", "format"] +keywords = ["cpython", "float", "dtoa", "zmij", "ffc", "fmt", "fast_float", "repr", "format"] [project.urls] Homepage = "https://github.com/eendebakpt/floatium" @@ -45,8 +45,8 @@ wheel.packages = ["floatium"] # files take effect. [tool.scikit-build.cmake.define] -FLOATIUM_FORMAT_BACKEND = "fmt_opt" -FLOATIUM_PARSE_BACKEND = "fast_float" +FLOATIUM_FORMAT_BACKEND = "zmij" +FLOATIUM_PARSE_BACKEND = "ffc" [tool.cibuildwheel] # CPython 3.13–3.15, incl. free-threaded 3.14t / 3.15t. cibuildwheel 4.x From 866866b247702235eb5dad4b15c6c149f92047df Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 20 Oct 2026 21:06:35 +0200 Subject: [PATCH 12/13] ci: run the all-backends install step under bash on Windows PowerShell does not treat a trailing backslash as a line continuation, so the multi-line pip install ran '-C ...' as a separate command and the windows-latest all-backends job failed at install time. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0129427..02419cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,6 +67,9 @@ jobs: python-version: "3.14" - name: Install with every backend compiled in + # bash on every OS: the default Windows shell is PowerShell, where + # the backslash continuations below are not line continuations. + shell: bash run: | python -m pip install --upgrade pip python -m pip install ".[test]" \ From c1d54479afc819f2f8840f9c5cc4b9d2d49a91cc Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 20 Oct 2026 21:10:24 +0200 Subject: [PATCH 13/13] wuffs: hoist out of function bodies so the header builds with MSVC The extract included inside multiply_u64 and count_leading_zeroes_u64 for the MSVC branches; MSVC rejects a system header at block scope (C2598 and a cascade of errors in xmmintrin.h), which broke the new windows-latest all-backends CI job. Documented as local modification 8 and re-applied by tools/sync_wuffs.sh. Co-Authored-By: Claude Fable 5.1 --- third_party/wuffs/README.vendor | 4 ++++ third_party/wuffs/floatconv_wuffs.h | 7 +++++-- tools/sync_wuffs.sh | 16 +++++++++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/third_party/wuffs/README.vendor b/third_party/wuffs/README.vendor index f1ba990..362d3c2 100644 --- a/third_party/wuffs/README.vendor +++ b/third_party/wuffs/README.vendor @@ -42,6 +42,10 @@ extractions, not algorithmic changes. From the file header: Plus one floatium-local bug fix, re-applied by tools/sync_wuffs.sh: + 8. The include for the MSVC branches of multiply_u64 and + count_leading_zeroes_u64 sits at the top of the header; the extract + had it inside the function bodies, which MSVC rejects (system + headers cannot be included at block scope). 7. high_prec_dec parse: integer-part digits beyond the 800-digit HPD cap now still advance the decimal point (`dp++`). Upstream (also in wuffs main as of 2026-10, 0.4.0-alpha.11) drops them, so e.g. diff --git a/third_party/wuffs/floatconv_wuffs.h b/third_party/wuffs/floatconv_wuffs.h index 3c9e421..6f3ea40 100644 --- a/third_party/wuffs/floatconv_wuffs.h +++ b/third_party/wuffs/floatconv_wuffs.h @@ -31,6 +31,11 @@ #include #include #include /* memcpy */ +#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) +#include /* _umul128, _BitScanReverse64 (floatium: hoisted out of + the function bodies below; a system header included + inside a function does not compile with MSVC) */ +#endif /* ---- Minimal Wuffs type definitions ---- */ @@ -79,7 +84,6 @@ wuffs_base__multiply_u64(uint64_t x, uint64_t y) { wuffs_base__multiply_u64__output o; o.lo = (uint64_t)z; o.hi = (uint64_t)(z >> 64); return o; #elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) -#include wuffs_base__multiply_u64__output o; o.lo = _umul128(x, y, &o.hi); return o; #else @@ -97,7 +101,6 @@ wuffs_base__count_leading_zeroes_u64(uint64_t u) { return u ? (uint32_t)__builtin_clzl(u) : 64u; } #elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) -#include static inline uint32_t wuffs_base__count_leading_zeroes_u64(uint64_t u) { unsigned long idx; diff --git a/tools/sync_wuffs.sh b/tools/sync_wuffs.sh index 524c344..1ac9d20 100755 --- a/tools/sync_wuffs.sh +++ b/tools/sync_wuffs.sh @@ -75,7 +75,21 @@ new = ( if s.count(old) != 1: sys.exit("sync_wuffs: HPD integer-digit hunk not found exactly once; " "upstream may have changed (or fixed) it — review by hand") -p.write_text(s.replace(old, new)) +s = s.replace(old, new) +# README.vendor item 8: MSVC cannot include at block scope, so +# hoist the two in-function includes of the extract to the top. +infn = "#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))\n#include \n" +if s.count(infn) != 2: + sys.exit("sync_wuffs: expected two in-function includes") +s = s.replace(infn, "#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))\n") +top = "#include /* memcpy */\n" +if s.count(top) != 1: + sys.exit("sync_wuffs: include block not found") +s = s.replace(top, top + "#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))\n" + "#include /* _umul128, _BitScanReverse64 (floatium: hoisted out of\n" + " the function bodies below; a system header included\n" + " inside a function does not compile with MSVC) */\n#endif\n") +p.write_text(s) PY # Adapter (pystrtod_wuffs.h): apply the only local mod — rewrite