Skip to content

Make abs and lohi exact; bring coverage to 100%#10

Merged
timholy merged 1 commit into
mainfrom
teh/coverage
Jul 15, 2026
Merged

Make abs and lohi exact; bring coverage to 100%#10
timholy merged 1 commit into
mainfrom
teh/coverage

Conversation

@timholy

@timholy timholy commented Jul 14, 2026

Copy link
Copy Markdown
Member

Started as a coverage pass (66.1% → 100%), which turned up two correctness bugs along the way.

abs is now exact when the span crosses zero

Previously, abs always reflected: abs(0 ± 1) returned 0.0 ± 1.0. The center was wrong by O(σ) — the folded normal's mean is σ√(2/π) ≈ 0.8 — and the code compensated by charging σ to moment_error rather than by giving the right answer.

The folded normal's first three moments have a closed form, so abs now computes them. With α = c/σ, ϕ the standard normal density, and e = erf(α/√2) = E[sign(x)]:

E[|x|]  = σ(2ϕ + αe)
E[|x|²] = c² + σ²
E[|x|³] = σ³((α³ + 3α)e + 2(α² + 2)ϕ)

So abs(0 ± 1) is now 0.798 ± 0.603, exact, with moment_error == 0; distrust still flags it, but through the skew (the half-normal's skewness is ≈0.995), which is the honest defect. Beyond |α| = 8 the wrong-side mass is below roundoff and E[|x|²] - E[|x|]² starts to cancel badly, so the reflection branch is kept there. The two branches agree where they meet.

This needs erf, hence the new SpecialFunctions dependency.

lohi now produces a span that actually contains [lo, hi]

Two problems with G((lo + hi)/2, nextfloat((hi - lo)/2)):

  1. The unconditional nextfloat inflated even exact spans — a degenerate span got a subnormal radius, which is how min(3 ± 1, 2.0) came out as 2.0 ± 5.0e-324.
  2. Containment was broken anyway: 3020 random spans per million failed loval(g) <= lo && hival(g) >= hi. When the span is narrow but far from zero, an ulp of σ is far smaller than an ulp of the center, so no amount of widening (hi - lo)/2 can recover what rounding the center lost.

Measuring the radius from the rounded center, max(center - lo, hi - center), fixes both: it is exact in the narrow-span case (Sterbenz), needs at most a single ulp of widening in the rest (verified over 10⁶ adversarial spans — adjacent floats, extreme and mixed magnitudes), and leaves a degenerate span with σ == 0.

Coverage

66.1% → 100%. New tests cover sums/differences of GVars (which were entirely untested), abs, min/max, scalar arithmetic in both argument orders, the integer-backed methods (reachable only via GVar{Int}, since GVar(1, 2) float-promotes), show, hash, and the trait functions. The folded-normal moments are checked against both the half-normal closed form and Monte Carlo; regression tests pin the lohi containment property.

175 tests pass on both release and LTS.

🤖 Generated with Claude Code

`abs` of a `GVar` whose span crosses zero is a folded normal. Its first
three moments have a closed form, so compute them: the center is now
exact instead of being off by O(σ) and flagged through `moment_error`.
Beyond 8σ from zero the fold is a pure reflection to within roundoff,
and the moment differences lose their conditioning, so reflect there.
This requires `erf`, hence the new SpecialFunctions dependency.

`lohi` measures the radius from the rounded center, `max(center - lo,
hi - center)`, and widens by an ulp only when rounding breaks
containment. A narrow span far from zero was not contained before: an
ulp of σ is far smaller than an ulp of the center, so inflating
`(hi - lo)/2` could not recover it. A degenerate span now has σ == 0
rather than a subnormal radius, so `min(3 ± 1, 2.0)` is exactly 2.0.

Tests cover the whole public surface, including sums of `GVar`s, `abs`,
`min`/`max`, the integer-backed methods reachable only through
`GVar{Int}`, `show`, and the trait functions. The folded-normal moments
are checked against both the half-normal closed form and Monte Carlo.

Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
@timholy timholy merged commit 33e0b58 into main Jul 15, 2026
2 checks passed
@timholy timholy deleted the teh/coverage branch July 15, 2026 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant