Implement a sharable const cache for FBig#83
Open
cmpute wants to merge 20 commits into
Open
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>
cmpute
commented
Jun 22, 2026
| let sqrt_10005 = work_context | ||
| .sqrt(&work_context.convert_int::<B>(10005.into()).value().repr) | ||
| .value(); | ||
| let sqrt_10005 = |
Owner
Author
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)
cmpute
commented
Jun 22, 2026
| } else { | ||
| Repr::zero() | ||
| }; | ||
| Ok(FBig::<R, B>::new(zero, *ctx).with_precision(ctx.precision)) |
Owner
Author
There was a problem hiding this comment.
The last with_precision call is unnecessary. new() already ensures this
cmpute
commented
Jun 22, 2026
| /// 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>( |
Owner
Author
There was a problem hiding this comment.
I think we still need #[must_use] tag for functions using the cache.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.