1use alloc::rc::Rc;
4use core::cell::RefCell;
5use core::cmp::Ordering;
6use core::str::FromStr;
7
8use dashu_base::{AbsOrd, ConversionError, EstimatedLog2, ParseError, Sign, Signed};
9use dashu_int::{IBig, UBig};
10
11use crate::error::panic_unlimited_precision;
12use crate::fbig::FBig;
13use crate::math::cache::ConstCache;
14use crate::repr::{Context, Repr, Word};
15use crate::round::{mode, Round, Rounded};
16use crate::utils::digit_len;
17
18pub struct CachedFBig<R: Round = mode::Zero, const B: Word = 2> {
60 pub(crate) fbig: FBig<R, B>,
61 pub(crate) cache: Rc<RefCell<ConstCache>>,
62}
63
64impl<R: Round, const B: Word> CachedFBig<R, B> {
65 #[inline]
67 pub fn new(value: FBig<R, B>, cache: Rc<RefCell<ConstCache>>) -> Self {
68 Self { fbig: value, cache }
69 }
70
71 #[inline]
73 pub fn from_repr(repr: Repr<B>, context: Context<R>, cache: Rc<RefCell<ConstCache>>) -> Self {
74 Self {
75 fbig: FBig::new(repr, context),
76 cache,
77 }
78 }
79
80 #[inline]
82 pub fn with_cache(repr: Repr<B>, context: Context<R>) -> Self {
83 Self::from_repr(repr, context, Rc::new(RefCell::new(ConstCache::new())))
84 }
85
86 #[inline]
89 pub(crate) fn from_fbig(fbig: FBig<R, B>, cache: &Rc<RefCell<ConstCache>>) -> Self {
90 Self {
91 fbig,
92 cache: Rc::clone(cache),
93 }
94 }
95
96 #[inline]
98 pub fn as_fbig(&self) -> &FBig<R, B> {
99 &self.fbig
100 }
101
102 #[inline]
104 pub fn into_fbig(self) -> FBig<R, B> {
105 self.fbig
106 }
107
108 #[inline]
112 pub fn cache(&self) -> impl core::ops::Deref<Target = ConstCache> + '_ {
113 self.cache.borrow()
114 }
115
116 #[inline]
120 pub fn clear_cache(&self) {
121 self.cache.borrow_mut().clear();
122 }
123
124 pub fn pi(precision: usize, cache: &Rc<RefCell<ConstCache>>) -> Self {
126 let fbig = {
127 let mut c = cache.borrow_mut();
128 Context::<R>::new(precision).pi::<B>(Some(&mut *c)).value()
129 };
130 Self::from_fbig(fbig, cache)
131 }
132
133 #[inline]
137 pub const fn precision(&self) -> usize {
138 self.fbig.context.precision
139 }
140
141 #[inline]
143 pub fn digits(&self) -> usize {
144 self.fbig.repr.digits()
145 }
146
147 #[inline]
149 pub const fn context(&self) -> Context<R> {
150 self.fbig.context
151 }
152
153 #[inline]
155 pub const fn repr(&self) -> &Repr<B> {
156 &self.fbig.repr
157 }
158
159 #[inline]
161 pub fn into_repr(self) -> Repr<B> {
162 self.fbig.repr
163 }
164
165 #[inline]
167 pub const fn sign(&self) -> Sign {
168 self.fbig.repr.sign()
169 }
170
171 pub fn with_precision(&self, precision: usize) -> Rounded<Self> {
173 self.fbig
174 .clone()
175 .with_precision(precision)
176 .map(|f| Self::from_fbig(f, &self.cache))
177 }
178
179 pub fn with_rounding<NewR: Round>(&self) -> CachedFBig<NewR, B> {
181 CachedFBig::from_fbig(self.fbig.clone().with_rounding::<NewR>(), &self.cache)
182 }
183}
184
185impl<R: Round, const B: Word> CachedFBig<R, B> {
186 pub fn ulp(&self) -> Self {
188 if self.fbig.context.precision == 0 {
189 panic_unlimited_precision();
190 }
191 let repr = Repr {
192 significand: dashu_int::IBig::ONE,
193 exponent: self.fbig.repr.exponent + self.fbig.repr.digits() as isize
194 - self.fbig.context.precision as isize,
195 };
196 Self::from_repr(repr, self.fbig.context, Rc::clone(&self.cache))
197 }
198
199 pub fn to_int(&self) -> Rounded<dashu_int::IBig> {
201 self.fbig.clone().to_int()
202 }
203
204 pub fn to_f32(&self) -> Rounded<f32> {
206 self.fbig.clone().to_f32()
207 }
208
209 pub fn to_f64(&self) -> Rounded<f64> {
211 self.fbig.clone().to_f64()
212 }
213
214 pub fn from_parts(significand: dashu_int::IBig, exponent: isize) -> Self {
216 let precision = digit_len::<B>(&significand).max(1);
217 let repr = Repr::new(significand, exponent);
218 Self::with_cache(repr, Context::new(precision))
219 }
220}
221
222impl<R: Round, const B: Word> From<FBig<R, B>> for CachedFBig<R, B> {
227 #[inline]
228 fn from(fbig: FBig<R, B>) -> Self {
229 Self::new(fbig, Rc::new(RefCell::new(ConstCache::new())))
230 }
231}
232
233impl<R: Round, const B: Word> From<CachedFBig<R, B>> for FBig<R, B> {
234 #[inline]
235 fn from(cached: CachedFBig<R, B>) -> Self {
236 cached.into_fbig()
237 }
238}
239
240impl<R: Round, const B: Word> FBig<R, B> {
241 #[inline]
243 pub fn into_cached(self, cache: Rc<RefCell<ConstCache>>) -> CachedFBig<R, B> {
244 CachedFBig::new(self, cache)
245 }
246}
247
248impl<R: Round, const B: Word> FromStr for CachedFBig<R, B> {
257 type Err = ParseError;
258
259 #[inline]
260 fn from_str(s: &str) -> Result<Self, ParseError> {
261 Ok(FBig::from_str(s)?.into())
262 }
263}
264
265impl<R: Round, const B: Word> From<UBig> for CachedFBig<R, B> {
266 #[inline]
267 fn from(n: UBig) -> Self {
268 FBig::from(n).into()
269 }
270}
271
272impl<R: Round, const B: Word> From<IBig> for CachedFBig<R, B> {
273 #[inline]
274 fn from(n: IBig) -> Self {
275 FBig::from(n).into()
276 }
277}
278
279macro_rules! impl_from_int_for_cached_fbig {
280 ($($t:ty)*) => {$(
281 impl<R: Round, const B: Word> From<$t> for CachedFBig<R, B> {
282 #[inline]
283 fn from(value: $t) -> Self {
284 FBig::from(value).into()
285 }
286 }
287 )*};
288}
289impl_from_int_for_cached_fbig!(u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);
290
291impl<R: Round> TryFrom<f32> for CachedFBig<R, 2> {
292 type Error = ConversionError;
293
294 #[inline]
295 fn try_from(value: f32) -> Result<Self, Self::Error> {
296 FBig::try_from(value).map(Self::from)
297 }
298}
299
300impl<R: Round> TryFrom<f64> for CachedFBig<R, 2> {
301 type Error = ConversionError;
302
303 #[inline]
304 fn try_from(value: f64) -> Result<Self, Self::Error> {
305 FBig::try_from(value).map(Self::from)
306 }
307}
308
309macro_rules! impl_try_from_cached_fbig_for_int {
310 ($($t:ty)*) => {$(
311 impl<R: Round, const B: Word> TryFrom<CachedFBig<R, B>> for $t {
312 type Error = ConversionError;
313
314 #[inline]
315 fn try_from(value: CachedFBig<R, B>) -> Result<Self, Self::Error> {
316 value.fbig.try_into()
317 }
318 }
319 )*};
320}
321impl_try_from_cached_fbig_for_int!(
322 u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize UBig IBig
323);
324
325impl<R: Round> TryFrom<CachedFBig<R, 2>> for f32 {
326 type Error = ConversionError;
327
328 #[inline]
329 fn try_from(value: CachedFBig<R, 2>) -> Result<Self, Self::Error> {
330 value.fbig.try_into()
331 }
332}
333
334impl<R: Round> TryFrom<CachedFBig<R, 2>> for f64 {
335 type Error = ConversionError;
336
337 #[inline]
338 fn try_from(value: CachedFBig<R, 2>) -> Result<Self, Self::Error> {
339 value.fbig.try_into()
340 }
341}
342
343impl<R: Round, const B: Word> Clone for CachedFBig<R, B> {
348 #[inline]
349 fn clone(&self) -> Self {
350 Self {
351 fbig: self.fbig.clone(),
352 cache: Rc::clone(&self.cache),
353 }
354 }
355}
356
357impl<R: Round, const B: Word> Default for CachedFBig<R, B> {
358 #[inline]
360 fn default() -> Self {
361 Self::with_cache(Repr::zero(), Context::new(0))
362 }
363}
364
365impl<R: Round, const B: Word> core::fmt::Debug for CachedFBig<R, B> {
366 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
367 f.debug_struct("CachedFBig")
368 .field("repr", &self.fbig.repr)
369 .field("precision", &self.fbig.context.precision)
370 .finish()
371 }
372}
373
374impl<R: Round, const B: Word> core::fmt::Display for CachedFBig<R, B> {
382 #[inline]
383 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
384 core::fmt::Display::fmt(&self.fbig, f)
385 }
386}
387
388impl<R: Round, const B: Word> core::fmt::LowerExp for CachedFBig<R, B> {
389 #[inline]
390 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
391 core::fmt::LowerExp::fmt(&self.fbig, f)
392 }
393}
394
395impl<R: Round, const B: Word> core::fmt::UpperExp for CachedFBig<R, B> {
396 #[inline]
397 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
398 core::fmt::UpperExp::fmt(&self.fbig, f)
399 }
400}
401
402macro_rules! impl_cached_fmt_with_base {
406 ($base:literal, $trait:ident) => {
407 impl<R: Round> core::fmt::$trait for CachedFBig<R, $base> {
408 #[inline]
409 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
410 core::fmt::$trait::fmt(&self.fbig, f)
411 }
412 }
413 };
414}
415impl_cached_fmt_with_base!(2, Binary);
416impl_cached_fmt_with_base!(2, LowerHex);
417impl_cached_fmt_with_base!(2, UpperHex);
418impl_cached_fmt_with_base!(8, Octal);
419impl_cached_fmt_with_base!(16, LowerHex);
420impl_cached_fmt_with_base!(16, UpperHex);
421
422impl<R1: Round, R2: Round, const B: Word> PartialEq<CachedFBig<R2, B>> for CachedFBig<R1, B> {
423 #[inline]
424 fn eq(&self, other: &CachedFBig<R2, B>) -> bool {
425 self.fbig.repr == other.fbig.repr
427 }
428}
429
430impl<R: Round, const B: Word> Eq for CachedFBig<R, B> {}
431
432impl<R1: Round, R2: Round, const B: Word> PartialOrd<CachedFBig<R2, B>> for CachedFBig<R1, B> {
438 #[inline]
439 fn partial_cmp(&self, other: &CachedFBig<R2, B>) -> Option<Ordering> {
440 self.fbig.partial_cmp(&other.fbig)
441 }
442}
443
444impl<R: Round, const B: Word> Ord for CachedFBig<R, B> {
445 #[inline]
446 fn cmp(&self, other: &Self) -> Ordering {
447 self.fbig.cmp(&other.fbig)
448 }
449}
450
451impl<R: Round, const B: Word> AbsOrd for CachedFBig<R, B> {
452 #[inline]
453 fn abs_cmp(&self, other: &Self) -> Ordering {
454 AbsOrd::abs_cmp(&self.fbig, &other.fbig)
455 }
456}
457
458impl<R: Round, const B: Word> AbsOrd<UBig> for CachedFBig<R, B> {
459 #[inline]
460 fn abs_cmp(&self, other: &UBig) -> Ordering {
461 AbsOrd::abs_cmp(&self.fbig, other)
462 }
463}
464impl<R: Round, const B: Word> AbsOrd<CachedFBig<R, B>> for UBig {
465 #[inline]
466 fn abs_cmp(&self, other: &CachedFBig<R, B>) -> Ordering {
467 AbsOrd::abs_cmp(self, &other.fbig)
468 }
469}
470impl<R: Round, const B: Word> AbsOrd<IBig> for CachedFBig<R, B> {
471 #[inline]
472 fn abs_cmp(&self, other: &IBig) -> Ordering {
473 AbsOrd::abs_cmp(&self.fbig, other)
474 }
475}
476impl<R: Round, const B: Word> AbsOrd<CachedFBig<R, B>> for IBig {
477 #[inline]
478 fn abs_cmp(&self, other: &CachedFBig<R, B>) -> Ordering {
479 AbsOrd::abs_cmp(self, &other.fbig)
480 }
481}
482
483impl<R: Round, const B: Word> Signed for CachedFBig<R, B> {
484 #[inline]
485 fn sign(&self) -> Sign {
486 self.fbig.sign()
487 }
488}
489
490impl<R: Round, const B: Word> EstimatedLog2 for CachedFBig<R, B> {
491 #[inline]
492 fn log2_bounds(&self) -> (f32, f32) {
493 EstimatedLog2::log2_bounds(&self.fbig)
494 }
495
496 #[inline]
497 fn log2_est(&self) -> f32 {
498 EstimatedLog2::log2_est(&self.fbig)
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505 use crate::round::mode;
506 use alloc::format;
507
508 fn handle() -> Rc<RefCell<ConstCache>> {
509 Rc::new(RefCell::new(ConstCache::new()))
510 }
511
512 fn fbig(n: i32, prec: usize) -> FBig<mode::HalfAway, 10> {
515 FBig::from_repr(Repr::new(n.into(), 0), Context::new(prec))
516 }
517
518 #[test]
519 fn test_pi_matches_fbig() {
520 for &precision in &[10usize, 50, 100] {
521 let h = handle();
522 let cached = CachedFBig::<mode::HalfAway, 10>::pi(precision, &h).into_fbig();
523 let direct = FBig::<mode::HalfAway, 10>::pi(precision);
524 assert_eq!(cached, direct, "pi mismatch at precision {precision}");
525 }
526 }
527
528 #[test]
529 fn test_transcendentals_match_fbig() {
530 let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
531 Repr::new(1234.into(), -3), Context::new(50),
533 );
534 let y = FBig::<mode::HalfAway, 10>::from_repr(Repr::new(1234.into(), -3), Context::new(50));
535
536 assert_eq!(x.clone().ln().into_fbig(), y.clone().ln());
537 assert_eq!(x.clone().exp().into_fbig(), y.clone().exp());
538 assert_eq!(x.clone().sin().into_fbig(), y.clone().sin());
539 assert_eq!(x.clone().cos().into_fbig(), y.clone().cos());
540 assert_eq!(x.clone().exp_m1().into_fbig(), y.clone().exp_m1());
541 assert_eq!(x.clone().ln_1p().into_fbig(), y.clone().ln_1p());
542 assert_eq!(x.powf(&x.clone()).into_fbig(), y.clone().powf(&y));
543 }
544
545 #[test]
546 fn test_cache_extension_matches_scratch() {
547 let h = handle();
549 let _pi_100 = CachedFBig::<mode::HalfAway, 10>::pi(100, &h);
550 let pi_1000 = CachedFBig::<mode::HalfAway, 10>::pi(1000, &h).into_fbig();
551 let direct = Context::<mode::HalfAway>::new(1000).pi::<10>(None).value();
552 assert_eq!(pi_1000, direct);
553 }
554
555 #[test]
556 fn test_cache_survives_arithmetic() {
557 let h = handle();
560 let a = CachedFBig::<mode::HalfAway, 10>::from_repr(
561 Repr::new(2.into(), 0),
562 Context::new(30),
563 h.clone(),
564 );
565 let b = CachedFBig::<mode::HalfAway, 10>::from_repr(
566 Repr::new(3.into(), 0),
567 Context::new(30),
568 h.clone(),
569 );
570 let sum_ln = (a.clone() + b.clone()).ln().into_fbig();
571 let expected = (fbig(2, 30) + fbig(3, 30)).ln();
572 assert_eq!(sum_ln, expected);
573 }
574
575 #[test]
576 fn test_arithmetic_matches_fbig() {
577 let a =
578 CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(2.into(), 0), Context::new(20));
579 let b =
580 CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(3.into(), 0), Context::new(20));
581
582 assert_eq!((a.clone() + b.clone()).into_fbig(), fbig(2, 20) + fbig(3, 20));
583 assert_eq!((a.clone() - b.clone()).into_fbig(), fbig(2, 20) - fbig(3, 20));
584 assert_eq!((a.clone() * b.clone()).into_fbig(), fbig(2, 20) * fbig(3, 20));
585 assert_eq!((a.clone() / b.clone()).into_fbig(), fbig(2, 20) / fbig(3, 20));
586 }
587
588 #[test]
589 fn test_debug_compiles() {
590 let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
591 Repr::new(1234.into(), -3),
592 Context::new(50),
593 );
594 let s = format!("{:?}", x);
595 assert!(s.contains("CachedFBig"));
596 }
597
598 #[test]
599 fn test_arithmetic_with_fbig() {
600 let a =
601 CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(2.into(), 0), Context::new(20));
602 let b = fbig(3, 20);
603
604 let c = a.clone() + b.clone();
606 assert_eq!(c.into_fbig(), fbig(2, 20) + fbig(3, 20));
607
608 let d = b.clone() + a.clone();
610 assert_eq!(d.into_fbig(), fbig(3, 20) + fbig(2, 20));
611
612 assert_eq!((a.clone() - b.clone()).into_fbig(), fbig(2, 20) - fbig(3, 20));
614 assert_eq!((a.clone() * b.clone()).into_fbig(), fbig(2, 20) * fbig(3, 20));
615 assert_eq!((a.clone() / b.clone()).into_fbig(), fbig(2, 20) / fbig(3, 20));
616 }
617
618 #[test]
619 fn test_arithmetic_with_primitives() {
620 let a =
621 CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(2.into(), 0), Context::new(20));
622
623 assert_eq!((a.clone() + 3u8).into_fbig(), fbig(2, 20) + 3u8);
625 assert_eq!((a.clone() - 3i32).into_fbig(), fbig(2, 20) - 3i32);
626 assert_eq!((a.clone() * 4u64).into_fbig(), fbig(2, 20) * 4u64);
627
628 assert_eq!((3u8 + a.clone()).into_fbig(), 3u8 + fbig(2, 20));
630 assert_eq!((10i32 - a.clone()).into_fbig(), 10i32 - fbig(2, 20));
631 }
632
633 #[test]
634 fn test_cache_size() {
635 let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
636 Repr::new(1234.into(), -3),
637 Context::new(50),
638 );
639 let _ = x.ln();
640 assert!(x.cache().total_terms() > 0);
642 assert!(x.cache().total_words() > 0);
643 }
644
645 #[test]
646 fn test_cache_clear() {
647 let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
648 Repr::new(1234.into(), -3),
649 Context::new(50),
650 );
651 let before_clear = x.ln().into_fbig();
652 assert!(x.cache().total_terms() > 0);
653
654 x.clear_cache();
655 assert_eq!(x.cache().total_terms(), 0);
656 assert_eq!(x.cache().total_words(), 0);
657
658 let after_clear = x.ln().into_fbig();
660 assert_eq!(after_clear, before_clear);
661 }
662}