Skip to content

Implement a sharable const cache for FBig#83

Open
cmpute wants to merge 20 commits into
developfrom
float-cache
Open

Implement a sharable const cache for FBig#83
cmpute wants to merge 20 commits into
developfrom
float-cache

Conversation

@cmpute

@cmpute cmpute commented Jun 21, 2026

Copy link
Copy Markdown
Owner

No description provided.

Jacob Zhong and others added 20 commits June 16, 2026 23:20
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>
Comment thread float/src/math/consts.rs
let sqrt_10005 = work_context
.sqrt(&work_context.convert_int::<B>(10005.into()).value().repr)
.value();
let sqrt_10005 =

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. The code of calculation of pi are duplicate in consts.ts and cache.rs
  2. the constant 10005 here don't need rounding (unnecessary)

Comment thread float/src/math/trig.rs
} else {
Repr::zero()
};
Ok(FBig::<R, B>::new(zero, *ctx).with_precision(ctx.precision))

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last with_precision call is unnecessary. new() already ensures this

Comment thread float/src/math/trig.rs
/// 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>(

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we still need #[must_use] tag for functions using the cache.

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