alice-det-math
Cross-platform bit-exact f32 / f64 transcendentals — sin, cos, exp,
ln, atan2, asin, acos, tan, tanh, powf, cbrt, hypot, … — built
from IEEE 754 basic operations only, so every function is a pure function of
its input bits on x86_64, aarch64, wasm32 and every other IEEE target.
no_std, no alloc, no unsafe.
The platform libm is not: sin(x) differs in the last ulp between macOS,
glibc, MSVC and wasm. A lockstep simulation that calls it diverges across
peers; a signed-distance field evaluated on two machines disagrees about which
side of a surface a point is on. This crate is the transcendental layer of
alice-physics (128-bit deterministic
physics) and alice-sdf (SDF geometry),
extracted so both — and anything else — evaluate the same law.
It is not a faster or more accurate libm: the functions are within 1–2 ulp
of correctly rounded, not correctly rounded, and they keep a fixed operation
order (no mul_add) rather than the fastest one for each target. Use it where two machines must produce the same bits; where
only the value matters, the platform libm is the better choice.
License: MIT OR Apache-2.0
Contents
- Installation
- Example
- Features
SEMANTICS_ID- Policy for consumers
- Accuracy and guarantee scope
- no_std
- Minimum supported Rust version
- Building and testing
- Related Projects
- License
Installation
Example
use ;
let = sin_cos;
assert_eq!; // the same bits on every platform
assert_eq!;
assert_eq!;
let a = atan2; // π, fdlibm special cases
assert_eq!;
// f64 over the whole range: 1e22 needs the Payne–Hanek reduction
let = sin_cos64;
assert_eq!; // -0.8522008497671888
assert_eq!; // 0.523214785395139
With simd:
use simd;
use f32x8;
let x = new;
let = sin_cos;
for in x.to_array.into_iter.enumerate
Features
- Scalar
f32kernels (Cephessinf/cosf/expf, musllogf) andf64kernels (fdlibmatan/atan2/asin/acos/exp/log), ≤ 1–2 ulp of correctly rounded over the documented domains sin64/cos64/sin_cos64:f64sine and cosine over every finite argument — muslsin.c/cos.cwith the Payne–Hanek reduction for|x| ≥ 2^20·π/2, ≤ 1 ulp of correctly rounded (measured against 6234 values computed withmpmathat 2400 bits, not against a libm);sin_cos64reduces once and is bit-identical to the two separate callstan64:f64tangent over every finite argument — fdlibmk_tan.con
the same reduction, ≤ 1 ulp of correctly rounded (measured against 6310
values from mpmath at 2400 bits). It is not compared with a platform libm
at all: one is ~1.0e5 ulp off in the Payne–Hanek range while another is
within 2 ulp, so a bound on that difference would depend on the machine
log2/log10/log2_64/log10_64: base-2 and base-10
logarithms on the exponent split of fdlibm e_log10.c, which keeps the
mantissa below 1 for a negative exponent so nothing cancels around x = 1.
A power of two returns its exponent exactly, down to the last subnormal;
log2_64 is ≤ 2 ulp and log10_64 ≤ 1 ulp, and the f32 entry points are
the f64 kernels rounded once (measured max 0 ulp from correctly rounded)
- SIMD (
simdfeature):wide::f32x8versions ofsin/cos/sin_cos/exp/ln/round/sqrt, lane-for-lane bit-identical to the scalar functions — same constants, same operation order, nomul_add metric:lerp/clamp/smoothstepwith one canonical operation
order, the three 3D norms, and MetricWeights — a metric built as a
non-negative combination of ‖·‖₁ / ‖·‖₂ / ‖·‖∞, carrying exact closed
forms for its Lipschitz constant and for how far a ball of radius r
reaches in Euclidean space
round/sqrtwithout libm: musl'sx + 2^23 − 2^23rounding and a correctly rounded software square root, so the crate works on bare-metal targets and returns the same bits there- Canonical NaN: a NaN input never propagates through arithmetic (whose payload / sign rules are platform-specific); every function returns the canonical NaN or passes the input bits through unchanged
- Pinned:
tests/golden.rsholds a SHA-256 of every function over a fixed input grid; CI reproduces it on macOS ARM / Intel, Linux x86 / ARM, Windows, wasm32 (wasmtime), with the SSE2-only SIMD fallback, with AVX2 + FMA forced on, and withno_std(softwaresqrt) SEMANTICS_ID: one 32-byte constant that identifies this crate's
numeric behaviour (see below)
SEMANTICS_ID
SEMANTICS_ID is a 32-byte constant that identifies *how this crate evaluates
arithmetic*. It changes whenever any function here would return different bits
for the same input, and only then: it is the SHA-256 of the per-function bit
pins in tests/golden.rs, folded in ascending order of function name, each as
the name's length in four big-endian bytes, then the name, then its 32-byte
pin. No platform libm, no timing and no environment enters it, so it is the
same value on every target the pins reproduce on.
It is for callers that build an identifier out of a formula and its parameters: mixing this in makes the identifier cover the arithmetic as well, so two runs that agree on formula, parameters and this value computed the same bits — and if the value differs, they did not, however well everything else matches.
Two tests keep it honest. One recomputes it from the pins and fails if the
constant has drifted; the other reads the crate's public numeric functions out
of src/lib.rs and fails if any of them has no pin, since a function outside
the table could change behaviour without moving the identifier.
It moved in 0.4.0, when a corrected atan64 coefficient changed that
function's output bits — which is the intended behaviour: a consumer that
mixes this value into its own identifiers sees the arithmetic change instead
of silently inheriting it.
Policy for consumers
Add the platform libm methods to clippy.toml disallowed-methods and run
clippy with -D warnings, so a stray x.sin() on a float is a red gate:
= [
{ = "f32::sin", = "platform libm; use alice_det_math::sin" },
{ = "f32::cos", = "platform libm; use alice_det_math::cos" },
{ = "f64::sin", = "platform libm; use alice_det_math::sin64" },
{ = "f64::cos", = "platform libm; use alice_det_math::cos64" },
{ = "f32::exp", = "platform libm; use alice_det_math::exp" },
{ = "f32::ln", = "platform libm; use alice_det_math::ln" },
{ = "f32::atan2", = "platform libm; use alice_det_math::atan2" },
{ = "f32::mul_add", = "fused on FMA targets only; write a * b + c" },
]
Accuracy and guarantee scope
See the crate documentation (cargo doc --open) for the per-function error
table and the exact scope of the bit-exactness guarantee (IEEE 754 basic
operations on the target; x87 without SSE2 and fast-math builds are outside it).
The reference values for every f64 function whose bound is asserted are
committed under tests/data/, so no f64 bound depends on the platform
libm (atan64 is the one exception). They are regenerated with
no_std
The crate is #![no_std] without the default std feature and needs no
alloc. sqrt is then a correctly rounded software square root that returns
the same bits as the hardware instruction; CI builds the library for
thumbv7em-none-eabihf (with and without simd) and runs the golden hashes
with the software path.
Minimum supported Rust version
Rust 1.85 (rust-version in Cargo.toml), checked in CI for the library with
default features, with std,simd, and as a no_std rlib for
thumbv7em-none-eabihf. Development and CI use the toolchain pinned in
rust-toolchain.toml.
Building and testing
Related Projects
Crates that evaluate their laws through this one, so two machines agree bit for
bit on the result. This crate is the joint of the ALICE core: a signed-distance
field (ALICE-SDF) and the bodies
moving through it (ALICE-Physics)
only agree on a surface if they compute sin the same way, and a law verifier
(ALICE-LOL) can only prove something
about a field that is reproducible in the first place.
Keep the version unified across the resolved graph. Two versions of this crate in one dependency tree means two implementations of the same function, and the guarantee is gone. Check with
cargo tree -i alice-det-math.
| Project | Why it needs bit-exact transcendentals | Links |
|---|---|---|
| ALICE-SDF | A signed-distance field evaluated on two machines must agree which side of a surface a point is on | crates.io · docs.rs · GitHub |
| ALICE-Physics | Lockstep / rollback simulation diverges across peers the moment one sin differs by an ulp |
crates.io · docs.rs · GitHub |
| ALICE-LOL | Its SDF law checks run through ALICE-SDF, which evaluates through this crate; from its next release, the research_law module also evaluates multi-variable research laws (exp, ln, sqrt, powers, sin, cos) through the f64 functions here, so a law re-computed on another machine gives the same bits |
crates.io · docs.rs · GitHub |
ALICE-Zip does not depend on this
crate: its generators currently use libm.
License
MIT OR Apache-2.0. Coefficients and algorithms are from Cephes (Stephen L. Moshier), fdlibm (Sun Microsystems) and musl; see the source for attribution.