Implement a sharable const cache for FBig#83
Conversation
Add an opt-in MathCache<B> type to dashu-float that caches exact binary-splitting tree state for mathematical constants, so repeated calls at increasing precision *extend* prior work instead of recomputing from scratch (e.g. π at 100 digits then 1000 digits pays only for the extra work). Context and FBig are untouched and remain Copy + Send + Sync; the cache is purely additive (Send + !Sync, wrappable in Arc<Mutex<..>>). Implementation (FLOAT-CACHE.md): - math/cache.rs: MathCache<B>, ConstCache (one Option<CachedState> field per series — pi, iacoth_6/9/99), CachedState (exact (P,Q,T,num_terms)), extend_or_compute (reuse / extend / cold-compute), manual Debug that reports term counts and bit lengths rather than MB-sized integers. - math/consts.rs: factor out a shared universal merge() and add iacoth_bs() (ratio-form binary splitting for L(n), keeping Q at O(p) digits); give chudnovsky_bs an a>=b identity guard and make the helpers pub(crate). - math::pi/ln2/ln10/ln_base: finalize from the cached exact integers; ln2/ln10/ln_base combine sub-series at an elevated work precision so the linear combination survives the final round. - Switch Context::iacoth from its iterative loop to iacoth_bs (behavior pinned by the existing test_iacoth / test_ln2_ln10 fixtures). Works in no_std (uses core::cell::RefCell + EstimatedLog2; no std-only transcendentals). All float unit tests, doctests, cargo check --all-features --tests, clippy -D warnings, and rustfmt pass. Co-Authored-By: Claude <noreply@anthropic.com>
The design plan has been fully reflected in float/src/math/cache.rs and the supporting helpers; the document no longer adds value beyond the code, tests, and changelog. Only the explicitly-deferred Phase 4 roadmap items (benchmark/leaf-threshold tuning, optional sqrt(10005) caching, future sin/cos) were not implemented. Co-Authored-By: Claude <noreply@anthropic.com>
The cache Debug test used format!, which is not in scope when the crate is built without std (the CI runs `cargo test --no-default-features --features rand`). Import it from alloc, matching how other no_std test modules in the workspace pull in alloc items. Co-Authored-By: Claude <noreply@anthropic.com>
The first several leaves of the L(n) = acoth(n) binary splitting are cheap but redundant to recompute on every fresh evaluation. Precompute their merged (P, Q, T) state as a compile-time constant for the sub-series that back ln2 / ln10 (n = 6, 9, 99), and use it as the basecase of iacoth_bs: when the range starts at the series origin it returns the constant triple instead of recursing into those leaves. K is chosen per n so that P, Q and |T| each fit in a DoubleWord, which keeps the triples portable consts on both 32- and 64-bit Word (verified by the CI `--cfg force_bits="32"` clippy/test gate). Because the merge is associative, the constant equals the recursively computed state regardless of split order — pinned by a new test that re-derives each block independently and cross-checks iacoth_bs. pi cannot use this trick: its 2-term T (~1.5e23) already overflows a DoubleWord, so Chudnovsky keeps its existing single-term k=0 const leaf. Co-Authored-By: Claude <noreply@anthropic.com>
The previous basecase constants fit only u64, so they failed to compile on Word = u16 (where DoubleWord = u32), which the CI exercises via --cfg force_bits="16". dashu-int's Word width is not detectable from the float crate, so instead keep every precomputed P/Q/|T| within u32: a u32 literal is accepted by from_dword / from_parts_const on Word = u16/32/64 alike, giving a single portable set of constants with no width detection. This reduces the precomputed depth (4/3/2 leaves for n = 6/9/99, down from 7/6/4) in exchange for portability. Verified on force_bits = 16, 32 and 64 (clippy -D warnings + tests), no_std, and the default 64-bit build. Co-Authored-By: Claude <noreply@anthropic.com>
Introduce `CachedFBig`, an FBig carrying a shared `Rc<RefCell<ConstCache>>` handle whose transcendental operations (ln, exp, trig, pi, base conversion) thread the handle through the `Context` methods, reusing/extending the cached exact binary-splitting state instead of recomputing constants from scratch. The cache lives outside `Context`, which stays `Copy + Send + Sync + no_std` (so `static_fbig!`/`static_dbig!` keep working); only `CachedFBig` is `!Send + !Sync` (sharing state via `Rc<RefCell<..>>`). `ConstCache` replaces the earlier `MathCache` wrapper as the sole public cache type (`Send + Sync`, `&mut self` methods). Since `Context` accepts `Option<&mut ConstCache>`, users can also build `Arc<Mutex<ConstCache>>`-based variants. - The constant-source `Context` methods (ln, ln_1p, exp, exp_m1, powf, pi, sin/cos/sin_cos/tan/asin/acos/atan/atan2, and internal ln2/ln10/ln_base/ convert_base) gain a `cache: Option<&mut ConstCache>` parameter — a breaking change to the low-level `Context` API. The high-level `FBig` API is unchanged (it passes `None`). - CachedFBig mirrors FBig's surface explicitly; every value-producing op preserves the handle, so `(a + b).ln().exp()` stays cached. - Fix a pre-existing no_std bug: `f64::ceil()` in ConstCache's precision helpers is std-only on the MSRV (1.68) and broke the workspace `--all-features --tests` build (dashu-float is built without std as a dependency of dashu-ratio); replaced with an integer `ceil_usize`. Co-Authored-By: Claude <noreply@anthropic.com>
Re-encode +inf/-inf as exponent isize::MAX/isize::MIN (was 1/-1) and add the Repr::neg_zero() constructor at exponent -1, per the repr.rs:125-126 plan. Update is_infinite() to match the new sentinel exponents and add is_neg_zero(). normalize() now preserves infinity sentinels instead of clobbering them (the prior documented bug); -0 is not yet produced by any operation, so existing behavior is unchanged. NumHash short-circuits zero-significand values to avoid overflow when negating the isize::MIN exponent of -inf. No user-visible behavior change; infinity still panics in arithmetic. First milestone of the signed-zero / FpResult-reshape refactor (single PR). Co-Authored-By: Claude <noreply@anthropic.com>
Operations now produce the IEEE-mandated sign of zero, and -0 is a first-class value distinct from +0 only where the sign matters (1/-0 = -inf etc., landing in M3). Highlights: - Repr: manual PartialEq/Eq so +0 == -0; normalize() preserves the -0 encoding. - Neg/Abs/signum: correctly toggle ±0 and ±inf by flipping the sentinel exponent (negating IBig::ZERO is a no-op). Sign-mul (Sign*FBig) delegates to Neg. - Comparisons (cmp.rs): ±0 compare and order equal. - Arithmetic: mul attaches XOR sign to zero products; div/rem attach the dividend/XOR sign to zero results; add/sub cancellation yields -0 only under roundTowardNegative (Down), +0 otherwise (new Round::IS_ROUND_TOWARD_NEGATIVE). repr_round preserves input sign when rounding to zero. - Transcendentals: sqrt/cbrt/nth_root preserve ±0; sin/tan/atan/sin_cos carry the sign (cos(±0)=+1); ln_1p(±0)=±0; pow(-0,n) sign via sqr/mul. - Rounding ops: trunc/round/fract yield signed zero; ceil/floor pass -0 through. - Conversions: -0.0 round-trips through f32/f64 (TryFrom checks is_sign_negative; into_f*_internal preserves -0). num_traits is_positive/is_negative follow the sign bit (matching Rust's f64::is_sign_*). - shift.rs skips exponent modification for any zero-significand value. Infinity-as-value (1/0 -> +inf, ln(0) -> -inf, exp(huge) -> +inf) and the FpResult reshape remain for M3. Co-Authored-By: Claude <noreply@anthropic.com>
Reshape the result model so that infinite *inputs* are errors and infinite
*outputs* are legitimate values, unifying the old FpResult enum with Rounded.
- FpError { InfiniteInput, OutOfDomain, Indeterminate } (Display + std Error),
modeled on ConversionError; FpResult<T> = Result<Rounded<T>, FpError>.
- unwrap_fp() maps FpError variants to granular panics for the FBig/CachedFBig
convenience layer, which now uniformly panics on error (including trig).
- Deleted the old FpResult enum.
- Context arithmetic/transcendental/trig methods return FpResult:
* inf input -> Err(InfiniteInput), except atan(±inf)=±π/2 and the atan2
signed-∞ quadrant table (preserved, well-defined finite results).
* inf output as a value: 1/0 -> ±inf, inv(0) -> ±inf, ln(0) -> -inf,
tan(odd·π/2) -> ±inf (repr_div produces inf for finite/0).
* 0/0 -> Err(Indeterminate); sqrt/ln of negative, asin(|x|>1), pow(neg base),
even root of negative -> Err(OutOfDomain).
- FBig/CachedFBig layers panic-on-error via unwrap_fp; new
forward_to_context_unwrap! macro unifies sin/cos/tan/asin/acos/atan (which
finally fit the macro now that trig returns the uniform Result shape).
- Internal call sites updated (cache/consts/exp/log/convert/rand); removed
unused panic helpers; tests and doctests updated to the Result shape.
New tests/fpresult.rs covers the inf-as-value / inf-input-as-error / domain
contract. The old dead Overflow/Underflow variants are gone; inf flows as a
value, errors flow as Err.
Co-Authored-By: Claude <noreply@anthropic.com>
- Rewrite the IEEE-754 compliance section in fbig.rs to describe the new model: NaN → FpError/panic, signed zero, and infinities as terminal values (producible and comparable, but inf inputs error/panic; atan/atan2 inf cases preserved). - Replace the stale lib.rs TODO about inf arithmetic with a note describing the implemented terminal-value behavior. - CHANGELOG (Unreleased, → 0.5.0): document the breaking encoding change (±inf sentinel exponents, -0), the FpResult = Result<Rounded<T>, FpError> reshape and old-enum removal, and the FBig trig methods returning Self. rational::to_float and dashu-python are unchanged (they only use the still-Rounded conversions); the workspace compiles and all 66 test binaries pass. Co-Authored-By: Claude <noreply@anthropic.com>
- FBig +/- operators: produce -0 on exact cancellation under round-toward-negative (Down), matching Context::add/sub. The equal-exponent fast paths now route the summed significand through cancel_zero (previously they yielded +0). - powf(±0, y): return the *positive* result (+0 for y>0, +inf for y<0), matching the common float-pow convention (a float exponent doesn't track parity). powi remains the sign-correct variant (pow(-0, odd) = -0). Fixes powf(0, negative) which previously returned +0. - exp(huge)/exp_m1(huge)/powi: return +inf (or 0 / -1 for huge negative exp args) instead of panicking when the result exponent overflows isize. - Fix pre-existing bug: FBig's ShrAssign (>>=) subtracted the shift twice; now once. - CHANGELOG: document the powf convention, overflow-to-inf, and the shr_assign fix. New tests cover operator cancellation under Down, exp overflow, powf zero base, and shr_assign. Co-Authored-By: Claude <noreply@anthropic.com>
…iacoth Adds bench_iacoth::bench_ln2_iter_vs_binary_splitting (#[ignore], run via `cargo test -- --ignored --nocapture`) that times ln(2) via master's iterative Maclaurin-series iacoth against the current binary-splitting path. Measured speedup of binary splitting over the iterative method grows with precision (decimal digits): ~9x@50, ~26x@200, ~92x@1000, ~420x@5000, ~791x@10000 — consistent with the asymptotic gap (iterative O(p·M(p)) vs binary splitting O(M(p)·log p)). Co-Authored-By: Claude <noreply@anthropic.com>
| let sqrt_10005 = work_context | ||
| .sqrt(&work_context.convert_int::<B>(10005.into()).value().repr) | ||
| .value(); | ||
| let sqrt_10005 = |
There was a problem hiding this comment.
- The code of calculation of pi are duplicate in consts.ts and cache.rs
- the constant 10005 here don't need rounding (unnecessary)
| } else { | ||
| Repr::zero() | ||
| }; | ||
| Ok(FBig::<R, B>::new(zero, *ctx).with_precision(ctx.precision)) |
There was a problem hiding this comment.
The last with_precision call is unnecessary. new() already ensures this
bench_pi_sqrt::bench_pi_vs_sqrt10005 measures how much of the Chudnovsky π computation is spent on its sqrt(10005) sub-computation. Result: sqrt(10005) (Karatsuba square root) is ~1–10% of π's time across 50–5000 digits, so caching it would be a modest win; the dominant cost remains the binary-splitting series, which ConstCache already amortizes. Co-Authored-By: Claude <noreply@anthropic.com>
| /// Calculate the cosine of the floating point representation. | ||
| #[must_use] | ||
| pub fn cos<const B: Word>(&self, x: &Repr<B>) -> FpResult<B> { | ||
| pub fn cos<const B: Word>( |
There was a problem hiding this comment.
I think we still need #[must_use] tag for functions using the cache.
ConstCache now caches floor(√10005 · 2^bits) as a base-free integer (computed via Karatsuba UBig::sqrt, O(M(n))), reused by π. Newton refinement would be no faster since UBig::sqrt is already optimal, so extension just recomputes. The isqrt is folded into the π integer ratio π = (426880 · isqrt_val · Q) / (T · 2^isqrt_bits), which reuses the fast convert_int path for Q and T and avoids any cross-base conversion of √10005 (an earlier with_base approach was ~O(p²) and thousands of times slower). When a higher-precision isqrt is already cached, a lower-precision π request reuses it (the extra bits act as guard digits); the exponent tracks the isqrt's own scaling. - ConstCache gains sqrt_10005 / sqrt_10005_bits fields; total_words() counts the isqrt; total_terms() is unchanged (it isn't a series); clear() resets it. - New tests cover caching/reuse, total_words accounting, and clear(). - Ignored benches (bench_pi_vs_sqrt10005, bench_pi_repeat_call) measure the √10005 share of π and the warm-call speedup (~20× at 500 digits). Co-Authored-By: Claude <noreply@anthropic.com>
| impl<R: Round> Context<R> { | ||
| // Convert the [Repr] from base B to base NewB, with the precision under the target base from this context. | ||
| #[allow(non_upper_case_globals)] | ||
| fn convert_base<const B: Word, const NewB: Word>(&self, repr: Repr<B>) -> Rounded<Repr<NewB>> { |
There was a problem hiding this comment.
Shall we also return FpResult in convert_base, since overflow and underflow can happen
| /// Attach the dividend/divisor XOR sign to a zero quotient: the raw quotient significand is | ||
| /// `+0`, so the sign of a zero result (`0/finite`, or a finite/finite that rounds to zero) is | ||
| /// `sign(lhs) XOR sign(rhs)`. | ||
| fn div_repr<const B: Word>(sign_negative: bool, significand: IBig, exponent: isize) -> Repr<B> { |
There was a problem hiding this comment.
need a better name, make_div_repr?
| /// `ln(-x)`, `asin(|x| > 1)`, `pow(negative, non-integer)`, an even root of a negative value. | ||
| OutOfDomain, | ||
|
|
||
| /// An indeterminate form, e.g. `0 / 0`. |
There was a problem hiding this comment.
need to check whether x / 0 all goes to indeterminate. If so, document here
| forward_to_context!(exp); | ||
| forward_to_context!(exp_m1); | ||
|
|
||
| /// Square root (see [`Context::sqrt`]). |
There was a problem hiding this comment.
Why can't we wrap sqrt, and inv?
| // TODO: test if we can use log_B(p/2log_B(n)) directly | ||
| let guard_digits = (self.precision.log2_est() / B.log2_est()) as usize; | ||
| let work_context = Self::new(self.precision + guard_digits + 2); | ||
| let n: u32 = (&n).try_into().expect("iacoth argument must fit in u32"); |
There was a problem hiding this comment.
We lost the nice graphic comment
|
|
||
| /// Raw product of two finite reprs, attaching the XOR sign of the operands to a zero product | ||
| /// (the significand product alone is `+0`, losing the sign). | ||
| fn mul_finite_reprs<const B: Word>(lhs: &Repr<B>, rhs: &Repr<B>) -> Repr<B> { |
There was a problem hiding this comment.
make naming consistent: make_mul_repr?
| impl<R: Round, const B: Word> FBig<R, B> { | ||
| /// Return `±0` carrying the same sign as `self` (used by trunc/round/fract where a zero | ||
| /// result inherits the sign of the input). The result has precision 0, matching `FBig::ZERO`. | ||
| fn signed_zero(&self) -> Self { |
There was a problem hiding this comment.
These two helper functions seems very redundant, can we have something like UBig::with_sign()?
| @@ -0,0 +1,187 @@ | |||
| //! Tests for the `FpResult` contract: infinite inputs → `Err`, infinite outputs → `Ok(±inf)`, | |||
There was a problem hiding this comment.
Most of the testing cases should be moved to the test cases per operations.
| - Fix rounding issues in `to_f32()` and `to_f64()` ([#53](https://github.com/cmpute/dashu/issues/53), [#56](https://github.com/cmpute/dashu/issues/56)). | ||
| - Fix several rounding bugs in `FBig`/`Context` addition and subtraction: severe-cancellation collapse, spurious-ULP errors from negligible operands, the window-edge boundary, and `Context::sub` with a zero left operand under directed rounding modes. | ||
| - Fix `FBig::fract()` inflating context precision and `split_at_point_internal` using an incorrect fractional scale for values smaller than one. | ||
| - Fix rounding issues in `to_32()` and `to_f64()` (fixes [#53](https://github.com/cmpute/dashu/issues/53) and [#56](https://github.com/cmpute/dashu/issues/56)). |
There was a problem hiding this comment.
We shouldn't touch the existing Changelog items
…MPC oracle
- `tests/special_values.rs`: expanded Annex-G coverage — `proj` (Riemann collapse),
`conj` with infinities, `arg` of imaginary infinities, the signed-zero
`log(-r ± i0) = ln r ± iπ` branch cut, and `sqrt(-∞) = 0 + i·∞`.
- `benches/` (criterion, `required-features = ["rand"]`, not run in CI): `arith`
(`mul`/`div`/`sqr` over precisions {53,113,256,1024}), `transcendental`
(`exp`/`log`/`sin`/`cos`/`sqrt`/`abs`/`arg`), and `io` (`Display`/`FromStr`),
each with a `random_cbig` helper (log-scale sizing, `StdRng::seed_from_u64(1)`).
- `fuzz/tests/cmplx_random.rs`: a `rug::Complex` (GNU MPC) differential oracle for
field arithmetic at 53-bit precision — `mul`/`div`/`sqr` match MPC within a few
ulps over 8192 random cases (manual: `--ignored`). `dashu-cmplx` added to fuzz deps.
The pre-existing fuzz tests (`add_random`, `trig_random`) are stale against the
post-#83 `FpResult` (unchanged here); the new oracle compiles and passes standalone.
Co-Authored-By: Claude <noreply@anthropic.com>
…d.yml The workspace-excluded `fuzz/` crate had not compiled since the #83 FpResult change (its tests targeted the pre-#83 `Context::sin(f)`/`FpResult::Normal` API). Restore it and convert the differentials to proptest. Fuzz crate (step 1): - Port every test to the current `Context`/`FpResult` API (`ctx.sin::<B>(&x, None).unwrap().value()`; `FpResult` is `Result<Rounded<_>, FpError>`). - Replace the `for _ in 0..N { rng… }` loops with proptest strategies (`fuzz_config` = 1024 cases, overridable via `PROPTEST_CASES`) so a mismatch shrinks to a minimal counterexample. Shared strategies live in `fuzz/src/lib.rs`. - `add_random`: dispatches all six rounding modes per case, precision biased toward 1/2/3. `trig_random`: five broad differentials (sin/cos/tan/atan2/ inv-trig) in proptest + three deterministic sweeps (pi/asin-near-1/large-exp tan) ported; `atan2(0,0)` indeterminate handled by skipping. `cmplx_random`: MPC mul/div/sqr over f64 pairs. Trig tests use `HalfAway` to match `DBig`. All remain `#[ignore]`d (manual, release-time — they link `rug` and run long). CI (step 2): - New `.github/workflows/build.yml` for compile-only checks: a `fuzz-check` job (cargo check the fuzz crate so it can't silently rot again — tests stay manual), plus `build-benchmark` and `build-aarch64` moved out of `tests.yml`, plus the `check` (cargo check, stable/1.85/1.68 MSRV matrix) job. - `tests.yml` now runs only the test matrix + x86/x86_64/no-std tests + fmt + clippy. Co-Authored-By: Claude <noreply@anthropic.com>
* Add complex crate design
* Update the CBig design
* Update TODO
* Further update TODO
* Update design
* Update AbsEq note in TODO-cmplx to reflect Phase 1 removal
AbsEq was folded into AbsOrd and removed in Phase 1 (not merely
deprecated); reword the §5.3 parenthetical to match.
Co-Authored-By: Claude <noreply@anthropic.com>
* Refine v0.5/cmplx design docs
TODO-v05.md:
- Note the full C <tgmath.h> type-generic math surface as a long-term
goal, explicitly out of scope for 0.5 and 0.5.x.
TODO-cmplx.md:
- unwrap_cfp: define dashu-cmplx's own Context::unwrap_cfp (the float
unwrap_fp is pub but typed for FpResult<FBig>, unusable for CfpResult).
- Conversions: replace from_real/from_int with From<FBig>/From<UBig>/
From<IBig> and add TryFrom<CBig> for FBig/IBig, mirroring FBig's
"Convert from/to UBig/IBig" pattern.
- hypot: make FBig::hypot (overflow-safe scaled sum-of-squares) a
prerequisite dashu-float step; CBig::abs becomes a thin composition.
Co-Authored-By: Claude <noreply@anthropic.com>
* Minor improvement
* dashu-cmplx (M1): skeleton, type model, and easy operations
Add the new `dashu-cmplx` crate (the headline 0.5 feature) with the `CBig`
arbitrary-precision complex type built on `dashu-float`'s `FBig`.
Type & context model (§2):
- `CBig<R: Round = Zero, const B: Word = 2>` — two `Repr<B>` parts over a single
shared `dashu_cmplx::Context<R>` newtype, mirroring `FBig`'s `Repr`+`Context`
layout (the uniform-precision invariant is structural).
- `Context<R>` wraps `dashu_float::Context<R>` and hosts the context-layer ops;
`CRounded`/`CfpResult` carry per-axis `(Rounding, Rounding)` inexactness.
Construction/conversion (§5.1): `from_parts`/`re`/`imag`/`into_parts`, the
`ZERO`/`ONE`/`I` constants, `From<FBig>`/`From<UBig>`/`From<IBig>`, and the lossy
`TryFrom<CBig> for FBig`/`IBig` (composing CBig → FBig → IBig).
Easy ops (single-pass rounding): add/sub (operators only, calling the context
layer — matching `FBig`'s no-inherent-add pattern), `neg`/`conj`/`proj`/`mul_i`
(exact), `norm` (squared modulus) and `arg` (Annex-G `atan2`).
Comparison (§5.3): componentwise `Eq`, lexicographic `Ord`, `AbsOrd` via `|z|²`,
and `NumOrd`/`NumHash` (behind `num-order`).
I/O (§5.6): algebraic `a+bi` `Display`/`FromStr` (MPC's parenthesized form is
rejected) and structured `Debug`.
Workspace + meta-crate wiring: `complex/` added to the workspace, the meta-crate
gains `dashu::complex` and the `dashu::Complex = CBig` alias, and `dashu-cmplx`
is wired into the shared feature-forwarding lines.
Co-Authored-By: Claude <noreply@anthropic.com>
* dashu-cmplx (M2): mul/div/sqr/inv + abs, and FBig::hypot
dashu-float prerequisite:
- `FBig::hypot` / `Context::hypot`: overflow-safe `sqrt(a²+b²)` via the scaled
sum-of-squares (the larger operand is never squared; `m·sqrt(1+r²)` with
`r=min/max ∈ [0,1]`). `hypot(±inf,·)=+inf`, `hypot(0,0)=+0`. This is the kernel
`CBig::abs` composes, landed on FBig so it is a first-class real op too.
CBig field arithmetic (near-correctly rounded via the §6.1 guard-digit recipe —
evaluate each component at `p+g`, re-round to `p`; no Ziv retry):
- `sqr` `(x²-y²)+i(2xy)`, `mul` `(xu-yv)+i(xv+yu)` (naive 4-mul),
`div` (Smith's overflow-safe method, `|u|≥|v|` branch), `inv` `conj(z)/|z|²`.
- `abs` = `ctx.hypot(re, im)` at guard precision.
- Scalar mul/div by a real `FBig` via mixed-type operators (`z*r`, `z/r`, `r*z`,
`r/z`) — no named methods (Decision 9).
Annex-G special-value short-circuits: `0·∞`/`0/0`/`∞/∞` → `Indeterminate`;
`z/0`/`∞·finite` → the Riemann point at infinity (`+∞+i·0`); `finite/∞`/`0/finite`
→ 0. (dashu's convention: complex infinity is the single Riemann point produced
by `proj`.)
Tests:
- `tests/special_values.rs` — exact Annex-G vectors for mul/div/inv.
- `tests/arith_prop.rs` — exact identity proptests (commutativity, z+0=z, z-z=0,
z·1=z, z·0=0, mul_i⁴=id, conj²=id, proj idempotent, z+(-z)=0).
- `tests/rounding_prop.rs` — correct-rounding self-oracle (mul/sqr/div/abs at `p`
vs `2p` re-rounded, ≤1 ulp/component) and `z·conj(z)=norm`.
Co-Authored-By: Claude <noreply@anthropic.com>
* dashu-cmplx (M3): sqrt, exp, log, and powers
- `sqrt` (principal branch, cut `]−∞, 0]`): cancellation-free form — for
`x ≥ 0`, `a = sqrt((r+x)/2)` and `b = y/(2a)`; for `x < 0`, `b` from `(r-x)/2`
and `a = y/(2b)`. Avoids the `(r−x)` catastrophic cancellation when `|y|≪|x|`.
Annex-G specials: `sqrt(±0)=±0`, `sqrt(+∞+iy)=+∞+i·0`, `sqrt(-∞+iy)=0+i·sign(y)∞`.
- `exp(x+iy) = e^x·(cos y + i sin y)` reusing FBig `exp`/`sin_cos`; `exp(0)=1`,
`exp(+∞+ifinite)=+∞`, `exp(-∞+ifinite)=0`, infinite imaginary → `Indeterminate`.
- `log z = ln|z| + i·arg z` (Im ∈ `]−π,π]`); `log(0)=-∞`, `log(∞)=+∞`, cut `]−∞,0]`.
- `powi` (integer exponent, repeated squaring, branch-cut-free) and `powf`
(`exp(w·log z)` at `p+POWF_GUARD`, re-rounded); `powf(z,0)=1` incl. `powf(0,0)=1`.
The transcendental context ops (`exp`/`log`/`powf`/`arg`) now carry
`cache: Option<&mut ConstCache>` (threaded via a centralized `reborrow_cache`
helper), matching the spec so the deferred `CachedCBig` needs no signature change.
Tests:
- `tests/transcendental_prop.rs` — sqrt/exp/log self-oracles (p vs 2p, ≤2 ulp),
`sqrt(conj z)=conj(sqrt z)`, and `log z = ln|z| + i·arg z`.
- `tests/special_values.rs` — extended with sqrt/exp/log special-value vectors.
Co-Authored-By: Claude <noreply@anthropic.com>
* dashu-cmplx (M4): trig and inverse trig
Forward trig via the real–imaginary decomposition, reusing `dashu-float`'s real
`sin`/`cos` and cancellation-free `sinh`/`cosh` (avoids the `exp(±iz)` blow-up for
large `|Im z|`):
- `sin(x+iy) = sin x·cosh y + i·cos x·sinh y`
- `cos(x+iy) = cos x·cosh y − i·sin x·sinh y`
- `sin_cos` (shared reduction), `tan = sin/cos`. Infinite input → `Indeterminate`.
Inverse trig (Kahan log forms; the inner `log` argument always has positive real
part, so the cut comes from the `sqrt`):
- `asin z = -i·log(iz + sqrt(1-z²))`, `acos z = -i·log(z + i·sqrt(1-z²))`,
`atan z = (i/2)·(log(1-iz) - log(1+iz))`.
dashu-float: `FBig::from_repr`'s debug assert now accepts the documented single
guard digit (`precision + 1`), so `CBig::into_parts` doesn't panic on a guard-digit
part produced by an inexact add.
Tests: `transcendental_prop.rs` extended with sin/cos/asin/atan self-oracles
(p vs 2p) and the pythagorean identity (modest-range strategy, since `cosh²y`/
`sinh²y` cancel for large `|Im z|`).
Co-Authored-By: Claude <noreply@anthropic.com>
* dashu-cmplx (M5): hardening — Annex-G specials, benches, and the rug/MPC oracle
- `tests/special_values.rs`: expanded Annex-G coverage — `proj` (Riemann collapse),
`conj` with infinities, `arg` of imaginary infinities, the signed-zero
`log(-r ± i0) = ln r ± iπ` branch cut, and `sqrt(-∞) = 0 + i·∞`.
- `benches/` (criterion, `required-features = ["rand"]`, not run in CI): `arith`
(`mul`/`div`/`sqr` over precisions {53,113,256,1024}), `transcendental`
(`exp`/`log`/`sin`/`cos`/`sqrt`/`abs`/`arg`), and `io` (`Display`/`FromStr`),
each with a `random_cbig` helper (log-scale sizing, `StdRng::seed_from_u64(1)`).
- `fuzz/tests/cmplx_random.rs`: a `rug::Complex` (GNU MPC) differential oracle for
field arithmetic at 53-bit precision — `mul`/`div`/`sqr` match MPC within a few
ulps over 8192 random cases (manual: `--ignored`). `dashu-cmplx` added to fuzz deps.
The pre-existing fuzz tests (`add_random`, `trig_random`) are stale against the
post-#83 `FpResult` (unchanged here); the new oracle compiles and passes standalone.
Co-Authored-By: Claude <noreply@anthropic.com>
* dashu-cmplx (M6): cbig! macro, guide chapter, and 0.5.0 version sync
cbig! literal macro (dashu-macros):
- `cbig!` / `static_cbig!` (+ `cbig_embedded` / `static_cbig_embedded`) accepting the
algebraic `a+bi` form or a `re, im` pair; each coefficient reuses the `fbig!` base-2
parser. The static variant builds via a new `CBig::from_repr_parts` const constructor
(each Repr via `from_static_words`/`Repr::zero()`), gated on Rust 1.64+ like the other
static macros. Exposed as `dashu::cbig!` in the meta-crate.
Guide: a "Complex Numbers" chapter (construction, two-layer API, no-NaN special-value
model, near-correct rounding), linked from SUMMARY.md.
Version sync (Phase 5): all crates bumped to 0.5.0 (base/int/float/ratio/macros 0.4.x →
0.5.0; complex already 0.5.0) and every inter-crate dependency version string updated;
changelels consolidated (Unreleased → 0.5.0).
Co-Authored-By: Claude <noreply@anthropic.com>
* ci: extend the MSRV dep-drop script for dashu-cmplx
The MSRV (1.68) job strips rand 0.9/0.10 (which need newer Rust) from the
manifests before `cargo check --features rand`. The new `dashu-cmplx` crate
wasn't in that list, and it forwards `rand_v09 → dashu-float/rand_v09`, while
the meta-crate's `rand` feature still pointed at `dashu-cmplx/rand` — so after
the strip, resolution failed with "dashu-float does not have feature rand_v09".
Mirror the existing handling: strip `rand_v09`/`rand_v010` (and complex's
`rand` alias + the `required-features = ["rand"]` benches) for the MSRV build,
and drop the `dashu-cmplx/rand` forwarding from the meta-crate's `rand` line.
Stable/1.85 still exercise the full rand surface via `--all-features`.
Verified by running the script + the exact MSRV check command locally
(`cargo check --workspace --exclude dashu-python --features
"std,num-order,serde,zeroize,rand,num-traits_v02"` builds cleanly).
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cmplx): import alloc::format in the no_std test modules
The inline Display/FromStr unit tests use format!, which is not in the prelude
under no_std. The fmt/parse test modules now `use alloc::format;` (the same fix
dashu-ratio uses), so `cargo test --no-default-features --features rand` compiles.
Verified: `cargo test --no-default-features --features rand --workspace
--exclude dashu-python` passes; the all-features (std) build and clippy are
unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(cmplx): rand support for CBig (rand = rand_v08 default)
Add random generation for CBig, mirroring dashu-float/ratio:
- Features: `rand = ["rand_v08"]` (default alias), with `rand_v09`/`rand_v010`
opt-in — matching integer/float/rational, and MSRV-compatible (rand 0.8 runs
on 1.68, so the MSRV `rand` build no longer needs a complex-specific carve-out).
- `third_party/rand.rs`: `UniformCBig<R,B>` samples the box `[low, high)` (each
part an independent `UniformFBig` range). `rand_v08`/`rand_v09`/`rand_v010`
add the `Distribution<CBig>` impls; `Standard`/`StandardUniform`/`Open01`/
`OpenClosed01` sample the unit square `[0,1)²` (each part uniform in `[0,1)`).
No bespoke sampling algorithm — it reuses `dashu_float::rand::UniformFBig`.
- No single-axis `Uniform`/`SampleUniform` (complex has no interval order); the
box `UniformCBig::new(low, high, precision)` is the ranged sampler.
The MSRV dep-drop script is simplified back to just adding `complex/Cargo.toml`
to the existing rand_v09/rand_v010 strip loop (complex now keeps `rand = rand_v08`).
Tests: `tests/random.rs` (the `Standard` unit square, the `UniformCBig` box, and
`Open01` excludes zero). Verified across rand 0.8/0.9/0.10, `no_std`, and the
MSRV check command.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cmplx): CBig NumHash consistent with num-complex + num-order
The old CBig NumHash hashed re and im sequentially (tuple-style), inconsistent
with num-order's native Complex<f64> hashing (algebraic combination of the
per-part field elements).
CBig NumHash now mirrors num-order's "Case 4": treat z = a + b·i and write
hash(a + bterm) where bterm = ∓PROOT²·b² (sign of b), with a, b the per-part
residues. This keeps a CBig and a num-complex Complex<f64> of the same value
in sync.
dashu-float changes (behind `num-order`):
- `Repr::num_hash_residue(&self) -> i128`: the numeric-hash field element
(mod 2¹²⁷−1), extracted from `Repr::num_hash` so composite types can reuse
it. For base-2 uses the same `absm(&127)` exponent reduction as num-order's
f64 fhash.
- ±∞ now map to `HASH_INF`/`HASH_NEGINF` (= ±M127), matching num-order's
f64 fhash. The subsequent `i128::num_hash` still reduces these to 0 (since
M127 ≡ 0 mod M127), so the *final* hash of a real ±∞ is unchanged — but the
*residue* now distinguishes them so CBig's algebraic combination is correct
for complex numbers with infinite parts. ±0 still maps to 0.
- `test_fbig_num_hash_matches_f64` verifies FBig↔f64 consistency including
±∞ and -0.
dashu-cmplx changes:
- `_num-modular` added to the `num-order` feature.
- `CBig::num_hash` rewritten to the algebraic formula.
- `cbig_num_hash_matches_num_complex` cross-checks against the transcribed
num-order Complex<f64> formula for 7 sample values.
Co-Authored-By: Claude <noreply@anthropic.com>
* Restructure dashu-cmplx modules to mirror dashu-float
Align dashu-cmplx's module layout and operator-impl style with dashu-float.
Pure restructuring; no behavioral change.
- Merge `sub.rs` into `add.rs` and `power.rs` into `exp.rs`, matching float's
single-file grouping: add+sub share one sign-parameterized kernel
(`signed_add`, sub = add with negated rhs), and `exp` hosts the power family.
- Rewrite the `CBig·CBig` `Mul`/`MulAssign` impls (and `add.rs`'s
`AddAssign`/`SubAssign`) as explicit ref/val impls plus a new
`impl_binop_assign_by_taking!` macro, mirroring float's `mul.rs`/`add.rs`
instead of the one-shot `impl_cbig_binop!` mega-macro.
- Promote `trig.rs` to a `math/` submodule (`math/{mod,trig}`), matching
float's `pub mod math`.
- Rename `context.rs` to `repr.rs` (float keeps `Context` in `repr.rs`),
updating every `crate::context::` path to `crate::repr::`.
Co-Authored-By: Claude <noreply@anthropic.com>
* Tighten TODO-v05.md: mark dashu-cmplx done, prune resolved items
- Phase 3 (dashu-cmplx): mark ✅ Implemented (M1–M6); fold the detailed
TODO-cmplx.md design doc into a consolidated §3.4 deferred-to-0.5.x list
(Ziv, hyperbolics, fma, CachedCBig forward-compat note, CRound, etc.).
TODO-cmplx.md is removed.
- Phase 0: drop the "record baseline benchmark" item — results are
hardware-dependent and won't be committed; note the decision inline.
- Phase 1 §1.5: replace vague prose with four concrete checkboxes (guide
prose migration + the pow/div/exp algorithm TODOs), line refs updated.
- Remove the "Open Decisions" section — all four (CBig scope, serde
padding, proptest, MSRV) are resolved and documented in their phases.
Co-Authored-By: Claude <noreply@anthropic.com>
* Promote the sin many-digit rounding reproducer to CI tests
The `Context::sin` 49-digit-significand rounding regression (found during
fuzzing) lived only in the workspace-excluded `fuzz/` crate, so it never ran in
CI. Move it into float's inline trig tests as
`test_sin_many_digit_rounding_no_panic` (rewritten to the current
`Context::sin` API), and consolidate the pythagorean identity proptest to sweep
precisions {20, 50, 100} — a strict superset of the former single-precision
version, replacing the complementary fuzz sweep that was deleted.
Co-Authored-By: Claude <noreply@anthropic.com>
* Rewrite fuzz differentials in proptest; split build-only CI into build.yml
The workspace-excluded `fuzz/` crate had not compiled since the #83 FpResult
change (its tests targeted the pre-#83 `Context::sin(f)`/`FpResult::Normal`
API). Restore it and convert the differentials to proptest.
Fuzz crate (step 1):
- Port every test to the current `Context`/`FpResult` API (`ctx.sin::<B>(&x,
None).unwrap().value()`; `FpResult` is `Result<Rounded<_>, FpError>`).
- Replace the `for _ in 0..N { rng… }` loops with proptest strategies
(`fuzz_config` = 1024 cases, overridable via `PROPTEST_CASES`) so a mismatch
shrinks to a minimal counterexample. Shared strategies live in `fuzz/src/lib.rs`.
- `add_random`: dispatches all six rounding modes per case, precision biased
toward 1/2/3. `trig_random`: five broad differentials (sin/cos/tan/atan2/
inv-trig) in proptest + three deterministic sweeps (pi/asin-near-1/large-exp
tan) ported; `atan2(0,0)` indeterminate handled by skipping. `cmplx_random`:
MPC mul/div/sqr over f64 pairs. Trig tests use `HalfAway` to match `DBig`.
All remain `#[ignore]`d (manual, release-time — they link `rug` and run long).
CI (step 2):
- New `.github/workflows/build.yml` for compile-only checks: a `fuzz-check` job
(cargo check the fuzz crate so it can't silently rot again — tests stay
manual), plus `build-benchmark` and `build-aarch64` moved out of `tests.yml`,
plus the `check` (cargo check, stable/1.85/1.68 MSRV matrix) job.
- `tests.yml` now runs only the test matrix + x86/x86_64/no-std tests + fmt +
clippy.
Co-Authored-By: Claude <noreply@anthropic.com>
* Note SIMD-optimized FFT multiplication as a v1.0 item
Co-Authored-By: Claude <noreply@anthropic.com>
* Add comprehensive fuzz oracles vs GMP/MPFR/MPC (step 3)
Four new proptest-driven test files in the workspace-excluded `fuzz/` crate,
differential against `rug` (GMP/MPFR/MPC) to complete the correctness-oracle
coverage:
- `transcendental.rs` — all 17 non-trig FBig transcendetals (exp/exp_m1,
ln/ln_1p/sqrt/cbrt/nth_root/hypot/atan/powf/powi + the hyperbolic family),
compared vs `rug::Float` (MPFR) with a `within_k_ulps(k=2)` tolerance
(dashu near-correct, MPFR Ziv-correct → ≤1 ulp is legitimate).
- `cmplx_transcendental.rs` — 10 CBig transcendetals (exp/log/sqrt/
sin/cos/tan/asin/acos/atan/powf) vs `rug::Complex` (MPC) at 53-bit,
reusing the shared `close()` helpers.
- `integer.rs` — 15 UBig/IBig ops (mul/sqr/gcd/div_rem/pow/sqrt/nth_root/
bit-ops/shifts) vs `rug::Integer` (GMP) via exact decimal-string round-trip.
- `ratio.rs` — 8 RBig ops (add/sub/mul/div/sqr/pow/inv/reduce) vs
`rug::Rational` (GMP mpq) via exact canonical-form comparison.
Shared infra: `fuzz/src/lib.rs` gains `ubig_strategy`, `pos_dbig_strategy`,
`unit_dbig` (promoted from trig_random), and a `cmplx` module holding the
53-bit build/compare helpers (moved from cmplx_random). The fuzz crate now
depends on `dashu-ratio`.
Bug fix discovered by the integer oracle:
- `UBig/IBig::nth_root(0, n)` (and `cbrt(0)`) returned 1 instead of 0:
the `bits <= n` shortcut in `integer/src/root_ops.rs` fired for the zero
input (bit_len == 0). Fixed with a zero guard. Regression test added.
Co-Authored-By: Claude <noreply@anthropic.com>
* Revert version changes
* Add FBig::sinh_cosh for combined sinh+cosh computation
- Add Context::sinh_cosh(x, cache) -> (FpResult, FpResult) in float/src/math/hyper.rs,
sharing the exp_m1(±x) sub-computations to roughly halve the cost vs separate calls.
- Add FBig::sinh_cosh() -> (Self, Self) convenience method with doc example.
- Add CachedFBig::sinh_cosh forwarding (mirrors sin_cos pattern, threads ConstCache).
- Use sinh_cosh in complex CBig::sin_cos kernel instead of separate sinh+cosh calls.
- Add sinh_cosh_fuzz proptest oracle vs rug's independent sinh/cosh (within 2 ulps).
Co-Authored-By: Claude <noreply@anthropic.com>
* Address review feedback: renames, ownership patterns, and cleanup
- log() → ln() on CBig, matching FBig's naming; context-layer stays log()
- imag() → im() across all call sites
- Remove from_repr_parts; make new() pub (identical code)
- Inline is_numeric_zero helper at call sites
- Inline trig.rs helper fns (cbig_one, cbig_real, reround, …)
- Add #[inline] to Context::sin/cos
- Replace CBig::inv() with Inverse trait impl (matching FBig)
- Split add.rs signed_add into per-ownership kernel functions
- Use impl_cbig_binop! macro for Mul (matching Div)
- Unroll parameterless impl_scalar_mul!/impl_scalar_div! macros
- Fix abs_ge to avoid cloning Repr parts
- Fix overflow: both parts +∞ instead of ±∞ + i·0
- Extract bterm helper; test shares it instead of duplicating formula
- Add Uniform01<BASE> struct in rand.rs for complex unit-square sampling
- Fix copyright year (2026), use IBig::ONE/NEG_ONE constants
- Update CHANGELOG with all review-driven changes
Co-Authored-By: Claude <noreply@anthropic.com>
* Address additional review feedback: inline abs_repr, delete guide, rename cbig→cmplx
- Inline abs_repr helper in float/src/root.rs (clone avoidance)
- Delete premature guide/src/complex.md (deferred to post-merge)
- Rename macros/src/parse/cbig.rs → cmplx.rs for naming consistency
Co-Authored-By: Claude <noreply@anthropic.com>
* Address second batch of review feedback
- Simplify trig constant construction (CBig::ONE and IBig::from(2).into())
- Use impl_cbig_binop! for Add/Sub (replace kernel functions)
- Add impl_cbig_scalar_binop! macro for CBig/FBig scalar ops
- Remove unused impl_binop_assign_by_taking macro
- Replace abs_ge with inline FBig::abs_cmp at call sites
- Restore signed-zero preservation in underflow
- Fix fuzz: rug::Complex::imag() accidentally renamed to im()
- Fix formatting in float/src/root.rs
- Update CHANGELOG to reflect current state
Co-Authored-By: Claude <noreply@anthropic.com>
* Note: expose ownership-aware kernel fns from dashu-float for dashu-cmplx
Add a deferred item under Phase 3.4 to make float's add_val_val / add_val_ref /
add_ref_val / add_ref_ref kernel functions (and equivalents for sub/mul/div)
public, so dashu-cmplx can call them directly in its own per-ownership operator
impls rather than immediately borrowing through the complex Context.
Co-Authored-By: Claude <noreply@anthropic.com>
* Use live num_complex::Complex64 NumHash in test instead of manual formula
Make num-complex_v04 a non-optional regular dependency and enable
num-order/num-complex so Complex<f64>: NumHash is available in unit
tests. The cbig_num_hash_matches_num_complex test now calls
residue(&Complex64::new(re, im)) directly against num-order's actual
implementation — no duplicated bterm transcription.
Co-Authored-By: Claude <noreply@anthropic.com>
* Add num-complex conversions; decouple num-complex from num-order
- New `num-complex` feature providing TryFrom conversions between CBig and
num-complex's Complex<f32>/Complex<f64> (base-2, composing through FBig):
exact lift with NaN -> OutOfBounds, infinities/signed zeros preserved;
rounding back errors on overflow or inexactness — mirroring FBig's
primitive-float TryFrom pair.
- The `num-order` feature no longer enables `num-order/num-complex`, and
num-complex is no longer an unconditional dependency: it is now an opt-in
normal dep behind `num-complex`, plus a dev-dependency (the num-order
NumHash test compares against num-complex's live Complex<f64> reference,
and the new conversion tests use it). Enabling only `num-order` (the
default) therefore no longer drags num-complex into the dependency tree.
Co-Authored-By: Claude <noreply@anthropic.com>
* Add cargo doc CI guard; fix workspace intra-doc links
- New `rustdoc` job in build.yml runs `cargo doc --workspace` with
`-D warnings` (stable, --all-features). The meta-crate `dashu` and
`dashu-python` both name their lib `dashu`, so they collide on the output
path and are documented in two invocations (both are checked).
- Fix 9 broken intra-doc links surfaced by the check: dashu-cmplx
(Context::guard pub(crate) link, dashu_float::Rounded path), dashu-float
(Exact/Inexact variant paths, FpError::InfiniteInput crate path,
static_fbig! external macro -> plain code), dashu-int (MontgomeryRepr
self link), dashu-ratio (Display path).
Co-Authored-By: Claude <noreply@anthropic.com>
* Rename guide ieee754.md to compliance.md; add C99 Annex G section
The compliance page now covers two aspects of standards conformance:
dashu-float's FBig vs IEEE 754-2008 (the former ieee754.md content,
demoted one heading level) and dashu-cmplx's CBig vs C99 Annex G
(IEC 60559 complex) — data model, arithmetic, branch cuts, and the
no-NaN / Riemann-point policy, sourced from the crate's actual behavior.
Also updates the SUMMARY.md link and a stale pointer in TODO-v05.md.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Jacob Zhong <jacob@rimbot.com>
Co-authored-by: Claude <noreply@anthropic.com>
No description provided.