Skip to main content

Crate alice_det_math

Crate alice_det_math 

Source
Expand description

Deterministic f32 / f64 transcendentals (cross-platform bit-exact).

IEEE 754 guarantees that +, -, *, / and sqrt produce the same bits on every platform Rust targets (SSE2+, aarch64, wasm32). It does not cover sin, exp, ln, powf, cbrt, hypot, … — those come from the platform libm and differ in the last ulp between macOS, glibc, MSVC and wasm. A lockstep simulation that calls them diverges across peers; a signed-distance field evaluated on two machines disagrees about which side of a surface a point is on.

Every function here is built from the guaranteed operations only — integer range reduction (trunc(t + (t < 0 ? -0.5 : 0.5)), three operations), fixed-degree polynomials evaluated in a fixed order, and bit-level exponent construction — so the result is a pure function of the input bits on every target. mul_add is not used anywhere: it fuses into one rounding on FMA hardware and stays two roundings elsewhere, which would break the guarantee.

The crate is no_std (no alloc either). round and sqrt, which core does not provide, are in ops; with the std feature sqrt is the hardware instruction, without it a correctly rounded software one — both return the same bits.

With the simd feature, the simd module provides wide::f32x8 versions that are lane-for-lane bit-identical to the scalar functions.

use alice_det_math::{atan2, exp, sin_cos, sin_cos64};

let (s, c) = sin_cos(1.0_f32);
assert_eq!(s.to_bits(), 0x3f57_6aa5); // the same bits on every platform
assert_eq!(c.to_bits(), 0x3f0a_5140);
assert_eq!(exp(1.0_f32).to_bits(), 0x402d_f854);
let a = atan2(0.0_f32, -1.0); // π, fdlibm special cases
assert_eq!(a, core::f32::consts::PI);

// f64 over the whole range: 1e22 needs the Payne–Hanek reduction
let (s, c) = sin_cos64(1.0e22);
assert_eq!(s.to_bits(), 0xbfeb_453a_b76b_f397); // -0.8522008497671888
assert_eq!(c.to_bits(), 0x3fe0_be2c_ef01_c8f4); // 0.523214785395139

§Accuracy

Measured over dense sweeps (tests/accuracy.rs). f32 functions are compared with a correctly rounded reference (f64 libm rounded once to f32), sin64 / cos64 with an independent correctly rounded reference, the other f64 functions with the platform f64 libm — which itself varies by ≤ 1 ulp between macOS / glibc / MSVC, so those bounds carry that slack:

functiondomain measuredmax error
sin / cos / sin_cos|x| ≤ 100≤ 2 ulp (Cephes single-precision coefficients), including every input within 64 ulp of a zero k·π/2; measured max 2 out to |x| ≤ 8192 (tests/accuracy_trig_zeros.rs)
exp[-87, 88]≤ 2 ulp
ln[1e-30, 1e30]≤ 1 ulp (musl logf algorithm)
log2 / log10[1e-30, 1e30]≤ 1 ulp, measured max 0 (log2_64 / log10_64 rounded once; powers of two are exact)
cbrt[1e-30, 1e30]≤ 1 ulp (bit-hack seed + 3 Newton steps)
hypotfinite≤ 1 ulp of sqrt(x² + y²)
powfx ∈ [1e-3, 1e3], |y| ≤ 8≤ 1 ulp (evaluated in f64, rounded once)
atan / atan2|x| ≤ 1e4, all quadrants≤ 1 ulp (fdlibm s_atanf / e_atan2f, single precision)
asin / acos[-1, 1]≤ 1 ulp (fdlibm in f64, rounded once)
tan|x| ≤ 100≤ 1 ulp (fdlibm k_sin/k_cos in f64)
tanhfinite≤ 1 ulp (exp64 based, x below 2^-14)
sin64 / cos64 / sin_cos64every finite f64 (Cody–Waite below 2^20·π/2, Payne–Hanek above)≤ 1 ulp of correctly rounded, measured max 1 over 6234 points of an independent 2400-bit reference (musl sin.c / cos.c)
tan64every finite f64 (same reduction as sin64)≤ 1 ulp of correctly rounded, measured max 1 over 6310 points of an independent 2400-bit reference (fdlibm k_tan.c)
atan64 / atan2_64every finite f64, all quadrants≤ 1 ulp of correctly rounded, measured max 1 over 1758 points of an independent 2400-bit reference covering every branch of the reduction (fdlibm s_atan.c / e_atan2.c)
exp64 / ln64[-745, 710] / [1e-300, 1e300]≤ 2 ulp, measured max 1 against an independent 2400-bit reference (fdlibm algorithms)
log2_64 / log10_64[1e-300, 1e300]≤ 2 ulp of the platform libm, measured max 2 / 1 (ln64 converted to the base; powers of two are exact for log2_64)
powf64x ∈ [1e-3, 1e3], |y| ≤ 8≤ 16 ulp measured 13 (exp64(y·ln64 x) with double-double argument) ⚠️ outside this domain the result can be wrong, not just less accurate — see powf64
round / round64 / sqrt / sqrt64allexact (bit-identical to f32::round / f32::sqrt, except that every NaN is the canonical one)

The reduction subtracts k·π/2 in four f32 pieces, the third formed exactly with Dekker’s two-product (no fused multiply-add, so every target computes the same bits). The first two products are exact only for |k| < 2¹³, so large arguments to sin / cos (|x| > 2¹³) are still deterministic but lose accuracy, exactly as libm’s own sinf does without Payne–Hanek. Within the domain the remaining error is the Cephes polynomial’s (≤ 2 ulp). sin64 / cos64 reduce every finite argument exactly (Payne–Hanek with 2/π to 1584 bits), so their bound holds up to f64::MAX; the reference values are in tests/data/sin_cos64_reference.txt, generated by scripts/gen_sin_cos64_reference.py (mpmath, not a libm). Every other f64 function whose bound is asserted has its own table under tests/data/, from the matching scripts/gen_*_reference.py, so no f64 bound depends on a platform libm. tan64 is deliberately not compared with a platform libm at all: one disagrees with the correctly rounded value by ~1.0e5 ulp in the Payne–Hanek range, so a bound on that difference would depend on the machine running it.

§Guarantee scope

Bit-exactness relies on the target honouring IEEE 754 for the basic operations: x86_64 (SSE2), aarch64, wasm32 and every other Rust tier-1/2 target qualify. 32-bit x86 built for x87 (i586) without SSE2, or any build with fast-math style flags, is outside the guarantee. tests/golden.rs pins a SHA-256 of every function’s output over a fixed input grid; CI runs it on macOS (ARM / Intel), Linux (x86 / ARM), Windows and wasm32, and with the SIMD fallback paths forced.

§Policy for consumers

List the f32 / f64 libm methods under clippy.toml disallowed-methods and run clippy with -D warnings, so a stray x.sin() on a float is a red gate; call these functions instead.

Re-exports§

pub use semantics::check_semantics;
pub use semantics::SemanticsCheck;
pub use semantics::PREVIOUS_SEMANTICS_IDS;
pub use double::acos64;
pub use double::asin64;
pub use double::atan2_64;
pub use double::atan64;
pub use double::cos64;
pub use double::exp64;
pub use double::ln64;
pub use double::log10_64;
pub use double::log2_64;
pub use double::powf64;
pub use double::sin64;
pub use double::sin_cos64;
pub use double::tan64;
pub use ops::round;
pub use ops::round64;
pub use ops::sqrt;
pub use ops::sqrt64;
pub use single::acos;
pub use single::asin;
pub use single::atan;
pub use single::atan2;
pub use single::cbrt;
pub use single::cos;
pub use single::exp;
pub use single::hypot;
pub use single::ln;
pub use single::log10;
pub use single::log2;
pub use single::powf;
pub use single::powi;
pub use single::sin;
pub use single::sin_cos;
pub use single::tan;
pub use single::tanh;

Modules§

consts
The f32 kernel constants (Cephes / musl), for code generators that must emit the same law elsewhere (a JIT, a shader) and be checked against these functions bit for bit.
double
f64 kernels ported from fdlibm.
metric
Deterministic blending and 3D norms — the kernels a distance field needs when the metric itself becomes a value.
ops
Deterministic round / sqrt built from IEEE 754 basic operations.
semantics
Reading arithmetic identifiers back from stored data.
simdsimd
f32x8 versions of the f32 kernels (feature simd).
single
f32 kernels: Cephes sinf / cosf / expf, musl logf, plus powf / powi / cbrt / hypot, and the f32 entry points of the double-precision fdlibm kernels in crate::double.

Constants§

SEMANTICS_ID
Identifies the numeric behaviour of this crate: it changes whenever any function here would return different bits for the same input, and otherwise only when the set of functions it covers grows.