Skip to content

Implement complex numbers#85

Open
cmpute wants to merge 26 commits into
developfrom
cplx
Open

Implement complex numbers#85
cmpute wants to merge 26 commits into
developfrom
cplx

Conversation

@cmpute

@cmpute cmpute commented Jun 26, 2026

Copy link
Copy Markdown
Owner

No description provided.

Comment thread TODO-cmplx.md Outdated
Comment thread TODO-cplx.md Outdated
Comment thread TODO-cplx.md Outdated
Comment thread TODO-cplx.md Outdated
Comment thread TODO-cplx.md Outdated
Comment thread TODO-cplx.md Outdated
Comment thread TODO-cplx.md Outdated
Comment thread TODO-cplx.md Outdated
Comment thread TODO-cplx.md Outdated
Comment thread TODO-cplx.md Outdated
Jacob Zhong and others added 24 commits June 26, 2026 23:05
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>
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>
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-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>
- `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>
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>
…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>
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>
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>
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>
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>
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>
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>
- 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>
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>
…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>
Co-Authored-By: Claude <noreply@anthropic.com>
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>
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