Skip to content

Add Ziv loop for guaranteed rounding#92

Open
cmpute wants to merge 11 commits into
masterfrom
ziv
Open

Add Ziv loop for guaranteed rounding#92
cmpute wants to merge 11 commits into
masterfrom
ziv

Conversation

@cmpute

@cmpute cmpute commented Jul 19, 2026

Copy link
Copy Markdown
Owner

No description provided.

Jacob Zhong and others added 11 commits July 18, 2026 23:37
Migrate the remaining real transcendentals to guaranteed-correct
rounding via the Ziv retry loop (`Context::ziv`/`ziv_pair`):

- Trig (sin, cos, sin_cos, tan, asin, acos, atan, atan2): extract
  near-correct `_compute` series cores and wrap them in Ziv. The
  argument reduction folds a `|k|·ulp(π/2)` reduction-error term into
  the radius so the containment test stays sound for huge `|x|`.
- Hyperbolic (sinh, cosh, sinh_cosh, tanh, asinh, acosh, atanh):
  composition-based, treating the Ziv-correct `exp_m1`/`ln_1p` as
  black boxes; the unreachable exp-overflow case is hoisted via a
  shared `exp_overflows` probe (closures can't return `Err`).
- `hypot`: composition-based (sqrt attenuates), small-constant radius.

`sin_cos`/`sinh_cosh` certify both halves via the new `ziv_pair`
driver. `powf` is left near-correct — its `exp(y·ln x)` amplification
makes the data-dependent radius converge poorly for large results, so
a dedicated treatment is deferred.

Bound propagation: the migrated `Context`/`FBig`/`CachedFBig` methods
move to `R: ErrorBounds`; dashu-cmplx's complex `sqrt`/`norm`/`abs`/
`arg` follow (they route through `hypot`/`atan2`).

Tests: 20 exact-oracle soundness tests (`f(x)@p == f(x)@2p re-rounded`)
covering every migrated function; existing trig/hyper/exp suites pass.
Docs: float CHANGELOG, V1-ROADMAP, and bilingual compliance/faq updated.

Co-Authored-By: Claude <noreply@anthropic.com>
…to powi

powf with a non-integer exponent is now correctly rounded via the Ziv loop.
The fix for the previously-deferred amplification problem: the error radius is
result.ulp()*(|y*ln x|+1)*(B+8) taken at the *working* precision, so it shrinks
as B^{-guard} and the containment test converges. A radius computed at unlimited
precision is constant across retries and never settles for a value near a
rounding boundary, which is why the earlier attempt infinite-retried.

An integer-valued exponent now delegates to powi (binary exponentiation), gated
on Repr::is_int. This also admits a negative base (sign fixed by the exponent's
parity) -- the old OutOfDomain TODO. That path is near-correct (<=1 ulp),
matching powi. The exp overflow case is hoisted out of the Ziv closure.

Also: Repr::is_int is now const (pure exponent/sentinel check).

Co-Authored-By: Claude <noreply@anthropic.com>
acos(1) = pi/2 - asin(1) cancels to exactly 0, but the composition carries a
positive radius. Under a directed rounding mode (Down/Zero/Up) the preimage of 0
is one-sided ([0, ulp)), so the Ziv containment test can never certify it -- any
positive radius dips the interval below 0 -- and it infinite-retried. This was
the signed_zero.rs full-file hang: test_asin_acos_unit_under_down hung on
acos(1) under Down.

Short-circuit x = 1 to the exact 0 before the Ziv loop, matching asin's existing
|x| = 1 special case. (asin(+-1) = +-pi/2 and acos(-1) = pi are nonzero, so they
were already fine.)

Co-Authored-By: Claude <noreply@anthropic.com>
A scan of every transcendental at its exact-representable special inputs under
Down/Up/Zero turned up two more instances of the acos(1) hang (a transcendental
whose true value is exactly representable carries a positive radius that can't be
certified against the value's one-sided preimage, so Ziv infinite-retries):

- asin(0) = 0: asin lacked the x=0 short-circuit that asinh/atanh/sin/cos/... all
  have, so it computed atan(0)=0 with a positive radius and infinite-retried.
  Now short-circuits to +-0 (asin is odd), matching the rest of the family.
- acos(-1) = pi: short-circuited for symmetry with acos(1).

The scan confirmed no other transcendentals hang (sqrt of perfect squares,
exp(0), cos(0), cosh(0), powf(x,0), nth_root of exact roots, etc. are all already
special-cased or detect exact results). Remaining: hypot of an exact Pythagorean
triple (e.g. hypot(3,4)=5) -- same containment class but no clean single special
case, plus a dashu-int NTT crash hit during the retry; tracked separately.

Co-Authored-By: Claude <noreply@anthropic.com>
add_signed_sqr_conv and add_signed_mul_conv assumed a non-zero input
(debug_assert!(la_bits > 0)); an all-zero slice panicked in debug and indexed
out of bounds in release. sqrt_rem of a perfect square with many trailing zero
words squares the estimate's all-zero low half to verify the remainder, so this
was reachable -- and was the crash behind dashu-float's hypot(3,4) under directed
rounding (the Ziv retry drove sqrt to a precision where the scaled significand
hit the NTT path).

An all-zero operand now returns early: the product is zero, so c += sign*a*b (or
a^2) is a no-op. This matches the existing zero-guard in the chunked-multiply
closure. Regression test: sqrt_rem of (1<<560000)^2.

Co-Authored-By: Claude <noreply@anthropic.com>
The float changelog's hypot note said the underlying dashu-int NTT crash was
"tracked separately"; that crash is now fixed (a482d7e), so update the note to
reflect that hypot(exact) still infinite-retries on the containment issue, but
no longer crashes.

Co-Authored-By: Claude <noreply@anthropic.com>
Replace the ratio form (large * sqrt(1 + (small/large)^2)) with the scaled
direct form sqrt(large^2 + small^2), tracking each step's Exact/Inexact flag
(MPFR's `exact` flag in hypot.c). When the whole chain is exact -- which holds
for every Pythagorean-triple input -- the closure reports radius 0, which ziv
accepts without the containment test. This is the only way to terminate an
exactly-representable result under directed rounding, where the value sits on a
one-sided preimage boundary.

The ratio form broke this for triples like hypot(5,12)=13: 5/12 is a
non-terminating binary fraction, so div returned Inexact even though the final
result 13 is exact. The direct form has no division, so sqr/add/sqrt are all
exact for integer inputs -- mirroring how MPFR avoids a division precisely so
its exact flag works for every triple.

Both operands are scaled down by k base-B digits before squaring (k chosen so
large^2 can't overflow the exponent) and the root scaled back, exactly MPFR's
sh. Regression tests: hypot of (3,4)/(5,12)/(8,15) under Down/Up/Zero all
return the exact integer.

Co-Authored-By: Claude <noreply@anthropic.com>
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