Working notes. Audience: us. Builds on understanding-fpsan.md.
FPSan encodes a float by a scrambling bijection
The idea here is the opposite. Make the encoding itself a ring homomorphism,
so the model computes the true value of the expression, reduced into a finite
field. Instead of the free algebra on the inputs, we get the actual element of
That single change — free model
Finite binary floats are dyadic rationals: a finite float32 is
the localization of
forced by
It is cleaner to name the largest domain. Let $\mathbb{Z}{(p)}$ be the localization of $\mathbb{Z}$ at the prime $p$ — the fractions $a/b$ with $p\nmid b$. It is a local ring with maximal ideal $p\mathbb{Z}{(p)}$ and residue field $\mathbb{Z}{(p)}/p\mathbb{Z}{(p)} \cong \mathbb{F}_p$. Then
and
The model. Carry, for each value, its residue
We restrict attention to compact 32-bit encodings throughout. Two variants
are developed in §8: a single prime algebraic_fpsan_generic.hpp +
algebraic_demo.cpp.
This deserves to be made sharp, because it decides everything and it cuts both ways.
-
FPSan = free. Inputs are scrambled into algebraically-independent
generators; the model is the free (exponential) ring on them. Two expressions
match iff they are provably equal from the axioms alone. Consequences:
$2{+}2$ does not match$4$ ;$x{\cdot}(1/x)$ does not match$1$ in general;$a{+}a$ does not match$2a$ . The model is blind to the actual numerical values of the leaves. -
$\varphi_p$ = value. Leaves are their true residues; the model is the actual element of$\mathbb{Q}$ . Two expressions match iff they have the same exact value (mod$p$ ). So$2{+}2 = 4$ ,$x/x = 1$ ,$a{+}a = 2a$ ,$(a{+}b)^2 = a^2{+}2ab{+}b^2$ , constant-folding, common-subexpression — all match.
Neither dominates universally:
- On the algebraic fragment (polynomials/rational functions of the inputs),
the true values have many relations (
$2{+}2=4$ really is$4$ ), so the value model is the faithful one and the free model over-discriminates (it would flag value-preserving rewrites as differences). - On transcendentals the situation flips — see §6 — because transcendental values are algebraically independent, so freeness becomes the faithful choice there.
So the honest framing of this whole note:
We give up injectivity (
(a) Collision with zero — the one that would hurt — does not happen.
(b) Leaf collisions (two distinct floats, same residue) are common but
mostly harmless. With
(c) Circuit-level false matches are fingerprinting at a random prime.
Two algebraically different expressions on shared inputs collide iff
This is exactly Freivalds / Schwartz–Zippel / Rabin-fingerprint territory: checking an identity by evaluating at a random point (here, reducing mod a random prime). Consequences:
- Per comparison, false-match
$\approx 2^{-24}$ to$2^{-32}$ with a 32-bit modulus. Staying compact (32-bit), the lever for more margin is repetition:$k$ runs with$k$ different primes multiply the rates (independent fingerprints / CRT). Two runs$\approx 2^{-48}$ –$2^{-64}$. The sanitizer is a test tool, so spending time (extra runs) rather than space (a wider word) is the right trade — and it keeps the instrumented program's footprint unchanged (§8). - Unlike FPSan's fixed scramble — which has a fixed set of colliding pairs that
collide on every run — randomizing
$p$ makes collisions non-repeatable and adversarially robust: there is no bad pair that always fools you.
The prototype confirms the rate: over 300k distinct random floats, observed leaf
collisions track
Verdict: the loss of injectivity is essentially cosmetic for the intended use. The substantive cost is a slightly higher per-prime collision rate, bought back by repeating with another prime.
FPSan scrambles to make leaves free. Can we keep
What plays the role of the hash seed is instead the choice of
What we do lose by not scrambling: diffusion as a debugging aid (FPSan's
payloads look random, so a single wrong bit anywhere produces a wildly different
payload that is easy to eyeball).
This is where
-
$+,-,\times$ : exact homomorphic images; every ring identity holds for free. -
$\div$ : when the modulus is prime (variant 1b),$\mathbb{F}_p$ is a field, so division is total except by residue$0$ , and$x/x = 1$ holds always (any$x$ with nonzero residue — i.e. any nonzero float, by §3a). Compare FPSan, whose$\mathbb{Z}/2^{w}$ has zero-divisors and only a parity-preserving involution for "inverse":$x/x=1$ there holds only for odd payloads. The field is a clean win. Division by residue$0$ occurs iff dividing by a genuine$\pm 0.0$ → honest infinity (§7). (The composite CRT variant gives this up for a small zero-divisor rate — §8.) -
Casts / mixed precision — and a real tension (corrected). If a single
modulus
$n$ is shared across all widths, a value-preserving cast doesn't change the residue, so casts are the identity and mixed precision is unified. But that needs$n$ large enough for the widest type ($n>2^{53}$ forf64) — a payload as wide as the widest type, which is incompatible with the compact, per-width encodings of §8 (each width$w$ has its own$n_w<2^w$ ). With per-width moduli, casts are not value-faithful: wideningf16$\to$ f32would need$\varphi_{n_{32}}(v)$ , but the 16-bit residue carries only 16 bits about$v$ and$\varphi_{n_{16}}$ is not injective, so$v$ can't be recovered (an information barrier, not a cleverness gap). So a compact, footprint-faithful sanitizer treats casts as a defined, non-faithful convention (Inf/NaN map across, a finite residue reduces mod the destination$n$ ; identity at same width) — exactly like FPSan's resize. The "casts are the identity" property is real only in the one-modulus regime, which trades away compactness; §8 keeps compactness and gives this up. (Only lossy narrowing additionally rounds, which §9 abstracts away.) - Subnormals: also dyadic, handled by the same map with no special case (FPSan punts on them).
What is lost, and it is fundamental: order, magnitude, abs,
min/max, comparisons. max(x₁,…,xₙ) is
simply not representable (it needs the total order); FPSan "solves" min/max
by imposing an order on the scrambled payload — associative and
reassociation-invariant, but value-infaithful (it may select the wrong
element). For us, min/max/abs/comparisons are out of scope: they need a
side channel (carry the actual float, or tag).
Cost. Mod-$n$ arithmetic is costlier than FPSan's free mod-$2^{w}$
wraparound (a reduction per op). With a 32-bit modulus the reduction is a single
multiply-high (Barrett/Montgomery); a pseudo-Mersenne
exp, log, sin, cos, sqrt, … are not rational functions, so the homomorphism
says nothing directly. The question is whether we can build an exp with the
right identity
As a function of the mod-$p$ residue, no. Such an exp would be a
homomorphism exp is just a tagged hash token, and
the prototype duly finds the law holding
As a function of a second, mod-$d$ residue, yes. The value-level map
CRT keeps this compact (variant CRT). Carrying exp whose image is only the order-$d$ subgroup, so exp results collide at
log is the dual of exp and works on the same channel — the earlier
"forced trivial by zero / not a function of the residue" claim was wrong (it
holds only for a log defined on the whole ring including exp
embeds log embeds it into the additive order-$d$ subgroup exp: a discrete logarithm (cheap for the small
exp2/log2 ride the same channel by a base change. The base family
exp2/log2 are honored exactly in the
Exp/Trig variants. The inter-base constant is rcpLog2 magic constant plays): each base keeps its own homomorphism and exp2 but leaves log2 an opaque token.
sin/cos need a genuine order-$d$ rotation, which requires the circle
group's order to be divisible by exp+log too (still
sqrt, erf, tanh, … have no usable identity and stay hash tokens — and
that is correct, not a fallback: by Lindemann–Weierstrass / Schanuel,
transcendental values at distinct algebraic arguments are algebraically
independent, so a fresh free generator per call is the faithful model.
| fragment | true-value structure | model here |
|---|---|---|
| algebraic ( |
richly related ( |
value: |
|
structured: |
||
|
structured: |
||
| base-$b$ analogues of |
structured: base change |
|
| angle-addition |
structured: |
|
| algebraically independent | free: hash token |
Finite floats are in
- finite nonzero
$\mapsto$ its residue in$\mathbb{F}_p^\times$ ;$\pm 0.0 \mapsto 0$ ;$\pm\infty \mapsto \infty$ ; NaN$\mapsto$ NaN. -
$\mathbb{P}^1(\mathbb{F}_p)$ gives$x/0=\infty$ for$x\neq 0$ for free, and the Möbius/IEEE-shaped rules$\infty+\text{finite}=\infty$ ,$\infty\cdot x=\infty$ ($x\neq0$ ). NaN is absorbing, and seeds the indeterminate forms$\infty+\infty,\ \infty-\infty,\ 0\cdot\infty,\ 0/0,\ \infty/\infty \mapsto \mathrm{NaN}$ . With unsigned$\infty$ , even$\infty+\infty$ is indeterminate — IEEE resolves it via the sign we dropped — so the model emits NaN slightly more freely than IEEE, but only at the already-non-finite fringe.
The §3a fact (
Not modeled (out of scope — these compact variants carry no sign bits):
-
Signed zero / signed infinity.
$+0.0,-0.0\mapsto 0$ and$\pm\infty\mapsto$ one$\infty$ ;$\mathbb{F}_p$ has one zero,$\mathbb{P}^1$ one pole. (Negation$\varphi_n(-v)=-\varphi_n(v)$ is exact on finite values — only the$\pm0$ and$\pm\infty$ poles merge.) -
(CRT variant) division by a nonzero zero-divisor poisons to NaN, not
$\infty$ — a units issue (§8), separate from the honest$1/0=\infty$ .
Everything above instantiates as one of three 32-bit designs. All carry a single residue plus the projective+NaN extension of §7; they differ only in the modulus.
Variant 1b — single prime. exp and all transcendentals are hash
tokens.
Variant CRT — composite exp and log homomorphisms (
-
zero-divisors at rate
$\sim 1/p+1/d$ : division by one is undefined and poisons to NaN ($x/x\ne1$ there). Observed$19/300\mathrm{k}$ . -
exp/log collisions at
$\sim 1/d$ : their image is only the order-$d$ subgroup. Observed: 300k inputs →$\approx d$ distinct exp outputs. - a slightly smaller algebraic modulus → marginally more leaf collisions.
Prefer
Variant Trig — composite exp, log, and sin/cos (§6) — all on the same
sin/cos-heavy code.
| 1b (prime) | CRT ( |
Trig ( |
|
|---|---|---|---|
| modulus | |||
| algebra | field; |
product of fields; zero-div |
same as CRT |
exp / log
|
✗ (tokens) | ✓ | ✓ |
sin / cos
|
✗ (tokens) | ✗ (tokens) | ✓ (order-$d$ rotation) |
| exp/log image | — | order |
order |
Both share the hybrid transcendental scheme: carry log, sqrt, … — and all transcendentals
in 1b). Faithful by §6.
A prototype of both — algebraic_fpsan_generic.hpp
and algebraic_demo.cpp — passes the ring-homomorphism,
ring-law, Inf/NaN, and (CRT) exp-homomorphism checks and reports the
collision / zero-divisor / exp-image statistics quoted above.
-
exp/log— resolved.$\exp(a{+}b)=\exp(a)\exp(b)$ holds via$g^{v\bmod d}$ (the worry that the prime field forbids it was about exp as a function of the mod-$p$ residue; the mod-$d$ residue, folded in by CRT, delivers it).logis its dual, into the additive order-$d$ subgroup (§6). Extra margin, if needed, comes from repeating with different prime pairs (time, not space — §3). -
sin/cos— resolved, in a dedicated variant. A genuine order-$d$ rotation needs$p\equiv1\pmod4$ , so it lives in the Trig variant ($p=4d+1$ ), carryingexp+log+sin+cosat a$\sim!\sqrt2$ smaller$d$ (§6, §8). -
Order /
min/max/abs/ comparisons — open, and fundamental.$\mathbb{F}_p$ is unordered; no sign or exponent trick recovers a total order. This is the real limitation of the approach, shared with FPSan (which fakes it with payload-order, sacrificing value-faithfulness). A side channel (the actual float, or a tag) is the only route. - Rounding — out of scope by design (§9).
Both systems carry exact model values and never round, so both answer "are
these the same up to exact algebra?" and are deliberately invariant to
reassociation and to rounding-order. The sanitizer flags rewrites that change
the exact value, not ones that merely reround. This is a feature (it is what
lets a reduction in two orders compare equal), and it is the same boundary FPSan
draws.
A corollary worth stating, since it surprises: overflow to 1/(sum of huge values) is an ordinary finite inverse here, where IEEE would
overflow to
A property-by-property comparison of the four shipping semantics. Any
FPSan-style sanitizer replaces floating-point ops with a deterministic,
reassociation-invariant fingerprint: two runs that should agree (reference vs.
optimized, eager vs. fused) get identical fingerprints, and a mismatch pinpoints
where the numerics diverged. Reassociation-invariance is just associativity plus
commutativity (rows 6–7); the variants then differ in which further identities
the fingerprint preserves. The FPSanLikeTriton column is read from Triton's
FpSanitizer.cpp; the others from this prototype.
| # | property / behavior | FPSanLikeTriton |
FPSanAlgebraic |
FPSanAlgebraicExponentials |
FPSanAlgebraicTrigonometry |
|---|---|---|---|---|---|
| — model — | |||||
| 1 | leaf encoding | scramble bijection of the IEEE bits | residue φₙ(value) | residue φₙ(value) | residue φₙ(value) |
| 2 | ring | Z/2ʷ (w = bit width) | 𝔽ₚ, p prime (a field) | Z/(p·d), p = 2d+1 | Z/(p·d), p = 4d+1 |
| 3 | deterministic, platform-independent | ✅ | ✅ | ✅ | ✅ |
| — ring identities — | |||||
| 4 | 0 + x == x |
✅ ① | ✅ | ✅ | ✅ |
| 5 | 1 * x == x |
✅ ① | ✅ | ✅ | ✅ |
| 6 | commutativity, associativity of +, * |
✅ | ✅ | ✅ | ✅ |
| 7 | distributivity a*(b+c) == a*b + a*c |
✅ | ✅ | ✅ | ✅ |
| 8 | x - x == 0, x + (-x) == 0 |
✅ | ✅ | ✅ | ✅ |
| — exact value relations (φₙ is a ring homomorphism) — | |||||
| 9 | any rational-function identity valid for the exact (un-rounded) operand values holds of the fingerprints, within one width | ❌ | ✅ | ✅ | ✅ |
| 10 | …e.g. constant folding: 2+2 == 4, 2*3 == 6, 0.5+0.5 == 1 |
❌ | ✅ | ✅ | ✅ |
| 11 | …e.g. symbolic: x+x == 2*x, (x+1)*(x-1) == x*x - 1 |
❌ ② | ✅ | ✅ | ✅ |
| — division & field structure — | |||||
| 12 | x / x == 1 for every x ≠ 0 |
🟡 ③ | ✅ | 🟡 ④ | 🟡 ④ |
| 13 | (a / b) * b == a (b a unit) |
🟡 ③ | ✅ | 🟡 ④ | 🟡 ④ |
| 14 | no zero-divisors (a*b==0 ⟹ a==0 ∨ b==0) |
❌ | ✅ | ❌ ⑤ | ❌ ⑤ |
| — transcendental / special functions — | |||||
| 15 | exp(a+b) == exp(a)*exp(b) |
✅ | ❌ | ✅ | ✅ |
| 16 | exp2(a+b) == exp2(a)*exp2(b) |
✅ | ❌ | ✅ | ✅ |
| 17 | log(x*y) == log(x)+log(y) |
❌ ⑥ | ❌ | ✅ | ✅ |
| 18 | log2(x*y) == log2(x)+log2(y) |
❌ ⑥ | ❌ | ✅ | ✅ |
| 19 | exp(log(exp v)) == exp v (log inverts exp) |
❌ ⑥ | ❌ | ✅ | ✅ |
| 20 | cos(a+b) == cos a*cos b - sin a*sin b |
✅ ⑦ | ❌ | ❌ | ✅ |
| 21 | cos²x + sin²x == 1 |
✅ ⑦ | ❌ | ❌ | ✅ |
| 22 | how exp/exp2 realized |
✅ modular exp cˣ in Z/2ʷ | 🟡 token ⑩ | ✅ gˣ ᵐᵒᵈ ᵈ | ✅ gˣ ᵐᵒᵈ ᵈ |
| 23 | how log/log2 realized |
🟡 token | 🟡 token | ✅ discrete log in ⟨g⟩ | ✅ discrete log in ⟨g⟩ |
| 24 | how cos/sin realized |
✅ (3,4,5) rotor in Z/2ʷ | 🟡 token | 🟡 token | ✅ order-d rotor in (Z/n)[i] |
| 25 | sqrt, rsqrt, erf, floor, ceil, … |
🟡 token | 🟡 token | 🟡 token | 🟡 token |
| 26 | exp/exp2 image-collision rate |
✅ ~2⁻ʷ (large image) | ✅ ~2⁻ʷ (token) | 🟡 ~1/d ≈ 1/√(2ʷ) | 🟡 ~1/d ≈ 1/√(2ʷ) |
| — infinity & NaN — | |||||
| 27 | 1/0 is a single unsigned ∞ (+∞, −∞ identified) |
❌ ⑧ | ✅ | ✅ | ✅ |
| 28 | 1 / ∞ == 0 |
❌ ⑧ | ✅ | ✅ | ✅ |
| 29 | x + ∞ == ∞, x * ∞ == ∞ (finite x ≠ 0) |
❌ ⑧ | ✅ | ✅ | ✅ |
| 30 | indeterminates ∞±∞, 0*∞, ∞/∞, 0/0 → NaN |
❌ ⑧ | ✅ | ✅ | ✅ |
| 31 | NaN is absorbing (NaN ∘ x == NaN) |
❌ ⑧ | ✅ | ✅ | ✅ |
| — miscellaneous — | |||||
| 32 | min/max/comparisons follow the IEEE numeric order |
❌ ⑨ | ❌ ⑨ | ❌ ⑨ | ❌ ⑨ |
| 33 | cross-width casts | sign-resize the payload ⑪ | reduce mod dest n ⑪ | reduce mod dest n ⑪ | reduce mod dest n ⑪ |
| 34 | subnormals | no special case ⑫ | no special case ⑫ | no special case ⑫ | no special case ⑫ |
| 35 | footprint | = the value (w bits) | = the value (w bits) | = the value (w bits) | = the value (w bits) |
| 36 | leaf-collision rate | ~2⁻ʷ | ~2⁻ʷ | ~2⁻ʷ | ~2⁻ʷ |
| 37 | collisions re-rollable: a fresh prime gives independent blind spots | ❌ ⑬ | ✅ ⑬ | ✅ ⑬ | ✅ ⑬ |
| 38 | per-op cost | mask to w bits | mod-p reduce | mod-n reduce (+ O(d) dlog for log/log2) |
mod-n reduce (+ dlog) |
Legend: ✅ holds exactly · ❌ does not hold · 🟡 holds with a caveat.
Notes.
① Triton's leaf scramble is tuned so embed(0.0)=0 and embed(1.0)=1; that is
why the bare 0/1 identities (rows 4–5, 8) survive even though the encoding is
otherwise a bit scramble that ignores values.
② x+x = 2·embed(x) but 2*x = embed(2.0)·embed(x), and embed(2.0) ≠ 2 under
the scramble — so the two sides disagree.
③ Triton divides by the ring inverse of (denominator | 1), forcing the divisor
odd: x/x == 1 for odd payloads, not for the ~50% that are even. Deterministic
either way.
④ The Exp/Trig moduli are composite (n = p·d), so a residue is a zero-divisor at
rate ≈ 1/p + 1/d; x/x on such a value poisons to NaN. Every unit (the
overwhelming majority) satisfies x/x == 1.
⑤ Only FPSanAlgebraic is an integral domain; the CRT-based variants trade that
for the exp/log channel.
⑥ Triton has genuine exp and exp2 but leaves log/log2 tokens, so they do
not compose. The Exp/Trig variants make log/log2 the discrete-log inverses of
exp/exp2. (exp2/log2 ride the same order-d channel via a fixed base change
exp2(v)=g^{Kv}; log2(e) is irrational so, as with Triton's rcpLog2, no
numeric relation between the bases is claimed — only each base's own homomorphism
and inverse.)
⑦ Triton's cos/sin are Re/Im of (a+bi)ˣ with (a,b) = (−3/5, 4/5), a
norm-1 Pythagorean rotor in Z/2ʷ — so angle addition and cos²+sin² == 1 hold.
The Trig variant reproduces this on the φₙ residue, where it coexists with the
exact value relations (rows 9–11).
⑧ Triton carries the IEEE ∞/NaN bit patterns as ordinary scrambled payloads
and applies ring arithmetic to them, so none of the ∞/NaN laws hold; they are
not modeled. The algebraic variants use the projective line ℙ¹ plus an absorbing
NaN (§7).
⑨ No finite ring carries an order compatible with its arithmetic. All four compute
min/max on a signed residue/payload representative — deterministic,
commutative, reassociation-invariant, but not the IEEE numeric order.
⑩ token (rows 22–25): a deterministic, op-distinct scramble — equal inputs give
equal outputs and distinct ops give distinct outputs, but it honors no real-math
identity. This is the correct model for a function with no usable algebraic
structure: by Lindemann–Weierstrass / Schanuel, transcendental values at distinct
algebraic arguments are independent, so a fresh free generator per op is faithful
(§6). In rows 22–25 the marker reads differently from the ✅/❌ identity rows
above: ✅ flags a realization that carries a genuine identity (the rows 15–21
laws), 🟡 a bare token (supported, but structureless) — so a column's ✅s are
exactly the transcendental laws it keeps.
⑪ No model is value-faithful across widths; the cast is the identity only at equal
width. Triton sign-extends / truncates the (scrambled) payload — chosen so a
sanitized mixed-precision program fingerprint-matches a Triton reference run that
does the same. The algebraic variants reduce mod the destination modulus, which a
per-width modulus forces: widening the value is information-theoretically
uncomputable from the narrow residue (an information barrier, not a gap; §5).
⑫ Neither model special-cases subnormals (no flush-to-zero). A subnormal is still
an exact dyadic value, so φₙ encodes it exactly like a normal and it obeys the same
value relations; FPSan scrambles its bit pattern like any other. Listed because
many fp tools do special-case subnormals — here nothing is needed.
⑬ The collision set — which distinct inputs land on the same fingerprint — is
fixed for FPSan's single tuned scramble: the same blind spots on every run. Each
algebraic prime has its own independent collision set, so the 1 and 2 variants
(or any freshly drawn prime) catch a coincidental match the other missed.
Bottom line. FPSanLikeTriton tracks the structure of a computation, not its
values: it nails the 0/1 ring identities by construction and ships genuine exp,
exp2, and cos/sin, but no value coincidence (2+2==4) survives the scramble,
and ∞/NaN are unmodeled. The algebraic variants make the encoding itself a ring
homomorphism, so every rational-function identity holds within a width and the
∞/NaN story is principled — at the cost of mod-n arithmetic and a few bits of
collision margin (recoverable by repeating with the 2 variant's second prime).
FPSanAlgebraic is the clean field — pick it when no transcendental law is
needed; FPSanAlgebraicExponentials adds the exact exp/exp2/log/log2
homomorphisms (zero-divisors as the price); FPSanAlgebraicTrigonometry adds
sin/cos on top, at a ≈√2 smaller d. The one limitation shared by all four,
not fixable by any sign/exponent trick, is order (min/max/comparisons): a
finite ring has no compatible order.