Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6762cdc
Initial impl for Ziv loop
Jul 18, 2026
4ee6451
Fix log perf
Jul 19, 2026
4022dc0
Simplify basic operations for Repr
Jul 19, 2026
a028151
Ziv loop for trigonometric, hyperbolic, and hypot (powf deferred)
Jul 20, 2026
856df5d
Ziv loop for powf (non-integer exponent); integer exponent delegates …
Jul 20, 2026
d9f1f18
Fix acos(1) hang under directed rounding
Jul 20, 2026
2ca4c67
Special-case asin(0) and acos(-1) under directed rounding
Jul 20, 2026
a482d7e
Fix NTT sqr/mul panic on all-zero operand
Jul 20, 2026
d06a91e
docs: update hypot note after the dashu-int NTT fix
Jul 20, 2026
dab5cc1
Restructure hypot to sqrt(large^2 + small^2) with exactness tracking
Jul 20, 2026
fa6d0f0
Address review: float cleanup + tan pole-check removal
Jul 21, 2026
371afb5
Address review: cmplx unlimited-precision handling
Jul 21, 2026
c55b03a
Ziv correct-rounding for the complex transcendentals
Jul 21, 2026
dbe3bfe
Complex tan via the cancellation-free double-angle form
Jul 21, 2026
294ede1
Fix no_std build of the test-only Ziv retry counter
Jul 21, 2026
b50348e
powi: guaranteed-correct rounding via the Ziv loop
Jul 21, 2026
01ab150
cmplx: guaranteed-correct rounding for powi via the Ziv loop
Jul 21, 2026
ab6e526
cmplx: factor 1-z² in asin/acos for accuracy at the z=±1 singularities
Jul 21, 2026
bf54c67
cmplx: exact-accumulating Sum for CBig
Jul 21, 2026
327e3e5
docs: mark powi correctly-rounded and asin/acos fixed at z=±1
Jul 21, 2026
66d6033
Merge branch 'master' into ziv
Jul 24, 2026
4ff74e2
log2: guaranteed-correct rounding via the Ziv loop
Jul 24, 2026
466e6a2
Merge master (#94 opendp-num fixes) into ziv
Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions V1-ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# dashu Roadmap — v0.5.x and beyond

Last updated: 2026-07-11
Last updated: 2026-07-20

Feature work deferred out of the **v0.5.0** release. The v0.5.x items are all **additive**
(no breaking changes) and safe to ship as point releases on top of 0.5.0; the post-v1 items
Expand Down Expand Up @@ -33,11 +33,22 @@ are longer-term goals. File:line references are anchors from the v0.5.0 tree and

### Correctness

- **Guaranteed-correct rounding (Ziv retry loop).** ✅ *Partially delivered.* `exp`, `exp_m1`,
`ln`, `ln_1p` are now guaranteed-correctly rounded via a Ziv retry loop in `dashu-float`
(`Context::ziv`, driven by the `ErrorBounds` preimage). Remaining: trig, hyperbolic, `powf`,
`hypot`, and inheriting the loop across `dashu-cmplx`'s complex transcendentals (which currently
route through the now-Ziv-backed real primitives but aren't themselves Ziv-wrapped).
- **Guaranteed-correct rounding (Ziv retry loop).** ✅ *Delivered for all real and complex
transcendentals.* `exp`, `exp_m1`, `ln`, `ln_1p`, the trigonometric family (`sin`, `cos`,
`sin_cos`, `tan`, `asin`, `acos`, `atan`, `atan2`), the hyperbolic family (`sinh`, `cosh`,
`sinh_cosh`, `tanh`, `asinh`, `acosh`, `atanh`), `hypot`, `powi`, and `powf` are
guaranteed-correctly rounded via a Ziv retry loop (`Context::ziv`/`ziv_pair`, driven by the
`ErrorBounds` preimage). Series transcendentals (trig, atan) use near-correct `_compute` cores the
wrapper certifies; composition transcendentals treat the Ziv-correct primitives as black boxes.
`powf`'s `exp(y·ln x)` amplification is handled by a work-precision radius
(`result.ulp()·(|y·ln x|+1)·(B+8)`) that shrinks as `B^{-guard}`, so the containment test
converges. `powi` (repeated squaring) carries a radius that scales with the squaring-compounding
error (`2^bit_len(n)·ulp`) and drops to zero when the chain is exact — required under the directed
rounding modes (`FBig`/`CBig` default to `mode::Zero`), where an exactly-representable result sits
on a one-sided rounding boundary. `dashu-cmplx`'s complex transcendentals
(`exp`/`log`/`powf`/`powi`/`sin`/`cos`/`tan`/`sin_cos`/`asin`/`acos`/`atan`/`sqrt`) are
correctly rounded via their own complex Ziv driver (certifies both parts); `asin`/`acos` use the
factored `1-z²=(1-z)(1+z)` to stay accurate right up to `z = ±1`.
- **Signed-zero preservation in `CBig` zero short-circuits.** `sin_cos` and `sqr` take a fast
path on exactly-zero input that returns `+0` components, so several Annex-G / IEEE signed-zero
cases are not preserved (all numerically equal to `+0`, hence deferred):
Expand All @@ -58,8 +69,8 @@ Consolidated from the original `CBig` design. All additive.
complex-valued functions themselves are deferred.)
- **More transcendentals** — complex `fma` (fused multiply-add — hard to round correctly),
`rootofunity`, complex `agm`, and `exp2`/`exp10`/`log2`/`log10`.
- **Vector ops** — `dot`/mean helpers and a correctly-rounded (exact-accumulating) `Sum` for
`CBig`. (Fold-based `Sum`/`Product` for `CBig` already exist; `Sum` is not yet correctly-rounded.)
- **Vector ops** — `dot`/mean helpers. (`Sum` for `CBig` is already exact-accumulating, matching
`FBig: Sum`; `Product` for `CBig` remains a fold, matching `FBig: Product`.)
- **Independent re/im rounding** — a `CRound` trait giving MPC `mpc_rnd_t` parity (0.5 uses a
single `R` for both parts).
- **Third-party integration** — `CBig` `serde`/`rkyv`/`zeroize`, and
Expand Down
43 changes: 43 additions & 0 deletions complex/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,49 @@
parts for each of the real/imaginary components (built on `Repr::new_const`). The `cbig!` literal
macro now works in `const` position for coefficients that fit in a `DoubleWord`; larger
coefficients fall back to the runtime heap path.
- **Correct rounding for the complex transcendentals** via a Ziv retry loop (`complex/src/ziv.rs`).
`exp`, `log`, `powf`, `sin`/`cos`/`tan`/`sin_cos`, `asin`/`acos`/`atan`, and `sqrt` now certify
*both* the real and imaginary parts — rounding each to the target precision and retrying with more
guard digits while either part's error interval straddles a rounding boundary — matching
`dashu-float`'s real transcendentals. Each transcendental reports a provable per-part error radius
(`result.ulp() × C`, plus an amplification term where the composition magnifies error: `log`'s
`ln|z|` near `|z| = 1`, `powf`'s result magnitude). `tan` uses the cancellation-free double-angle
form `(sin 2x + i·sinh 2y)/(cos 2x + cosh 2y)` (the naive `sin z/cos z` cancels in the real part for
large `|Im z|`), so it is accurate for all finite `|Im z|`. `abs` delegates directly to
`dashu-float`'s already-correctly-rounded `hypot`, dropping a double-rounding re-round. The
well-conditioned regime is guaranteed-correctly rounded; `asin`/`acos` compute the inner `1-z²` in
the factored form `(1-z)(1+z)` (Sterbenz-exact near the singularities `z = ±1`, where the direct
`1 - z²` would cancel against the `sqr` rounding error), so they stay accurate right up to `z = ±1`.
(`atan` was already sound near its singularities `z = ±i`: its `1 ± iz` is built from an exact
rotation and an exact-significand add/sub, so there is no cancellation to factor out.)

### Change
- **(breaking, bound)** The complex transcendentals (`exp`, `ln`, `powf`, `powi`, `sin`/`cos`/`tan`/
`sin_cos`, `asin`/`acos`/`atan`) and their `CachedCBig` forwarders now require `R: ErrorBounds`,
inherited from `dashu-float`'s Ziv-backed real `exp`/`ln`/`sinh_cosh` primitives. `powi` is now
correctly rounded via its own Ziv loop (repeated squaring, with a per-part radius that scales with
the squaring-compounding error and drops to zero when the chain is exact — required under the
directed rounding modes, `CBig`'s default). The field arithmetic
(`add`/`sub`/`mul`/`div`/`sqr`/`inv`) remains `R: Round`.

### Fix
- **`Sum` for `CBig` is now correctly rounded.** It exact-accumulates the real and imaginary
parts independently via `FBig`'s exact-accumulating `Sum` (lossless `Repr` accumulation, a single
final rounding per axis at the `Context::max` target), instead of the previous componentwise fold
whose per-step rounding could lose low-order terms (e.g. `1e20 + 1 - 1e20` folded to `0` at low
precision; it now correctly yields `1`). `Product` stays a fold, matching `FBig: Product`.
- **Unlimited-precision handling**, centralized in the Ziv driver: it asserts a limited context up
front, so every transcendental panics on precision 0 as documented (the exact special-value
shortcuts — `exp(0)=1`, `log(0)=-∞`, `powf(z,0)=1`, `sqrt(±0)`, `sin/cos(0)`, etc. — still bypass
it). The prior per-function `guard()`/`assert_limited()` scaffolding is removed (`guard()` itself
is gone, now unused).
- **Arithmetic at unlimited precision is correct.** `mul`/`sqr`/`norm` use `Context::work_context`,
which is the exact `self.float()` (precision 0) at unlimited, so they are exact there. `div`/`inv`
use the same path and panic at unlimited via `dashu-float`'s `div` (a quotient isn't exactly
representable in general). `abs` asserts limited before delegating to the float `hypot`.
- **`no_std` build of the test-only Ziv retry counter.** The `LAST_ZIV_RETRIES` `thread_local!`
requires `std`, so the crate failed to compile under `--no-default-features`. It's now gated behind
`feature = "std"` (with its uses and the counter-reading tests), so the Ziv driver is `no_std`-clean.

## 0.5.0 (Initial release)

Expand Down
32 changes: 19 additions & 13 deletions complex/src/cbig_cached_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use core::iter::{Product, Sum};
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};

use dashu_base::Inverse;
use dashu_float::round::Round;
use dashu_float::round::{ErrorBounds, Round};
use dashu_float::FBig;
use dashu_int::{IBig, Word};

Expand Down Expand Up @@ -372,29 +372,35 @@ macro_rules! delegate_to_cbig {
}

impl<R: Round, const B: Word> CachedCBig<R, B> {
// cache-threading transcendentals
forward_cached!(ln => log);
forward_cached!(exp => exp);
forward_cached!(sin => sin);
forward_cached!(cos => cos);
forward_cached!(tan => tan);
forward_cached!(asin => asin);
forward_cached!(acos => acos);
forward_cached!(atan => atan);

// non-cache delegations
delegate_to_cbig!(sqr);
delegate_to_cbig!(sqrt);
delegate_to_cbig!(conj);
delegate_to_cbig!(proj);
delegate_to_cbig!(mul_i(negative: bool));
delegate_to_cbig!(powi(exp: IBig));

/// The squared modulus `re² + im²` (a real [`FBig`]) (see [`CBig::norm`]).
#[inline]
pub fn norm(&self) -> FBig<R, B> {
self.cbig.norm()
}
}

// Transcendentals route through the Ziv-backed (or Ziv-dependent) real/complex methods, which
// require `R: ErrorBounds`.
impl<R: ErrorBounds, const B: Word> CachedCBig<R, B> {
// cache-threading transcendentals
delegate_to_cbig!(powi(exp: IBig));
forward_cached!(ln => log);
forward_cached!(exp => exp);
forward_cached!(sin => sin);
forward_cached!(cos => cos);
forward_cached!(tan => tan);
forward_cached!(asin => asin);
forward_cached!(acos => acos);
forward_cached!(atan => atan);

// `sqrt` and `abs` route through `Context::hypot`, which is now Ziv-backed (`R: ErrorBounds`).
delegate_to_cbig!(sqrt);

/// The modulus `|z|` (a real [`FBig`]) (see [`CBig::abs`]).
#[inline]
Expand Down
16 changes: 13 additions & 3 deletions complex/src/div.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl<R: Round> Context<R> {
if z.is_zero() {
return Ok(riemann(*self)); // 1/0 = ∞
}
let gctx = self.guard(DIV_GUARD);
let gctx = self.work_context(DIV_GUARD);
let p = self.precision();
let (x, y) = (z.re(), z.im());
// n = x² + y²
Expand All @@ -41,7 +41,7 @@ impl<R: Round> Context<R> {
if let Some(special) = div_special(z, w) {
return special;
}
let gctx = self.guard(DIV_GUARD);
let gctx = self.work_context(DIV_GUARD);
let p = self.precision();
let (x, y) = (z.re(), z.im());
let (u, v) = (w.re(), w.im());
Expand Down Expand Up @@ -109,7 +109,7 @@ impl<R: Round> Context<R> {
}
return Ok(riemann(*self)); // z/±0 (z≠0) = ∞
}
let gctx = self.guard(DIV_GUARD);
let gctx = self.work_context(DIV_GUARD);
let p = self.precision();
let re = gctx.div(z.re(), s.repr())?.value().with_precision(p);
let im = gctx.div(z.im(), s.repr())?.value().with_precision(p);
Expand Down Expand Up @@ -268,4 +268,14 @@ mod tests {
assert!(q_neg.is_infinite());
assert!(q_neg == q_pos); // both are +∞ + i·0
}

// `div` at unlimited precision panics via the float layer (`work_context` → `self.float()` →
// float `div`, which rejects unlimited: a quotient isn't exactly representable in general).
#[test]
#[should_panic(expected = "precision cannot be 0")]
fn complex_div_unlimited_panics() {
let one = C::ONE;
let i = C::I;
let _ = &one / &i; // 1/i at unlimited
}
}
Loading
Loading