Skip to main content

dashu_float/math/
cache.rs

1//! Opt-in cache of mathematical constants, enabling progressive refinement.
2
3use core::fmt;
4
5use dashu_base::{BitTest, EstimatedLog2, Sign, UnsignedAbs};
6use dashu_int::{IBig, UBig};
7
8use crate::error::assert_limited_precision;
9use crate::fbig::FBig;
10use crate::repr::{Context, Repr, Word};
11use crate::round::{Round, Rounded};
12use crate::utils::ceil_usize;
13
14/// Binary-splitting tree state — exact integers, losslessly extensible.
15///
16/// Represents `binary_split(start, num_terms)` as the universal triple
17/// `(P, Q, T)`, where `start` is 0 for π and 1 for `L(n)` (whose `k=0` term
18/// `1/n` is pulled out). These are pure integers: independent of base and
19/// rounding mode. To extend to `new_terms > num_terms`, compute the right half
20/// over the new range and merge with the universal `T' = T_l·Q_r + P_l·T_r`.
21#[derive(Clone)]
22pub(crate) struct CachedState {
23    pub p: UBig,
24    pub q: UBig,
25    pub t: IBig,
26    pub num_terms: usize,
27}
28
29/// An opt-in cache of mathematical constants.
30///
31/// Holds exact binary-splitting tree state so that repeated calls at increasing
32/// precision *extend* prior work instead of recomputing from scratch. For
33/// example, computing π at 100 digits and then at 1000 digits only pays for the
34/// extra ~900 digits of work.
35///
36/// The cache is **base-free**: a single [`ConstCache`] serves any base. The base
37/// and rounding mode are specified on each method call (e.g.
38/// `cache.pi::<10, HalfAway>(100)` for 100 decimal digits).
39///
40/// `ConstCache` is a plain struct of big integers, so it is `Send + Sync`. The
41/// methods take `&mut self` (they extend the cached state on a miss), so a caller
42/// either owns one directly, or — to share it across many values and operations —
43/// wraps it in `Rc<RefCell<ConstCache>>` as the
44/// [`CachedFBig`](crate::CachedFBig) type does. To share one cache across
45/// threads, wrap a `ConstCache` (or a `CachedFBig`) in `Arc<Mutex<..>>`.
46///
47/// # Examples
48///
49/// ```
50/// use dashu_float::ConstCache;
51/// use dashu_float::round::mode::HalfAway;
52///
53/// let mut cache = ConstCache::new();
54/// // first call computes from scratch
55/// let _pi_100 = cache.pi::<10, HalfAway>(100).value();
56/// // second call at higher precision extends the cached state
57/// let pi_1000 = cache.pi::<10, HalfAway>(1000).value();
58/// assert!(pi_1000.to_string().starts_with("3.141592653589793"));
59/// ```
60pub struct ConstCache {
61    pi: Option<CachedState>,
62    /// `L(6)`, `L(9)`, `L(99)` — the sub-series used by ln2 / ln10.
63    iacoth_6: Option<CachedState>,
64    iacoth_9: Option<CachedState>,
65    iacoth_99: Option<CachedState>,
66    /// Base-free integer `floor(sqrt(10005) · 2^sqrt_10005_bits)`, reused by π.
67    /// Unlike the series slots this holds a plain value (not a `(P,Q,T)` triple) and
68    /// is extended by a fresh Karatsuba `UBig::sqrt` — Newton refinement would be no
69    /// faster, since `UBig::sqrt` is already O(M(n)).
70    sqrt_10005: Option<UBig>,
71    sqrt_10005_bits: usize,
72}
73
74impl ConstCache {
75    /// Create an empty cache.
76    pub const fn new() -> Self {
77        Self {
78            pi: None,
79            iacoth_6: None,
80            iacoth_9: None,
81            iacoth_99: None,
82            sqrt_10005: None,
83            sqrt_10005_bits: 0,
84        }
85    }
86
87    /// `floor(sqrt(10005) · 2^bits)` as a base-free integer, cached and extended on
88    /// demand. Used by [`pi`](Self::pi). Computed via Karatsuba `UBig::sqrt` (O(M(n))).
89    /// Returns the value together with the number of bits it actually corresponds to
90    /// (which may be larger than requested, when a higher-precision value is reused).
91    fn sqrt_10005(&mut self, bits: usize) -> (UBig, usize) {
92        if bits > self.sqrt_10005_bits {
93            let n = UBig::from(10005u32) << (2 * bits);
94            self.sqrt_10005 = Some(dashu_base::SquareRoot::sqrt(&n));
95            self.sqrt_10005_bits = bits;
96        }
97        (self.sqrt_10005.as_ref().unwrap().clone(), self.sqrt_10005_bits)
98    }
99
100    /// π at `precision` base-`B` digits, rounded per `R`. Extends any prior π
101    /// state cached in `self`.
102    ///
103    /// # Panics
104    ///
105    /// Panics if `precision` is 0.
106    #[must_use]
107    pub fn pi<const B: Word, R: Round>(&mut self, precision: usize) -> Rounded<FBig<R, B>> {
108        assert_limited_precision(precision);
109
110        let bits = bits_for_precision::<B>(precision);
111        let num_terms = (bits * 100 / 4708) + 2;
112
113        let (_p, q, t) = extend_or_compute(&mut self.pi, 0, num_terms, chudnovsky_bs);
114
115        // Finalize: π = 426880·√10005·Q / T  (identical to Context::pi)
116        let guard_bits = num_terms.bit_len() + 32;
117        let work_bits = bits + guard_bits;
118        let work_precision = precision_for_bits::<B>(work_bits);
119        let work = Context::<R>::new(work_precision);
120
121        // Finalize: π = 426880·√10005·Q / T. With √10005 ≈ isqrt_val·2^(-isqrt_bits)
122        // from the base-free cached isqrt, this folds into a single integer ratio
123        //   π = (426880 · isqrt_val · Q) / (T · 2^isqrt_bits),
124        // avoiding any cross-base conversion of √10005 (convert_int is the fast path,
125        // the same one used for Q and T).
126        let (isqrt_val, isqrt_bits) = self.sqrt_10005(work_bits);
127        let num = IBig::from(426_880) * IBig::from(isqrt_val) * IBig::from(q);
128        let den = t << isqrt_bits;
129        let num_f = work.convert_int::<B>(num).value();
130        let den_f = work.convert_int::<B>(den).value();
131        let pi = num_f / den_f;
132        pi.with_precision(precision)
133    }
134
135    /// `L(n) = acoth(n)` at `precision` base-`B` digits, extending its cached
136    /// series state. Only `n ∈ {6, 9, 99}` are cached (the sub-series of ln2 / ln10).
137    fn iacoth<const N: u32, const B: Word, R: Round>(&mut self, precision: usize) -> FBig<R, B> {
138        // terms until r_k < B^{-p}: (2k+1)·log_B(n) > p. The count is generously
139        // over-provisioned (extra terms only add precision), so a plain (truncating)
140        // cast suffices in place of a ceiling.
141        let log_b_n = N.log2_est() / B.log2_est();
142        let required_terms = (precision as f32 / (2.0 * log_b_n)) as usize + 10;
143
144        let slot = match N {
145            6 => &mut self.iacoth_6,
146            9 => &mut self.iacoth_9,
147            99 => &mut self.iacoth_99,
148            _ => unreachable!("iacoth only caches n ∈ {{6, 9, 99}}"),
149        };
150        let (_p, q, t) = extend_or_compute(slot, 1, required_terms, |a, b| iacoth_bs(N, a, b));
151
152        // L(n) = (Q + T) / (n·Q)
153        let guard = ceil_usize(precision.log2_est() / B.log2_est()) + 2;
154        let work = Context::<R>::new(precision + guard);
155        let num = work.convert_int::<B>(q.as_ibig() + &t).value();
156        let denom = work.convert_int::<B>(IBig::from(N) * &q).value();
157        num / denom
158    }
159
160    /// ln(2) at `precision` base-`B` digits, reusing the cached `L(6)` and
161    /// `L(99)` sub-series.
162    ///
163    /// # Panics
164    ///
165    /// Panics if `precision` is 0.
166    #[must_use]
167    pub fn ln2<const B: Word, R: Round>(&mut self, precision: usize) -> FBig<R, B> {
168        // log(2) = 4·L(6) + 2·L(99)  (Gourdon & Sebah, "Log 2")
169        let work = precision + combine_guard::<B>(precision);
170        let l6 = self.iacoth::<6, B, R>(work);
171        let l99 = self.iacoth::<99, B, R>(work);
172        (4u8 * l6 + 2u8 * l99).with_precision(precision).value()
173    }
174
175    /// ln(10) at `precision` base-`B` digits, reusing the cached `L(6)`, `L(99)`,
176    /// and `L(9)` sub-series.
177    ///
178    /// # Panics
179    ///
180    /// Panics if `precision` is 0.
181    #[must_use]
182    pub fn ln10<const B: Word, R: Round>(&mut self, precision: usize) -> FBig<R, B> {
183        // log(10) = 3·log(2) + 2·L(9) = 3·(4·L(6) + 2·L(99)) + 2·L(9)
184        //          = 12·L(6) + 6·L(99) + 2·L(9)
185        // Flattening avoids the intermediate rounding of ln2 inside the product.
186        let work = precision + combine_guard::<B>(precision);
187        let l6 = self.iacoth::<6, B, R>(work);
188        let l99 = self.iacoth::<99, B, R>(work);
189        let l9 = self.iacoth::<9, B, R>(work);
190        (12u8 * l6 + 6u8 * l99 + 2u8 * l9)
191            .with_precision(precision)
192            .value()
193    }
194
195    /// ln(B) at `precision` base-`B` digits, reusing the cached ln2 / ln10 where
196    /// possible.
197    ///
198    /// The generic-base arm evaluates `ln(BASE)` through `ln_compute`, whose ball arithmetic can
199    /// report a range error. It cannot actually do so for a base that fits a [`Word`]
200    /// (`ln(BASE) ≤ Word::BITS · ln 2` and every intermediate stays within a few hundred bits of
201    /// it, nowhere near an `isize` exponent limit), so the arm asserts instead of plumbing a
202    /// `Result` through this near-correct constant accessor.
203    ///
204    /// # Panics
205    ///
206    /// Panics if `precision` is 0, or — for a base that is not 2, 10 or a power of two — if
207    /// `ln(BASE)` range-errors (unreachable for any base that fits a [`Word`], see above).
208    #[must_use]
209    pub fn ln_base<const B: Word, R: Round>(&mut self, precision: usize) -> FBig<R, B> {
210        match B {
211            2 => self.ln2::<B, R>(precision),
212            10 => self.ln10::<B, R>(precision),
213            b if b.is_power_of_two() => {
214                // ln(2^k) = k·ln(2); evaluate ln2 at elevated precision so the
215                // k·ln2 product survives the final round.
216                let work = precision + combine_guard::<B>(precision);
217                let bits = b.trailing_zeros() as usize;
218                (bits * self.ln2::<B, R>(work))
219                    .with_precision(precision)
220                    .value()
221            }
222            _ => {
223                // generic base: no cached L(n) sub-series applies, so compute ln(B) directly via
224                // the near-correct `ln_compute` (the cache only stores a near-correct constant — a
225                // cached value is re-rounded on read, and Ziv isn't needed here).
226                let ctx = Context::<R>::new(precision);
227                ctx.ln_compute::<B>(
228                    &Repr::new(Repr::<B>::BASE.into(), 0),
229                    // no cache for the generic base (its L(n) isn't cached)
230                    precision,
231                    false,
232                    None,
233                )
234                .expect("ln(BASE) cannot range-error (the base fits the exponent range)")
235                .to_value_radius::<R>(&ctx)
236                .0
237            }
238        }
239    }
240
241    /// Sum of `num_terms` across all populated cache slots.
242    #[inline]
243    pub fn total_terms(&self) -> usize {
244        let sum = |s: &Option<CachedState>| s.as_ref().map_or(0, |s| s.num_terms);
245        sum(&self.pi) + sum(&self.iacoth_6) + sum(&self.iacoth_9) + sum(&self.iacoth_99)
246    }
247
248    /// Sum of word counts across all cached big integers (P, Q, T, and the cached
249    /// `√10005` isqrt).
250    ///
251    /// This reflects the underlying storage words used by the cached state.
252    #[inline]
253    pub fn total_words(&self) -> usize {
254        let slot_words = |s: &Option<CachedState>| {
255            s.as_ref().map_or(0, |s| {
256                s.p.as_words().len() + s.q.as_words().len() + s.t.as_sign_words().1.len()
257            })
258        };
259        slot_words(&self.pi)
260            + slot_words(&self.iacoth_6)
261            + slot_words(&self.iacoth_9)
262            + slot_words(&self.iacoth_99)
263            + self.sqrt_10005.as_ref().map_or(0, |s| s.as_words().len())
264    }
265
266    /// Clear all cached constant state, freeing the underlying memory.
267    ///
268    /// After calling `clear()`, the next constant computation will start from scratch
269    /// rather than extending the prior cached state.
270    #[inline]
271    pub fn clear(&mut self) {
272        self.pi = None;
273        self.iacoth_6 = None;
274        self.iacoth_9 = None;
275        self.iacoth_99 = None;
276        self.sqrt_10005 = None;
277        self.sqrt_10005_bits = 0;
278    }
279}
280
281impl Default for ConstCache {
282    #[inline]
283    fn default() -> Self {
284        Self::new()
285    }
286}
287
288/// Ensure `slot` holds state for at least `target` terms, then return `(P, Q, T)`
289/// covering `target` terms (or more, when an existing higher-precision state
290/// already covers `target` — finalize then rounds down to the requested precision).
291///
292/// `range_bs(a, b)` computes the leaf-merged state over `[a, b)` and must handle
293/// `a == b` by returning the identity `(1, 1, 0)`.
294fn extend_or_compute<F>(
295    slot: &mut Option<CachedState>,
296    start: usize,
297    target: usize,
298    range_bs: F,
299) -> (UBig, UBig, IBig)
300where
301    F: Fn(usize, usize) -> (UBig, UBig, IBig),
302{
303    match slot {
304        // Already have >= target terms: reuse (extra terms only add precision).
305        Some(s) if s.num_terms >= target => (s.p.clone(), s.q.clone(), s.t.clone()),
306        // Have fewer terms: extend the right half [num_terms, target) and merge.
307        Some(s) => {
308            let (pr, qr, tr) = range_bs(s.num_terms, target);
309            let (p, q, t) = merge(&s.p, &s.q, &s.t, &pr, &qr, &tr);
310            *slot = Some(CachedState {
311                p: p.clone(),
312                q: q.clone(),
313                t: t.clone(),
314                num_terms: target,
315            });
316            (p, q, t)
317        }
318        // Cold: compute from `start`.
319        None => {
320            let (p, q, t) = range_bs(start, target);
321            *slot = Some(CachedState {
322                p: p.clone(),
323                q: q.clone(),
324                t: t.clone(),
325                num_terms: target,
326            });
327            (p, q, t)
328        }
329    }
330}
331
332/// Reborrow an `Option<&mut ConstCache>` so it can be threaded into several
333/// sequential sub-calls. `as_deref_mut` is the natural reborrow here; clippy's
334/// `needless_option_as_deref` flags it (the deref target equals the referent),
335/// so the lint is allowed at this single centralized spot.
336#[inline]
337#[allow(clippy::needless_option_as_deref)]
338pub(crate) fn reborrow_cache<'a>(
339    cache: &'a mut Option<&mut ConstCache>,
340) -> Option<&'a mut ConstCache> {
341    cache.as_deref_mut()
342}
343
344/// Number of bits needed to represent `precision` base-`B` digits exactly.
345///
346/// For power-of-two bases this is exact; for arbitrary bases it uses the upper
347/// bound from [`EstimatedLog2`], which is far tighter than `ilog2(B) + 1`.
348fn bits_for_precision<const B: Word>(precision: usize) -> usize {
349    if B.is_power_of_two() {
350        precision.saturating_mul(B.ilog2() as usize)
351    } else {
352        // ub ≥ log2(B) with error ≤ 2/256.  Multiply in f64 so the product
353        // is exact for precision up to 2^53.  +1 guards float rounding.
354        let ub = B.log2_bounds().1;
355        ceil_usize(precision as f32 * ub) + 1
356    }
357}
358
359/// Convert a work-precision expressed in bits back to base-`B` digits.
360///
361/// For base 2 the identity holds; for power-of-two bases it uses ceiling
362/// division; for arbitrary bases it inverts the lower bound from
363/// [`EstimatedLog2`] to get a tight ceiling.
364fn precision_for_bits<const B: Word>(bits: usize) -> usize {
365    if B.is_power_of_two() {
366        let log2 = B.ilog2() as usize;
367        (bits + log2 - 1) / log2
368    } else {
369        // lb ≤ log2(B), so 1/lb ≥ 1/log2(B).  +1 guards float rounding.
370        let lb = B.log2_bounds().0;
371        ceil_usize(bits as f32 / lb) + 1
372    }
373}
374
375/// Guard digits added when combining sub-series, large enough that the linear
376/// combination and its final round to `precision` are unaffected by summation
377/// rounding (a few digits cover the constant multipliers and term count).
378fn combine_guard<const B: Word>(precision: usize) -> usize {
379    ceil_usize(precision.log2_est() / B.log2_est()) + 4
380}
381
382/// Universal binary-splitting merge:
383/// `combine((P_l,Q_l,T_l), (P_r,Q_r,T_r)) = (P_l·P_r, Q_l·Q_r, T_l·Q_r + P_l·T_r)`.
384///
385/// This operation is associative, so the `(P, Q, T)` for a range is independent of
386/// how the recursion splits it — which is exactly what lets a cached partial tree
387/// state be extended by merging in a freshly computed right half.
388pub(crate) fn merge(
389    pl: &UBig,
390    ql: &UBig,
391    tl: &IBig,
392    pr: &UBig,
393    qr: &UBig,
394    tr: &IBig,
395) -> (UBig, UBig, IBig) {
396    let p = pl * pr;
397    let q = ql * qr;
398    // re-interpret the magnitudes as signed without cloning the big integers
399    let t = qr.as_ibig() * tl + pl.as_ibig() * tr;
400    (p, q, t)
401}
402
403/// Binary splitting implementation for the Chudnovsky series.
404/// Returns `(P, Q, T)` for the range `[a, b)`. An empty range `[a, a)` yields the
405/// identity `(1, 1, 0)`, so callers may safely merge a cached left state with a
406/// right half that starts exactly where the left one ended.
407pub(crate) fn chudnovsky_bs(a: usize, b: usize) -> (UBig, UBig, IBig) {
408    if a >= b {
409        return (UBig::ONE, UBig::ONE, IBig::ZERO);
410    }
411    if b - a == 1 {
412        const COEFF1: IBig = IBig::from_parts_const(Sign::Positive, 13591409);
413        const COEFF2: IBig = IBig::from_parts_const(Sign::Positive, 545140134);
414
415        // Base case: calculate single term
416        if a == 0 {
417            return (UBig::ONE, UBig::ONE, COEFF1);
418        }
419
420        let k = a as u64;
421        let p = UBig::from(6 * k - 5) * (2 * k - 1) * (6 * k - 1);
422        let q = UBig::from(k).pow(3) * 10_939_058_860_032_000u64;
423        let t_val = COEFF1 + COEFF2 * k;
424        let t_abs = &p * t_val.unsigned_abs();
425        let t = IBig::from(t_abs) * Sign::from(a % 2 == 1);
426        return (p, q, t);
427    }
428
429    // Recursive step
430    let mid = (a + b) / 2;
431    let (p_l, q_l, t_l) = chudnovsky_bs(a, mid);
432    let (p_r, q_r, t_r) = chudnovsky_bs(mid, b);
433
434    // T = T_L * Q_R + T_R * P_L  (the universal merge)
435    merge(&p_l, &q_l, &t_l, &p_r, &q_r, &t_r)
436}
437
438/// Binary splitting for `L(n) = acoth(n) = Σ_{k≥0} 1/(n^{2k+1}(2k+1))` over `[1, b)`.
439///
440/// Term ratio (k≥1): `r_k/r_{k-1} = p_k/q_k` with `p_k = 2k-1`, `q_k = (2k+1)·n²`.
441/// The `k=0` term `r_0 = 1/n` is pulled out; over `[1, b)` the tree state satisfies
442/// `T/Q = n · Σ_{k=1}^{b-1} 1/((2k+1)·n^{2k+1})`, hence `L(n) = (Q + T)/(n·Q)`.
443///
444/// Using the ratio form (rather than independent `1/q_k` terms) keeps
445/// `Q = Π(2k+1)·n²` at O(p) digits: each leaf multiplies only small integers
446/// (with `n²` folded in), no `n.pow(2k+1)` per leaf.
447pub(crate) fn iacoth_bs(n: u32, a: usize, b: usize) -> (UBig, UBig, IBig) {
448    debug_assert!(a >= 1, "iacoth_bs leaf index must be >= 1");
449    if a >= b {
450        return (UBig::ONE, UBig::ONE, IBig::ZERO); // identity
451    }
452    // Precomputed initial block [1, 1+K): skip its K leaves on every fresh
453    // computation. Because the merge is associative, the constant triple is
454    // identical to the recursively computed state regardless of split order.
455    // It only applies at the series start (a == 1); recursive/extend calls have
456    // a >= 1 + K and never reach this branch.
457    if a == 1 {
458        if let Some((k, p0, q0, t0)) = iacoth_initial_block(n) {
459            // the precomputed block covers [1, 1+k); use it when [a, b) reaches
460            // past its end (b > k)
461            if b > k {
462                let (pr, qr, tr) = iacoth_bs(n, 1 + k, b);
463                return merge(&p0, &q0, &t0, &pr, &qr, &tr);
464            }
465        }
466    }
467    if b - a == 1 {
468        // leaf at k = a (a >= 1): (p_a, q_a, p_a), p_a = 2a-1, q_a = (2a+1)·n²
469        let pa = UBig::from(2 * a - 1);
470        let n2 = UBig::from(n).pow(2);
471        let qa = UBig::from(2 * a + 1) * n2;
472        let ta = IBig::from(pa.clone());
473        return (pa, qa, ta);
474    }
475    let mid = (a + b) / 2;
476    let (pl, ql, tl) = iacoth_bs(n, a, mid);
477    let (pr, qr, tr) = iacoth_bs(n, mid, b);
478    merge(&pl, &ql, &tl, &pr, &qr, &tr) // universal merge
479}
480
481/// Precomputed binary-splitting state for `L(n) = acoth(n)` over the first `K`
482/// terms `[1, 1+K)`, stored as `(K, P, Q, T)`. `K` is chosen (per `n`) so that
483/// `P`, `Q` and `|T|` each fit in a `u32`. Since `DoubleWord` is `u32`/`u64`/`u128`
484/// for `Word = u16`/`u32`/`u64`, a `u32`-sized literal is accepted by
485/// [`UBig::from_dword`] / [`IBig::from_parts_const`] on **every** configuration —
486/// so this single set of constants is portable without needing to detect the
487/// `Word` width (which is internal to `dashu-int`). The constants also use the
488/// inline small-integer representation, so instantiating them never allocates.
489///
490/// Only the sub-series that back ln2 / ln10 (`n ∈ {6, 9, 99}`) are precomputed;
491/// π cannot use this trick because its 2-term `T` already overflows a `u32`.
492fn iacoth_initial_block(n: u32) -> Option<(usize, UBig, UBig, IBig)> {
493    match n {
494        // L(6) over [1, 5): 4 leaves.
495        6 => Some(IACOTH_6_INITIAL),
496        // L(9) over [1, 4): 3 leaves.
497        9 => Some(IACOTH_9_INITIAL),
498        // L(99) over [1, 3): 2 leaves.
499        99 => Some(IACOTH_99_INITIAL),
500        _ => None,
501    }
502}
503
504/// `(K, P, Q, T)` for `L(6)` over `[1, 5)` (4 leaves).
505const IACOTH_6_INITIAL: (usize, UBig, UBig, IBig) = (
506    4,
507    UBig::from_word(105),
508    UBig::from_dword(1587237120),
509    IBig::from_parts_const(Sign::Positive, 14946549),
510);
511/// `(K, P, Q, T)` for `L(9)` over `[1, 4)` (3 leaves).
512const IACOTH_9_INITIAL: (usize, UBig, UBig, IBig) = (
513    3,
514    UBig::from_word(15),
515    UBig::from_dword(55801305),
516    IBig::from_parts_const(Sign::Positive, 231351),
517);
518/// `(K, P, Q, T)` for `L(99)` over `[1, 3)` (2 leaves).
519const IACOTH_99_INITIAL: (usize, UBig, UBig, IBig) = (
520    2,
521    UBig::from_word(3),
522    UBig::from_dword(1440894015),
523    IBig::from_parts_const(Sign::Positive, 49008),
524);
525
526/// Binary splitting for `e = Σ_{k≥0} 1/k!` over the index range `[a, b)` with `a ≥ 1`.
527///
528/// The `k = 0` term `1/0! = 1` is added by the caller. For `k ≥ 1` the term ratio
529/// is `tₖ/tₖ₋₁ = 1/k`, so each leaf is `(pₖ, qₖ, pₖ) = (1, k, 1)` and the universal
530/// [`merge`] applies verbatim (with `P ≡ 1`, it reduces to `T' = Tₗ·Qᵣ + Tᵣ`).
531/// Over `[1, N+1)` the triple satisfies `T/Q = Σ_{k=1}^{N} 1/k!`, hence the
532/// finalization `e = (Q + T)/Q` in [`compute_e`].
533///
534/// This is the optimal algorithm for *e*: there is no Chudnovsky-analog, and the
535/// factorial series with binary splitting is `O(M(n) log n)` — faster than π —
536/// beating both the AGM approach and `exp(1)` (which pays for argument reduction
537/// and `√p` powering that this series needs neither of).
538///
539/// An empty range yields the identity `(1, 1, 0)`.
540pub(crate) fn e_bs(a: usize, b: usize) -> (UBig, UBig, IBig) {
541    if a >= b {
542        return (UBig::ONE, UBig::ONE, IBig::ZERO);
543    }
544    if b - a == 1 {
545        // leaf at k = a (a ≥ 1): (pₖ, qₖ, pₖ) = (1, a, 1)
546        return (UBig::ONE, UBig::from(a), IBig::ONE);
547    }
548    let mid = (a + b) / 2;
549    let (pl, ql, tl) = e_bs(a, mid);
550    let (pr, qr, tr) = e_bs(mid, b);
551    merge(&pl, &ql, &tl, &pr, &qr, &tr) // universal merge
552}
553
554/// Number of series terms `N` for `e = Σ 1/k!` so that the truncation tail is
555/// safely below the target precision.
556///
557/// The tail after `k = N` is `Σ_{k>N} 1/k! < 2/(N+1)!`, so we need
558/// `log₂((N+1)!) > bits`. Stirling's lower bound `m! ≥ (m/e)ᵐ` gives
559/// `log₂(m!) ≥ m·(log₂ m − log₂ e)`; we seed `m` from the closed-form inverse of
560/// that bound and grow until it holds, so the result is always sufficient.
561/// Over-provisioning is harmless: extra terms only add precision, which the final
562/// `with_precision` discards.
563fn e_term_count(bits: usize) -> usize {
564    // `log2_bounds` (not `f64::log2`, which is std-only) keeps this `no_std`-clean;
565    // using its lower bound makes `lb` a valid lower bound on the Stirling LB, so the
566    // loop never terminates too early.
567    let target = (bits + 64) as f64; // margin for the tail's factor of 2 and final rounding
568    let log2_e = core::f64::consts::LOG2_E;
569    let lb = |m: usize| -> f64 { m as f64 * (m.log2_bounds().0 as f64 - log2_e) };
570    let denom = (bits.log2_bounds().1 as f64 - log2_e).max(1.0);
571    let mut m = (target / denom) as usize + 1;
572    while lb(m) < target {
573        m += 1;
574    }
575    // `m` corresponds to `N+1`; return `N`.
576    m.saturating_sub(1)
577}
578
579/// One-shot computation of *e* (Euler's number) at `precision` base-`B` digits,
580/// rounded per `R`.
581///
582/// Evaluates `e = Σ_{k=0}^∞ 1/k!` by exact-integer binary splitting (the
583/// [`e_bs`] kernel) and finalizes `e = (Q + T)/Q`. Unlike π, this is fully
584/// self-contained — it depends on no cached sub-constant and is reused by no
585/// other operation — so it deliberately does **not** live in [`ConstCache`] and is
586/// not progressively refined across calls. (See `Context::e` for the rationale.)
587pub(crate) fn compute_e<const B: Word, R: Round>(precision: usize) -> Rounded<FBig<R, B>> {
588    assert_limited_precision(precision);
589
590    let bits = bits_for_precision::<B>(precision);
591    let n = e_term_count(bits);
592    // Σ_{k=1}^{n} 1/k! = T/Q; the k=0 term 1/0! = 1 is folded in as (Q + T)/Q.
593    let (_p, q, t) = e_bs(1, n + 1);
594
595    // Evaluate (Q + T)/Q at an elevated working precision so the integer→float
596    // conversion and the division round correctly down to `precision`. The guard
597    // mirrors `ConstCache::pi`: a few bits for the term count, plus a fixed margin.
598    let guard_bits = n.bit_len() + 32;
599    let work_precision = precision_for_bits::<B>(bits + guard_bits);
600    let work = Context::<R>::new(work_precision);
601    let num = work.convert_int::<B>(q.as_ibig() + &t).value();
602    let den = work.convert_int::<B>(IBig::from(q)).value();
603    let e = num / den;
604    e.with_precision(precision)
605}
606
607impl fmt::Debug for ConstCache {
608    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609        f.debug_struct("ConstCache")
610            .field("pi", &DebugSlot(&self.pi))
611            .field("iacoth_6", &DebugSlot(&self.iacoth_6))
612            .field("iacoth_9", &DebugSlot(&self.iacoth_9))
613            .field("iacoth_99", &DebugSlot(&self.iacoth_99))
614            .finish()
615    }
616}
617
618/// Newtype so we can implement `Debug` for `&Option<CachedState>` via the
619/// big-integer `Debug` formatters.
620struct DebugSlot<'a>(&'a Option<CachedState>);
621
622impl fmt::Debug for DebugSlot<'_> {
623    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
624        match self.0 {
625            Some(s) => f
626                .debug_struct("CachedState")
627                .field("num_terms", &s.num_terms)
628                .field("p", &s.p)
629                .field("q", &s.q)
630                .field("t", &s.t)
631                .finish(),
632            None => f.write_str("None"),
633        }
634    }
635}
636
637#[cfg(test)]
638mod tests {
639    use super::*;
640    use crate::round::mode;
641    use crate::DBig;
642    use alloc::format;
643    use alloc::string::ToString; // not in the prelude under no_std
644
645    /// Independently (left-fold) merge the first `k` leaves of `L(n)` and check
646    /// that the result matches the precomputed constant triple. Guards against
647    /// transcription errors in the `IACOTH_*_INITIAL` literals; correctness of the
648    /// finalized `L(n)` values is covered by `log::tests`.
649    #[test]
650    fn test_iacoth_initial_blocks() {
651        fn check(n: u32, expected: &(usize, UBig, UBig, IBig)) {
652            let k = expected.0;
653            // independently (left-fold) merge the first k leaves
654            let mut acc = (UBig::ONE, UBig::ONE, IBig::ZERO);
655            for kk in 1..=k {
656                let pa = UBig::from(2 * kk as u64 - 1);
657                let qa = UBig::from(2 * kk as u64 + 1) * UBig::from(n).pow(2);
658                let ta = IBig::from(pa.clone());
659                let (p, q, t) = merge(&acc.0, &acc.1, &acc.2, &pa, &qa, &ta);
660                acc = (p, q, t);
661            }
662            assert_eq!(acc.0, expected.1, "P mismatch for n={n}");
663            assert_eq!(acc.1, expected.2, "Q mismatch for n={n}");
664            assert_eq!(acc.2, expected.3, "T mismatch for n={n}");
665            // the seed branch must reproduce the same state as the full recursion
666            assert_eq!(iacoth_bs(n, 1, 1 + k), (acc.0, acc.1, acc.2));
667        }
668        check(6, &IACOTH_6_INITIAL);
669        check(9, &IACOTH_9_INITIAL);
670        check(99, &IACOTH_99_INITIAL);
671    }
672
673    #[test]
674    fn test_pi_matches_context() {
675        // Cache miss must reproduce Context::pi exactly.
676        for &precision in &[10usize, 50, 100] {
677            let mut cache = ConstCache::new();
678            let cached = cache.pi::<10, mode::HalfEven>(precision).value();
679            let direct = Context::<mode::HalfEven>::new(precision)
680                .pi::<10>(None)
681                .value();
682            assert_eq!(cached, direct, "pi mismatch at precision {precision}");
683        }
684    }
685
686    #[test]
687    fn test_pi_lower_precision_reuses() {
688        // Compute at high precision, then a lower-precision request must round
689        // down from the cached state and still be correct.
690        let mut cache = ConstCache::new();
691        let _pi_high = cache.pi::<10, mode::HalfEven>(200).value();
692        // the slot now holds >=200 terms; a 50-digit request reuses it
693        let pi_50 = cache.pi::<10, mode::HalfEven>(50).value();
694        let direct = Context::<mode::HalfEven>::new(50).pi::<10>(None).value();
695        assert_eq!(pi_50, direct);
696    }
697
698    #[test]
699    fn test_pi_extension_matches_scratch() {
700        // Extending 100 -> 1000 must be bit-identical to a from-scratch 1000-digit compute.
701        let mut cache = ConstCache::new();
702        let _pi_100 = cache.pi::<10, mode::HalfAway>(100).value();
703        let pi_1000_extended = cache.pi::<10, mode::HalfAway>(1000).value();
704
705        let direct = Context::<mode::HalfAway>::new(1000).pi::<10>(None).value();
706        assert_eq!(pi_1000_extended, direct);
707    }
708
709    #[test]
710    fn test_iacoth_matches_context() {
711        let mut cache = ConstCache::new();
712        // ln2 / ln10 via cache must match ln(2)/ln(10) computed independently
713        // through Context::ln (a different, atanh-based algorithm) at several precisions.
714        for &precision in &[20usize, 45, 80] {
715            let cached_ln2 = cache
716                .ln2::<10, mode::Zero>(precision)
717                .with_precision(precision)
718                .value();
719            let ln2_ctx = Context::<mode::Zero>::new(precision);
720            let direct_ln2 = ln2_ctx.unwrap_fp(ln2_ctx.ln::<10>(&Repr::new(2.into(), 0), None));
721            assert_eq!(cached_ln2, direct_ln2, "ln2 mismatch at precision {precision}");
722
723            let cached_ln10 = cache
724                .ln10::<10, mode::Zero>(precision)
725                .with_precision(precision)
726                .value();
727            let ln10_ctx = Context::<mode::Zero>::new(precision);
728            let direct_ln10 = ln10_ctx.unwrap_fp(ln10_ctx.ln::<10>(&Repr::new(10.into(), 0), None));
729            assert_eq!(cached_ln10, direct_ln10, "ln10 mismatch at precision {precision}");
730        }
731    }
732
733    #[test]
734    fn test_iacoth_extension_matches_scratch() {
735        // Extend ln2 from low to high precision; result must match from-scratch.
736        let mut cache = ConstCache::new();
737        let _ln2_low = cache.ln2::<10, mode::HalfAway>(20);
738        let ln2_high = cache.ln2::<10, mode::HalfAway>(120);
739
740        let mut fresh = ConstCache::new();
741        let direct = fresh.ln2::<10, mode::HalfAway>(120);
742        assert_eq!(ln2_high, direct);
743    }
744
745    #[test]
746    fn test_ln_base() {
747        // binary base: ln(base) == ln(2)
748        let mut cache = ConstCache::new();
749        let ln_base = cache.ln_base::<2, mode::HalfAway>(50);
750        let ln2 = cache.ln2::<2, mode::HalfAway>(50);
751        assert_eq!(ln_base, ln2);
752
753        // power-of-two base: ln(8) = 3·ln(2)
754        let ln8 = cache.ln_base::<8, mode::HalfAway>(50);
755        let expected = 3u8 * cache.ln2::<8, mode::HalfAway>(50);
756        assert_eq!(ln8.with_precision(50).value(), expected.with_precision(50).value());
757    }
758
759    #[test]
760    fn test_debug_shows_bigint_head_tail() {
761        let mut cache = ConstCache::new();
762        let _pi = cache.pi::<10, mode::HalfAway>(100); // populate the cache (value unused)
763        let s = format!("{:?}", cache);
764        assert!(s.contains("pi"));
765        assert!(s.contains("num_terms"));
766        // UBig/IBig Debug prints head..tail, so the output stays compact
767        assert!(s.contains(".."), "Debug output should use head..tail truncation");
768        assert!(s.len() < 512);
769    }
770
771    #[test]
772    fn test_sqrt_10005_cached_and_counted() {
773        // Computing π caches the base-free √10005 isqrt; total_words counts it, and
774        // clear() frees it.
775        let mut cache = ConstCache::new();
776        assert_eq!(cache.total_terms(), 0);
777        assert_eq!(cache.total_words(), 0);
778
779        let _pi = cache.pi::<10, mode::HalfAway>(200); // fills the cache (value unused)
780                                                       // the isqrt is now cached (total_terms stays series-only; words include isqrt)
781        assert!(cache.total_words() > 0);
782
783        cache.clear();
784        assert_eq!(cache.total_terms(), 0);
785        assert_eq!(cache.total_words(), 0);
786
787        // after clear, π recomputes from scratch and still matches the direct value
788        let direct = Context::<mode::HalfAway>::new(50).pi::<10>(None).value();
789        let after_clear = cache.pi::<10, mode::HalfAway>(50).value();
790        assert_eq!(after_clear, direct);
791    }
792
793    #[test]
794    fn test_sqrt_10005_reuse_higher_precision() {
795        // A high-precision π call caches a high-bit isqrt; a later lower-precision
796        // call must reuse it (no recompute) and still be correct.
797        let mut cache = ConstCache::new();
798        let _high = cache.pi::<2, mode::HalfEven>(1000);
799        let words_after_high = cache.total_words();
800
801        let low = cache.pi::<2, mode::HalfEven>(100).value();
802        // word count unchanged ⇒ isqrt (and series) were reused, not recomputed
803        assert_eq!(cache.total_words(), words_after_high);
804
805        let direct = Context::<mode::HalfEven>::new(100).pi::<2>(None).value();
806        assert_eq!(low, direct);
807    }
808
809    /// `e_bs(1, N+1)` produces `(P, Q, T)` with `T/Q = Σ_{k=1}^{N} 1/k!` and `P ≡ 1`.
810    #[test]
811    fn test_e_bs_partial_sums() {
812        // N=1: 1/1! = 1  →  (1, 1, 1)
813        let (p, q, t) = e_bs(1, 2);
814        assert_eq!(p, UBig::ONE);
815        assert_eq!(q, UBig::from(1u32));
816        assert_eq!(t, IBig::from(1));
817
818        // N=3: 1 + 1/2 + 1/6 = 10/6  →  (1, 6, 10)
819        let (p, q, t) = e_bs(1, 4);
820        assert_eq!(p, UBig::ONE);
821        assert_eq!(q, UBig::from(6u32));
822        assert_eq!(t, IBig::from(10));
823
824        // N=4: +1/24 → 41/24  →  (1, 24, 41)
825        let (p, q, t) = e_bs(1, 5);
826        assert_eq!(p, UBig::ONE);
827        assert_eq!(q, UBig::from(24u32));
828        assert_eq!(t, IBig::from(41));
829
830        // empty range is the identity triple
831        let (p, q, t) = e_bs(7, 7);
832        assert_eq!(p, UBig::ONE);
833        assert_eq!(q, UBig::ONE);
834        assert_eq!(t, IBig::ZERO);
835    }
836
837    /// `compute_e` (factorial binary splitting) and `exp(1)` (argument reduction,
838    /// Taylor series, and powering) are two independent algorithms for the same
839    /// value, so both must yield the identical correctly-rounded result.
840    #[test]
841    fn test_e_matches_exp_one() {
842        fn check<const B: Word>(p: usize) {
843            let ctx = Context::<mode::HalfAway>::new(p);
844            let e_const = ctx.e::<B>().value();
845            let exp_one = ctx
846                .exp::<B>(&Repr::<B>::new(IBig::from(1), 0), None)
847                .unwrap()
848                .value();
849            assert_eq!(e_const, exp_one, "e != exp(1) at precision {p}, base {B}");
850        }
851        for &p in &[1usize, 2, 5, 13, 50, 137, 500] {
852            check::<10>(p);
853            check::<2>(p);
854        }
855    }
856
857    /// Ground-truth prefix of e (OEIS A001113), independent of the `exp` path.
858    /// The 45-decimal prefix is far from the precision-100 rounding boundary.
859    #[test]
860    fn test_e_known_decimal_prefix() {
861        // e = 2.71828182845904523536028747135266249775724709369995957…
862        let s = DBig::e(100).to_string();
863        assert!(s.starts_with("2.718281828459045235360287471352662497757247093699"), "got {s}");
864        // sanity: e is in (2, 3)
865        let e = DBig::e(10);
866        assert!(e > DBig::from(2u32));
867        assert!(e < DBig::from(3u32));
868    }
869
870    /// `e_term_count` must return enough terms that Stirling's lower bound on
871    /// `(N+1)!` actually exceeds the target bit count.
872    #[test]
873    fn test_e_term_count_sufficient() {
874        for &bits in &[1usize, 10, 100, 1000, 10_000, 100_000] {
875            let n = e_term_count(bits);
876            let m = (n + 1) as f64;
877            let stirling_lb = m * (m.log2() - core::f64::consts::LOG2_E);
878            assert!(stirling_lb > bits as f64, "N={n} insufficient for bits={bits}");
879        }
880    }
881
882    /// π must round correctly at every precision — the Chudnovsky series accuracy
883    /// headroom must keep the truncation error well below 1 ulp.
884    #[test]
885    fn test_pi_no_off_by_one_ulp() {
886        let pi_ref = FBig::<mode::Zero, 2>::pi(2048);
887        for prec in 10..=1024 {
888            let mid = Context::<mode::Zero>::new(prec).pi::<2>(None).value();
889            let correct = pi_ref.clone().with_precision(prec).value();
890            assert_eq!(mid.into_repr(), correct.into_repr(), "pi off by 1 ulp at prec={prec}");
891        }
892    }
893}