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 pub fn e(precision: usize, cache: &Rc<RefCell<ConstCache>>) -> Self {
140 let fbig = Context::<R>::new(precision).e::<B>().value();
141 Self::from_fbig(fbig, cache)
142 }
143
144 #[inline]
148 pub const fn precision(&self) -> usize {
149 self.fbig.context.precision
150 }
151
152 #[inline]
154 pub fn digits(&self) -> usize {
155 self.fbig.repr.digits()
156 }
157
158 #[inline]
160 pub const fn context(&self) -> Context<R> {
161 self.fbig.context
162 }
163
164 #[inline]
166 pub const fn repr(&self) -> &Repr<B> {
167 &self.fbig.repr
168 }
169
170 #[inline]
172 pub fn into_repr(self) -> Repr<B> {
173 self.fbig.repr
174 }
175
176 #[inline]
178 pub const fn sign(&self) -> Sign {
179 self.fbig.repr.sign()
180 }
181
182 pub fn with_precision(&self, precision: usize) -> Rounded<Self> {
184 self.fbig
185 .clone()
186 .with_precision(precision)
187 .map(|f| Self::from_fbig(f, &self.cache))
188 }
189
190 pub fn with_rounding<NewR: Round>(&self) -> CachedFBig<NewR, B> {
192 CachedFBig::from_fbig(self.fbig.clone().with_rounding::<NewR>(), &self.cache)
193 }
194}
195
196impl<R: Round, const B: Word> CachedFBig<R, B> {
197 pub fn ulp(&self) -> Self {
199 if self.fbig.context.precision == 0 {
200 panic_unlimited_precision();
201 }
202 let repr = Repr {
203 significand: dashu_int::IBig::ONE,
204 exponent: self.fbig.repr.exponent + self.fbig.repr.digits() as isize
205 - self.fbig.context.precision as isize,
206 };
207 Self::from_repr(repr, self.fbig.context, Rc::clone(&self.cache))
208 }
209
210 pub fn ulp_lb(&self) -> Self {
212 Self::from_fbig(self.fbig.ulp_lb(), &self.cache)
213 }
214
215 pub fn signum(&self) -> Self {
217 Self::from_fbig(self.fbig.signum(), &self.cache)
218 }
219
220 pub fn split_at_point(self) -> (Self, Self) {
222 let CachedFBig { fbig, cache } = self;
223 let (a, b) = fbig.split_at_point();
224 (Self::from_fbig(a, &cache), Self::from_fbig(b, &cache))
225 }
226
227 pub fn to_int(&self) -> Rounded<dashu_int::IBig> {
229 self.fbig.clone().to_int()
230 }
231
232 pub fn to_f32(&self) -> Rounded<f32> {
234 self.fbig.clone().to_f32()
235 }
236
237 pub fn to_f64(&self) -> Rounded<f64> {
239 self.fbig.clone().to_f64()
240 }
241
242 pub fn from_parts(significand: dashu_int::IBig, exponent: isize) -> Self {
244 let precision = digit_len::<B>(&significand).max(1);
245 let repr = Repr::new(significand, exponent);
246 Self::with_cache(repr, Context::new(precision))
247 }
248}
249
250impl<R: Round, const B: Word> From<FBig<R, B>> for CachedFBig<R, B> {
255 #[inline]
256 fn from(fbig: FBig<R, B>) -> Self {
257 Self::new(fbig, Rc::new(RefCell::new(ConstCache::new())))
258 }
259}
260
261impl<R: Round, const B: Word> From<CachedFBig<R, B>> for FBig<R, B> {
262 #[inline]
263 fn from(cached: CachedFBig<R, B>) -> Self {
264 cached.into_fbig()
265 }
266}
267
268impl<R: Round, const B: Word> FBig<R, B> {
269 #[inline]
271 pub fn into_cached(self, cache: Rc<RefCell<ConstCache>>) -> CachedFBig<R, B> {
272 CachedFBig::new(self, cache)
273 }
274}
275
276impl<R: Round, const B: Word> FromStr for CachedFBig<R, B> {
285 type Err = ParseError;
286
287 #[inline]
288 fn from_str(s: &str) -> Result<Self, ParseError> {
289 Ok(FBig::from_str(s)?.into())
290 }
291}
292
293impl<R: Round, const B: Word> From<UBig> for CachedFBig<R, B> {
294 #[inline]
295 fn from(n: UBig) -> Self {
296 FBig::from(n).into()
297 }
298}
299
300impl<R: Round, const B: Word> From<IBig> for CachedFBig<R, B> {
301 #[inline]
302 fn from(n: IBig) -> Self {
303 FBig::from(n).into()
304 }
305}
306
307macro_rules! impl_from_int_for_cached_fbig {
308 ($($t:ty)*) => {$(
309 impl<R: Round, const B: Word> From<$t> for CachedFBig<R, B> {
310 #[inline]
311 fn from(value: $t) -> Self {
312 FBig::from(value).into()
313 }
314 }
315 )*};
316}
317impl_from_int_for_cached_fbig!(u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);
318
319impl<R: Round> TryFrom<f32> for CachedFBig<R, 2> {
320 type Error = ConversionError;
321
322 #[inline]
323 fn try_from(value: f32) -> Result<Self, Self::Error> {
324 FBig::try_from(value).map(Self::from)
325 }
326}
327
328impl<R: Round> TryFrom<f64> for CachedFBig<R, 2> {
329 type Error = ConversionError;
330
331 #[inline]
332 fn try_from(value: f64) -> Result<Self, Self::Error> {
333 FBig::try_from(value).map(Self::from)
334 }
335}
336
337macro_rules! impl_try_from_cached_fbig_for_int {
338 ($($t:ty)*) => {$(
339 impl<R: Round, const B: Word> TryFrom<CachedFBig<R, B>> for $t {
340 type Error = ConversionError;
341
342 #[inline]
343 fn try_from(value: CachedFBig<R, B>) -> Result<Self, Self::Error> {
344 value.fbig.try_into()
345 }
346 }
347 )*};
348}
349impl_try_from_cached_fbig_for_int!(
350 u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize UBig IBig
351);
352
353impl<R: Round> TryFrom<CachedFBig<R, 2>> for f32 {
354 type Error = ConversionError;
355
356 #[inline]
357 fn try_from(value: CachedFBig<R, 2>) -> Result<Self, Self::Error> {
358 value.fbig.try_into()
359 }
360}
361
362impl<R: Round> TryFrom<CachedFBig<R, 2>> for f64 {
363 type Error = ConversionError;
364
365 #[inline]
366 fn try_from(value: CachedFBig<R, 2>) -> Result<Self, Self::Error> {
367 value.fbig.try_into()
368 }
369}
370
371impl<R: Round, const B: Word> Clone for CachedFBig<R, B> {
376 #[inline]
377 fn clone(&self) -> Self {
378 Self {
379 fbig: self.fbig.clone(),
380 cache: Rc::clone(&self.cache),
381 }
382 }
383}
384
385impl<R: Round, const B: Word> Default for CachedFBig<R, B> {
386 #[inline]
388 fn default() -> Self {
389 Self::with_cache(Repr::zero(), Context::new(0))
390 }
391}
392
393impl<R: Round, const B: Word> core::fmt::Debug for CachedFBig<R, B> {
394 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
395 f.debug_struct("CachedFBig")
396 .field("repr", &self.fbig.repr)
397 .field("precision", &self.fbig.context.precision)
398 .finish()
399 }
400}
401
402impl<R: Round, const B: Word> core::fmt::Display for CachedFBig<R, B> {
410 #[inline]
411 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
412 core::fmt::Display::fmt(&self.fbig, f)
413 }
414}
415
416impl<R: Round, const B: Word> core::fmt::LowerExp for CachedFBig<R, B> {
417 #[inline]
418 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
419 core::fmt::LowerExp::fmt(&self.fbig, f)
420 }
421}
422
423impl<R: Round, const B: Word> core::fmt::UpperExp for CachedFBig<R, B> {
424 #[inline]
425 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
426 core::fmt::UpperExp::fmt(&self.fbig, f)
427 }
428}
429
430macro_rules! impl_cached_fmt_with_base {
435 ($base:literal, $trait:ident) => {
436 impl<R: Round> core::fmt::$trait for CachedFBig<R, $base> {
437 #[inline]
438 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
439 core::fmt::$trait::fmt(&self.fbig, f)
440 }
441 }
442 };
443}
444impl_cached_fmt_with_base!(2, Binary);
445impl_cached_fmt_with_base!(2, LowerHex);
446impl_cached_fmt_with_base!(2, UpperHex);
447impl_cached_fmt_with_base!(8, Octal);
448impl_cached_fmt_with_base!(16, LowerHex);
449impl_cached_fmt_with_base!(16, UpperHex);
450
451impl<R1: Round, R2: Round, const B: Word> PartialEq<CachedFBig<R2, B>> for CachedFBig<R1, B> {
452 #[inline]
453 fn eq(&self, other: &CachedFBig<R2, B>) -> bool {
454 self.fbig.repr == other.fbig.repr
456 }
457}
458
459impl<R: Round, const B: Word> Eq for CachedFBig<R, B> {}
460
461impl<R1: Round, R2: Round, const B: Word> PartialOrd<CachedFBig<R2, B>> for CachedFBig<R1, B> {
467 #[inline]
468 fn partial_cmp(&self, other: &CachedFBig<R2, B>) -> Option<Ordering> {
469 self.fbig.partial_cmp(&other.fbig)
470 }
471}
472
473impl<R: Round, const B: Word> Ord for CachedFBig<R, B> {
474 #[inline]
475 fn cmp(&self, other: &Self) -> Ordering {
476 self.fbig.cmp(&other.fbig)
477 }
478}
479
480impl<R: Round, const B: Word> AbsOrd for CachedFBig<R, B> {
481 #[inline]
482 fn abs_cmp(&self, other: &Self) -> Ordering {
483 AbsOrd::abs_cmp(&self.fbig, &other.fbig)
484 }
485}
486
487impl<R: Round, const B: Word> AbsOrd<UBig> for CachedFBig<R, B> {
488 #[inline]
489 fn abs_cmp(&self, other: &UBig) -> Ordering {
490 AbsOrd::abs_cmp(&self.fbig, other)
491 }
492}
493impl<R: Round, const B: Word> AbsOrd<CachedFBig<R, B>> for UBig {
494 #[inline]
495 fn abs_cmp(&self, other: &CachedFBig<R, B>) -> Ordering {
496 AbsOrd::abs_cmp(self, &other.fbig)
497 }
498}
499impl<R: Round, const B: Word> AbsOrd<IBig> for CachedFBig<R, B> {
500 #[inline]
501 fn abs_cmp(&self, other: &IBig) -> Ordering {
502 AbsOrd::abs_cmp(&self.fbig, other)
503 }
504}
505impl<R: Round, const B: Word> AbsOrd<CachedFBig<R, B>> for IBig {
506 #[inline]
507 fn abs_cmp(&self, other: &CachedFBig<R, B>) -> Ordering {
508 AbsOrd::abs_cmp(self, &other.fbig)
509 }
510}
511
512impl<R: Round, const B: Word> Signed for CachedFBig<R, B> {
513 #[inline]
514 fn sign(&self) -> Sign {
515 self.fbig.sign()
516 }
517}
518
519impl<R: Round, const B: Word> EstimatedLog2 for CachedFBig<R, B> {
520 #[inline]
521 fn log2_bounds(&self) -> (f32, f32) {
522 EstimatedLog2::log2_bounds(&self.fbig)
523 }
524
525 #[inline]
526 fn log2_est(&self) -> f32 {
527 EstimatedLog2::log2_est(&self.fbig)
528 }
529}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534 use crate::round::mode;
535 use alloc::format;
536
537 fn handle() -> Rc<RefCell<ConstCache>> {
538 Rc::new(RefCell::new(ConstCache::new()))
539 }
540
541 fn fbig(n: i32, prec: usize) -> FBig<mode::HalfAway, 10> {
544 FBig::from_repr(Repr::new(n.into(), 0), Context::new(prec))
545 }
546
547 #[test]
548 fn test_pi_matches_fbig() {
549 for &precision in &[10usize, 50, 100] {
550 let h = handle();
551 let cached = CachedFBig::<mode::HalfAway, 10>::pi(precision, &h).into_fbig();
552 let direct = FBig::<mode::HalfAway, 10>::pi(precision);
553 assert_eq!(cached, direct, "pi mismatch at precision {precision}");
554 }
555 }
556
557 #[test]
558 fn test_transcendentals_match_fbig() {
559 let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
560 Repr::new(1234.into(), -3), Context::new(50),
562 );
563 let y = FBig::<mode::HalfAway, 10>::from_repr(Repr::new(1234.into(), -3), Context::new(50));
564
565 assert_eq!(x.clone().ln().into_fbig(), y.clone().ln());
566 assert_eq!(x.clone().exp().into_fbig(), y.clone().exp());
567 assert_eq!(x.clone().sin().into_fbig(), y.clone().sin());
568 assert_eq!(x.clone().cos().into_fbig(), y.clone().cos());
569 assert_eq!(x.clone().exp_m1().into_fbig(), y.clone().exp_m1());
570 assert_eq!(x.clone().ln_1p().into_fbig(), y.clone().ln_1p());
571 assert_eq!(x.clone().log2().into_fbig(), y.clone().log2());
572 assert_eq!(x.clone().log10().into_fbig(), y.clone().log10());
573 assert_eq!(x.powf(&x.clone()).into_fbig(), y.clone().powf(&y));
574 }
575
576 #[test]
577 fn test_cache_extension_matches_scratch() {
578 let h = handle();
580 let _pi_100 = CachedFBig::<mode::HalfAway, 10>::pi(100, &h);
581 let pi_1000 = CachedFBig::<mode::HalfAway, 10>::pi(1000, &h).into_fbig();
582 let direct = Context::<mode::HalfAway>::new(1000).pi::<10>(None).value();
583 assert_eq!(pi_1000, direct);
584 }
585
586 #[test]
587 fn test_cache_survives_arithmetic() {
588 let h = handle();
591 let a = CachedFBig::<mode::HalfAway, 10>::from_repr(
592 Repr::new(2.into(), 0),
593 Context::new(30),
594 h.clone(),
595 );
596 let b = CachedFBig::<mode::HalfAway, 10>::from_repr(
597 Repr::new(3.into(), 0),
598 Context::new(30),
599 h.clone(),
600 );
601 let sum_ln = (a.clone() + b.clone()).ln().into_fbig();
602 let expected = (fbig(2, 30) + fbig(3, 30)).ln();
603 assert_eq!(sum_ln, expected);
604 }
605
606 #[test]
607 fn test_arithmetic_matches_fbig() {
608 let a =
609 CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(2.into(), 0), Context::new(20));
610 let b =
611 CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(3.into(), 0), Context::new(20));
612
613 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 assert_eq!((a.clone() / b.clone()).into_fbig(), fbig(2, 20) / fbig(3, 20));
617 }
618
619 #[test]
620 fn test_debug_compiles() {
621 let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
622 Repr::new(1234.into(), -3),
623 Context::new(50),
624 );
625 let s = format!("{:?}", x);
626 assert!(s.contains("CachedFBig"));
627 }
628
629 #[test]
630 fn test_arithmetic_with_fbig() {
631 let a =
632 CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(2.into(), 0), Context::new(20));
633 let b = fbig(3, 20);
634
635 let c = a.clone() + b.clone();
637 assert_eq!(c.into_fbig(), fbig(2, 20) + fbig(3, 20));
638
639 let d = b.clone() + a.clone();
641 assert_eq!(d.into_fbig(), fbig(3, 20) + fbig(2, 20));
642
643 assert_eq!((a.clone() - b.clone()).into_fbig(), fbig(2, 20) - fbig(3, 20));
645 assert_eq!((a.clone() * b.clone()).into_fbig(), fbig(2, 20) * fbig(3, 20));
646 assert_eq!((a.clone() / b.clone()).into_fbig(), fbig(2, 20) / fbig(3, 20));
647 }
648
649 #[test]
650 fn test_arithmetic_with_primitives() {
651 let a =
652 CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(2.into(), 0), Context::new(20));
653
654 assert_eq!((a.clone() + 3u8).into_fbig(), fbig(2, 20) + 3u8);
656 assert_eq!((a.clone() - 3i32).into_fbig(), fbig(2, 20) - 3i32);
657 assert_eq!((a.clone() * 4u64).into_fbig(), fbig(2, 20) * 4u64);
658
659 assert_eq!((3u8 + a.clone()).into_fbig(), 3u8 + fbig(2, 20));
661 assert_eq!((10i32 - a.clone()).into_fbig(), 10i32 - fbig(2, 20));
662 }
663
664 #[test]
665 fn test_cache_size() {
666 let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
667 Repr::new(1234.into(), -3),
668 Context::new(50),
669 );
670 let _ = x.ln();
671 assert!(x.cache().total_terms() > 0);
673 assert!(x.cache().total_words() > 0);
674 }
675
676 #[test]
677 fn test_cache_clear() {
678 let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
679 Repr::new(1234.into(), -3),
680 Context::new(50),
681 );
682 let before_clear = x.ln().into_fbig();
683 assert!(x.cache().total_terms() > 0);
684
685 x.clear_cache();
686 assert_eq!(x.cache().total_terms(), 0);
687 assert_eq!(x.cache().total_words(), 0);
688
689 let after_clear = x.ln().into_fbig();
691 assert_eq!(after_clear, before_clear);
692 }
693
694 #[test]
695 fn test_mirrored_methods_match_fbig() {
696 let cached = CachedFBig::<mode::HalfAway, 10>::with_cache(
697 Repr::new(1234.into(), -3), Context::new(50),
699 );
700 let plain = cached.as_fbig().clone();
701
702 assert_eq!(cached.round().into_fbig(), plain.round());
704 assert_eq!(cached.trunc().into_fbig(), plain.trunc());
705 assert_eq!(cached.ceil().into_fbig(), plain.ceil());
706 assert_eq!(cached.floor().into_fbig(), plain.floor());
707 assert_eq!(cached.fract().into_fbig(), plain.fract());
708 assert_eq!(cached.signum().into_fbig(), plain.signum());
709 assert_eq!(cached.ulp_lb().into_fbig(), plain.ulp_lb());
710
711 let (ci, cf) = cached.clone().split_at_point();
712 let (pi, pf) = plain.clone().split_at_point();
713 assert_eq!((ci.into_fbig(), cf.into_fbig()), (pi, pf));
714
715 assert_eq!(cached.nth_root(2).into_fbig(), plain.nth_root(2));
717 let other = CachedFBig::<mode::HalfAway, 10>::with_cache(
718 Repr::new(300.into(), -2), Context::new(50),
720 );
721 assert_eq!(cached.hypot(&other).into_fbig(), plain.hypot(other.as_fbig()));
722
723 assert_eq!(cached.quantize(-1).value().into_fbig(), plain.quantize(-1).value());
725 assert_eq!(cached.to_decimal().value().into_fbig(), plain.to_decimal().value());
726 assert_eq!(cached.to_binary().value().into_fbig(), plain.to_binary().value());
727 assert_eq!(
728 cached.clone().with_base::<2>().value().into_fbig(),
729 plain.clone().with_base::<2>().value()
730 );
731 assert_eq!(
732 cached
733 .clone()
734 .with_base_and_precision::<2>(40)
735 .value()
736 .into_fbig(),
737 plain.clone().with_base_and_precision::<2>(40).value()
738 );
739 }
740}