Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4c6bdc9
Add mdBook guide infra: KaTeX preprocessor + CI build-check
Jun 30, 2026
6ee05c0
Guide Batch 1: foundation pages — types/convert/construct
Jun 30, 2026
04f84a1
Guide Batch 2: I/O pages — parse/print/serialize/interop/index
Jun 30, 2026
a08aded
Guide Batch 3: Operations pages (7, incl. new trig_n_hyper)
Jun 30, 2026
19652f2
Guide Batch 4: reference pages — faq, cheatsheet
Jun 30, 2026
cf4a3c1
Guide Batch 5: KaTeX retrofit + SUMMARY cleanup
Jun 30, 2026
caa0156
types.md: restyle Complex section as "Layout of CBig"; add ConstCache…
Jul 6, 2026
45694c8
Guide: demote all page headings by one level (H1 → H2)
Jul 6, 2026
b290a16
Add CBig::NEG_ONE; document it in the guide
Jul 6, 2026
8202e31
Guide: split Cached Arithmetic into its own chapter
Jul 6, 2026
d744959
Guide: promote cached.md subsections to H2 (now a top-level chapter)
Jul 6, 2026
49505f4
Guide/convert: fold CBig into the type-conversion table
Jul 6, 2026
3302b49
Implement TryFrom<CBig> for UBig
Jul 6, 2026
683381d
Implement CBig primitive conversions (both directions)
Jul 6, 2026
43a8e9d
Guide/convert: clarify CBig's float conversion in the primitive table
Jul 6, 2026
a46a1f0
Guide: drop duplicate page-title headings, promote subsections
Jul 6, 2026
6fdb288
Guide: use dashu::module:: paths instead of dashu_X::
Jul 6, 2026
ac7e552
Guide/print: move Debug Print to the end; show every type's debug output
Jul 6, 2026
7250fce
Guide/print: show {:#?} output for every numeric type
Jul 6, 2026
ced7f09
Guide: move "Two-layer API" from exp_log to types
Jul 6, 2026
f3933c3
Guide/num_theory: document the Montgomery reducer
Jul 6, 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
37 changes: 37 additions & 0 deletions .github/workflows/guide.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
on:
push:
branches:
- master
- develop
pull_request:
branches:
- master
- develop

name: Guide

# Build-check for the mdBook user guide under `guide/`. Renders the book with mdBook and the
# mdbook-katex preprocessor (KaTeX math typesetting). The build fails on errors and — because
# mdBook errors on any `SUMMARY.md` entry whose target file is missing — also guards against
# broken internal links. This phase is build-check only: nothing is deployed.
#
# Version note: mdBook's 0.5.x line changed the preprocessor wire protocol, so the *released*
# mdbook-katex crate (0.9.x, built against mdbook 0.4) cannot talk to mdBook 0.5. We therefore
# install mdBook's 0.5 release and mdbook-katex's mdbook-0.5 build from upstream git, both pinned
# for reproducibility. Bump both together when upgrading.

jobs:
guide:
name: Build guide (mdBook + KaTeX)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Install mdBook
run: cargo install mdbook --locked --version 0.5.3
- name: Install mdbook-katex (mdbook 0.5 build)
run: cargo install --locked --git https://github.com/lzanini/mdbook-katex --rev 1f63ab8605cea8e975be4b001dd7e39dd5367834
- name: Build guide
run: mdbook build guide
8 changes: 8 additions & 0 deletions complex/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased

### Add
- `CBig::NEG_ONE` (`-1 + 0i`), mirroring `FBig::NEG_ONE`.
- `TryFrom<CBig> for UBig` — extracts the unsigned integer when the value is purely real, finite,
integer-valued, and non-negative (composes `CBig → FBig → UBig`).
- Primitive conversions to/from `CBig`: `From` for the integer primitives (`u8`–`u128`, `i8`–`i128`),
`TryFrom<f32>`/`TryFrom<f64>` (base 2), and `TryFrom<CBig>` for every integer and float primitive
(float out is base-2). All compose through `FBig`, mirroring `dashu-float`'s primitive surface.

### Improve
- The complex `sin_cos` kernel now calls `dashu-float`'s combined `sinh_cosh` (new in `dashu-float`)
instead of separate `sinh` + `cosh` calls, sharing the `exp_m1(±y)` sub-computations.
Expand Down
5 changes: 5 additions & 0 deletions complex/src/cbig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ impl<R: Round, const B: Word> CBig<R, B> {
/// The complex number one `1 + 0i` (unlimited precision).
pub const ONE: Self = Self::new(Repr::one(), Repr::zero(), Context::new(0));

/// The complex number negative one `-1 + 0i` (unlimited precision).
pub const NEG_ONE: Self = Self::new(Repr::neg_one(), Repr::zero(), Context::new(0));

/// The imaginary unit `0 + 1i` (unlimited precision).
pub const I: Self = Self::new(Repr::zero(), Repr::one(), Context::new(0));

Expand Down Expand Up @@ -196,6 +199,8 @@ mod tests {
fn constants() {
assert!(C::ZERO.is_zero());
assert!(!C::ONE.is_zero());
assert!(!C::NEG_ONE.is_zero());
assert!(C::NEG_ONE != C::ONE);
assert!(!C::I.is_zero());
let (re, im) = C::I.into_parts();
assert!(re.repr().is_zero());
Expand Down
111 changes: 111 additions & 0 deletions complex/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,70 @@ impl<R: Round, const B: Word> TryFrom<CBig<R, B>> for IBig {
}
}

impl<R: Round, const B: Word> TryFrom<CBig<R, B>> for UBig {
type Error = ConversionError;

/// Extract an unsigned integer, succeeding only when the number is purely real, finite,
/// integer-valued, and non-negative. Composes [`CBig`] → [`FBig`] → [`UBig`].
#[inline]
fn try_from(z: CBig<R, B>) -> Result<Self, Self::Error> {
let re: FBig<R, B> = FBig::try_from(z)?;
UBig::try_from(re)
}
}

// Conversions between `CBig` and the integer primitives (both directions), composing through
// `FBig`. Integers embed as purely-real complex numbers; extraction succeeds only when the value
// is purely real, finite, in range, and integer-valued.
macro_rules! impl_cbig_int_conv {
($($t:ty)*) => {$(
impl<R: Round, const B: Word> From<$t> for CBig<R, B> {
#[inline]
fn from(v: $t) -> Self {
FBig::from(v).into()
}
}

impl<R: Round, const B: Word> TryFrom<CBig<R, B>> for $t {
type Error = ConversionError;

#[inline]
fn try_from(z: CBig<R, B>) -> Result<Self, Self::Error> {
let re: FBig<R, B> = FBig::try_from(z)?;
re.try_into()
}
}
)*};
}
impl_cbig_int_conv!(u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);

// Conversions between `CBig` and `f32`/`f64` (both directions, base 2 only). NaN is rejected on
// input; infinities are preserved. Extraction succeeds only when purely real and exactly
// representable.
macro_rules! impl_cbig_float_conv {
($($t:ty)*) => {$(
impl<R: Round> TryFrom<$t> for CBig<R, 2> {
type Error = ConversionError;

#[inline]
fn try_from(f: $t) -> Result<Self, Self::Error> {
Ok(CBig::from(FBig::try_from(f)?))
}
}

impl<R: Round> TryFrom<CBig<R, 2>> for $t {
type Error = ConversionError;

#[inline]
fn try_from(z: CBig<R, 2>) -> Result<Self, Self::Error> {
let re: FBig<R, 2> = FBig::try_from(z)?;
re.try_into()
}
}
)*};
}
impl_cbig_float_conv!(f32 f64);

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -115,4 +179,51 @@ mod tests {
let z = C::from_parts(9.into(), 1.into());
assert_eq!(IBig::try_from(z), Err(ConversionError::LossOfPrecision));
}

#[test]
fn try_from_ubig_composes() {
let z: C = IBig::from(9).into();
let u: UBig = UBig::try_from(z).unwrap();
assert_eq!(u, UBig::from(9u8));

// negative real part → OutOfBounds
let z: C = IBig::from(-9).into();
assert_eq!(UBig::try_from(z), Err(ConversionError::OutOfBounds));

// fractional real part → LossOfPrecision
let z = C::from(F::from_parts(123.into(), -2)); // 1.23
assert_eq!(UBig::try_from(z), Err(ConversionError::LossOfPrecision));

// nonzero imaginary → LossOfPrecision
let z = C::from_parts(9.into(), 1.into());
assert_eq!(UBig::try_from(z), Err(ConversionError::LossOfPrecision));
}

#[test]
fn primitive_conversions() {
// integers embed as purely-real complex numbers (any base)
let z: C = 7u8.into();
assert_eq!(z.re().significand(), &7.into());
let z: C = (-3i8).into();
assert_eq!(z.re().significand(), &(-3i32).into());

// floats embed into a base-2 CBig; NaN is rejected
let z = CBig::<mode::HalfAway, 2>::try_from(2.5f64).unwrap();
assert_eq!(z.re().significand(), &5.into()); // 2.5 = 5 * 2^-1
assert!(CBig::<mode::HalfAway, 2>::try_from(f32::NAN).is_err());

// CBig -> integer primitive (fails on negative / out-of-range / fractional / nonzero-imag)
assert_eq!(u8::try_from(C::from(9u8)), Ok(9u8));
assert_eq!(u8::try_from(C::from(-9i8)), Err(ConversionError::OutOfBounds));
assert_eq!(u8::try_from(C::from(300u16)), Err(ConversionError::OutOfBounds));
assert_eq!(i8::try_from(C::from(-9i8)), Ok(-9i8));
assert_eq!(
i8::try_from(C::from(F::from_parts(123.into(), -2))), // 1.23
Err(ConversionError::LossOfPrecision)
);

// CBig -> float primitive (base-2 only)
let z = CBig::<mode::HalfAway, 2>::try_from(2.5f64).unwrap();
assert_eq!(f64::try_from(z), Ok(2.5));
}
}
7 changes: 6 additions & 1 deletion guide/book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@ title = "Dashu user guide"
description = "User guide of the dashu arbitrary precision library"
authors = ["Jacob Zhong"]
language = "en"
multilingual = false
src = "src"

[output.html]
git-repository-url = "https://github.com/cmpute/dashu"
edit-url-template = "https://github.com/cmpute/dashu/edit/master/guide/src/{path}"

[preprocessor.katex]
3 changes: 2 additions & 1 deletion guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- [Types](./types.md)
- [Construction and Destruction](./construct.md)
- [Cached Arithmetic for FBig](./cached.md)
- [Conversion](./convert.md)
- [Input and Output](./io/index.md)
- [Parsing](./io/parse.md)
Expand All @@ -14,10 +15,10 @@
- [Equality and Comparison](./ops/cmp.md)
- [Basic Arithmetics](./ops/basic.md)
- [Exponential and Logarithm](./ops/exp_log.md)
- [Trigonometric and Hyperbolic Functions](./ops/trig_n_hyper.md)
- [Bit Manipulation](./ops/bit.md)
- [Number Theoretic](./ops/num_theory.md)
- [FAQ](./faq.md)
- [Performance](./performance.md)
- [Cheatsheet](./cheatsheet.md)
- [Standards Compliance](./compliance.md)
- [Complex Numbers](./complex.md)
146 changes: 146 additions & 0 deletions guide/src/cached.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
The [`CachedFBig`] type is an [`FBig`] that carries a shared handle to a
`Rc<RefCell<ConstCache>>`. The cache stores exact binary-splitting state for
mathematical constants (π, ln2, ln10), so that transcendental operations
(`ln`, `exp`, `sin`, `cos`, …, `pi`) reuse and progressively extend prior
work instead of recomputing from scratch.

## Creation

A `CachedFBig` is created by attaching a cache handle to an `FBig`:

```rust
use std::rc::Rc;
use core::cell::RefCell;
use dashu::float::{CachedFBig, ConstCache, FBig, Repr, Context};

let cache = Rc::new(RefCell::new(ConstCache::new()));

// From an FBig
let a = FBig::ONE.into_cached(cache.clone());

// From raw parts with a fresh cache
let b = CachedFBig::<_, 10>::with_cache(
Repr::new(1234.into(), -3),
Context::new(50),
);
```

Use `From<FBig> for CachedFBig` for one-off conversions (it creates a fresh
empty cache):

```rust
let c: CachedFBig = FBig::from(3u8).into();
```

To drop the cache and get back a plain `FBig`, use `into_fbig()` or the
`From<CachedFBig> for FBig` trait:

```rust
let plain: FBig = cached.into(); // or cached.into_fbig()
```

## Cache sharing

Binary operations between `CachedFBig` values preserve the cache handle in
the result: `(a + b).ln().exp()` keeps extending the same cache throughout.
When two operands carry different cache handles, the **left-hand side** cache
is preserved. For `FBig op CachedFBig`, the `CachedFBig` operand's cache is
preserved regardless of which side it is on.

Operations with plain `FBig` and primitives (`u8`, `i32`, `UBig`, etc.) also
work and preserve the `CachedFBig` operand's cache:

```rust
let cached = CachedFBig::<_, 10>::with_cache(
Repr::new(2.into(), 0), Context::new(20),
);
let result = cached + 3u8; // CachedFBig, cache preserved
let result = 10i32 * cached; // CachedFBig, cache preserved
```

## Inspecting and clearing the cache

Use `cache()` to borrow the cache read-only and inspect its size:

```rust
let terms = cached.cache().total_terms();
let words = cached.cache().total_words();
```

Call `clear_cache()` to free all cached big-integer memory. The next
transcendental operation will recompute constants from scratch:

```rust
cached.clear_cache();
assert_eq!(cached.cache().total_terms(), 0);
```

## More constructors and accessors

Beyond `into_cached` / `with_cache` / `From<FBig>`, `CachedFBig` mirrors the rest of `FBig`'s construction surface while preserving the cache handle:

- `from_parts(significand, exponent)` — build from a significand and exponent, with a fresh cache.
- `with_rounding::<NewR>()` — change the rounding mode, keeping the cache handle.
- `as_fbig()` — borrow the inner `FBig` immutably (cheap; no cache detach).
- `from_repr(repr, context, cache)` / `into_repr()` — the raw-repr constructor/destructor that share a specific cache handle.

## Computing constants directly

The cache stores exact binary-splitting state for the constants π, ln2, and ln10, so the methods that produce them reuse and progressively extend prior work rather than recomputing from scratch. On `CachedFBig`, π is a single call:

```rust
use std::rc::Rc;
use core::cell::RefCell;
use dashu::float::{CachedFBig, ConstCache};
use dashu::float::round::mode::HalfAway;

let cache = Rc::new(RefCell::new(ConstCache::new()));
let _pi = CachedFBig::<HalfAway, 10>::pi(100, &cache);
// a later, higher-precision call extends the same cached state instead of restarting
let _pi_more = CachedFBig::<HalfAway, 10>::pi(1000, &cache);
```

You can also drive a bare `ConstCache` directly, without a `CachedFBig` — useful when you want the constants but not the per-value wrapper. The methods are generic over base and rounding mode, and a single cache serves any base:

```rust
use dashu::float::ConstCache;
use dashu::float::round::mode::HalfAway;

let mut cache = ConstCache::new();
let pi = cache.pi::<10, HalfAway>(100).value(); // computes from scratch
let pi_1000 = cache.pi::<10, HalfAway>(1000).value(); // extends the cached state
let ln2 = cache.ln2::<10, HalfAway>(100);
let ln10 = cache.ln10::<10, HalfAway>(100);
```

`ln_base::<B, R>(precision)` dispatches to the cached ln2 / ln10 when `B` is 2 or 10 (or a power of two), and falls back to a direct `ln(B)` otherwise.

## Thread safety

`CachedFBig` carries its cache as `Rc<RefCell<ConstCache>>`, so it is **`!Send + !Sync`** — a cached value cannot move across threads. `FBig` itself stays `Copy + Send + Sync` (which is why `static_fbig!` keeps working); only the cached wrapper is non-thread-safe. `ConstCache` is a plain struct of big integers and is itself `Send + Sync`, so to share one cache across threads, wrap a `ConstCache` (or a `CachedFBig`) in `Arc<Mutex<ConstCache>>`. The underlying `Context` methods accept `Option<&mut ConstCache>` regardless of the container, so this needs no API change.

## Worked example: reusing constants across a chain

Because every value-producing operation preserves the cache handle, a chain of transcendentals reuses the same constants throughout. Building several results from one shared handle pays for each constant once:

```rust
use std::rc::Rc;
use core::cell::RefCell;
use dashu::float::{CachedFBig, ConstCache, Context, Repr};
use dashu::float::round::mode::HalfAway;

type F = CachedFBig<HalfAway, 10>;
let cache = Rc::new(RefCell::new(ConstCache::new()));

// π is computed into the shared cache...
let _pi_50 = F::pi(50, &cache);
// ...and a later, higher-precision call extends it instead of restarting
let _pi_1000 = F::pi(1000, &cache);

// an arithmetic chain built on the same handle keeps it end to end
let a = F::from_repr(Repr::new(2.into(), 0), Context::new(50), cache.clone());
let b = F::from_repr(Repr::new(3.into(), 0), Context::new(50), cache.clone());
let _ = (a + b).ln().exp();

assert!(cache.borrow().total_terms() > 0);
```
Loading
Loading