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 to_int(&self) -> Rounded<dashu_int::IBig> {
212 self.fbig.clone().to_int()
213 }
214
215 pub fn to_f32(&self) -> Rounded<f32> {
217 self.fbig.clone().to_f32()
218 }
219
220 pub fn to_f64(&self) -> Rounded<f64> {
222 self.fbig.clone().to_f64()
223 }
224
225 pub fn from_parts(significand: dashu_int::IBig, exponent: isize) -> Self {
227 let precision = digit_len::<B>(&significand).max(1);
228 let repr = Repr::new(significand, exponent);
229 Self::with_cache(repr, Context::new(precision))
230 }
231}
232
233impl<R: Round, const B: Word> From<FBig<R, B>> for CachedFBig<R, B> {
238 #[inline]
239 fn from(fbig: FBig<R, B>) -> Self {
240 Self::new(fbig, Rc::new(RefCell::new(ConstCache::new())))
241 }
242}
243
244impl<R: Round, const B: Word> From<CachedFBig<R, B>> for FBig<R, B> {
245 #[inline]
246 fn from(cached: CachedFBig<R, B>) -> Self {
247 cached.into_fbig()
248 }
249}
250
251impl<R: Round, const B: Word> FBig<R, B> {
252 #[inline]
254 pub fn into_cached(self, cache: Rc<RefCell<ConstCache>>) -> CachedFBig<R, B> {
255 CachedFBig::new(self, cache)
256 }
257}
258
259impl<R: Round, const B: Word> FromStr for CachedFBig<R, B> {
268 type Err = ParseError;
269
270 #[inline]
271 fn from_str(s: &str) -> Result<Self, ParseError> {
272 Ok(FBig::from_str(s)?.into())
273 }
274}
275
276impl<R: Round, const B: Word> From<UBig> for CachedFBig<R, B> {
277 #[inline]
278 fn from(n: UBig) -> Self {
279 FBig::from(n).into()
280 }
281}
282
283impl<R: Round, const B: Word> From<IBig> for CachedFBig<R, B> {
284 #[inline]
285 fn from(n: IBig) -> Self {
286 FBig::from(n).into()
287 }
288}
289
290macro_rules! impl_from_int_for_cached_fbig {
291 ($($t:ty)*) => {$(
292 impl<R: Round, const B: Word> From<$t> for CachedFBig<R, B> {
293 #[inline]
294 fn from(value: $t) -> Self {
295 FBig::from(value).into()
296 }
297 }
298 )*};
299}
300impl_from_int_for_cached_fbig!(u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);
301
302impl<R: Round> TryFrom<f32> for CachedFBig<R, 2> {
303 type Error = ConversionError;
304
305 #[inline]
306 fn try_from(value: f32) -> Result<Self, Self::Error> {
307 FBig::try_from(value).map(Self::from)
308 }
309}
310
311impl<R: Round> TryFrom<f64> for CachedFBig<R, 2> {
312 type Error = ConversionError;
313
314 #[inline]
315 fn try_from(value: f64) -> Result<Self, Self::Error> {
316 FBig::try_from(value).map(Self::from)
317 }
318}
319
320macro_rules! impl_try_from_cached_fbig_for_int {
321 ($($t:ty)*) => {$(
322 impl<R: Round, const B: Word> TryFrom<CachedFBig<R, B>> for $t {
323 type Error = ConversionError;
324
325 #[inline]
326 fn try_from(value: CachedFBig<R, B>) -> Result<Self, Self::Error> {
327 value.fbig.try_into()
328 }
329 }
330 )*};
331}
332impl_try_from_cached_fbig_for_int!(
333 u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize UBig IBig
334);
335
336impl<R: Round> TryFrom<CachedFBig<R, 2>> for f32 {
337 type Error = ConversionError;
338
339 #[inline]
340 fn try_from(value: CachedFBig<R, 2>) -> Result<Self, Self::Error> {
341 value.fbig.try_into()
342 }
343}
344
345impl<R: Round> TryFrom<CachedFBig<R, 2>> for f64 {
346 type Error = ConversionError;
347
348 #[inline]
349 fn try_from(value: CachedFBig<R, 2>) -> Result<Self, Self::Error> {
350 value.fbig.try_into()
351 }
352}
353
354impl<R: Round, const B: Word> Clone for CachedFBig<R, B> {
359 #[inline]
360 fn clone(&self) -> Self {
361 Self {
362 fbig: self.fbig.clone(),
363 cache: Rc::clone(&self.cache),
364 }
365 }
366}
367
368impl<R: Round, const B: Word> Default for CachedFBig<R, B> {
369 #[inline]
371 fn default() -> Self {
372 Self::with_cache(Repr::zero(), Context::new(0))
373 }
374}
375
376impl<R: Round, const B: Word> core::fmt::Debug for CachedFBig<R, B> {
377 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
378 f.debug_struct("CachedFBig")
379 .field("repr", &self.fbig.repr)
380 .field("precision", &self.fbig.context.precision)
381 .finish()
382 }
383}
384
385impl<R: Round, const B: Word> core::fmt::Display for CachedFBig<R, B> {
393 #[inline]
394 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
395 core::fmt::Display::fmt(&self.fbig, f)
396 }
397}
398
399impl<R: Round, const B: Word> core::fmt::LowerExp for CachedFBig<R, B> {
400 #[inline]
401 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
402 core::fmt::LowerExp::fmt(&self.fbig, f)
403 }
404}
405
406impl<R: Round, const B: Word> core::fmt::UpperExp for CachedFBig<R, B> {
407 #[inline]
408 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
409 core::fmt::UpperExp::fmt(&self.fbig, f)
410 }
411}
412
413macro_rules! impl_cached_fmt_with_base {
417 ($base:literal, $trait:ident) => {
418 impl<R: Round> core::fmt::$trait for CachedFBig<R, $base> {
419 #[inline]
420 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
421 core::fmt::$trait::fmt(&self.fbig, f)
422 }
423 }
424 };
425}
426impl_cached_fmt_with_base!(2, Binary);
427impl_cached_fmt_with_base!(2, LowerHex);
428impl_cached_fmt_with_base!(2, UpperHex);
429impl_cached_fmt_with_base!(8, Octal);
430impl_cached_fmt_with_base!(16, LowerHex);
431impl_cached_fmt_with_base!(16, UpperHex);
432
433impl<R1: Round, R2: Round, const B: Word> PartialEq<CachedFBig<R2, B>> for CachedFBig<R1, B> {
434 #[inline]
435 fn eq(&self, other: &CachedFBig<R2, B>) -> bool {
436 self.fbig.repr == other.fbig.repr
438 }
439}
440
441impl<R: Round, const B: Word> Eq for CachedFBig<R, B> {}
442
443impl<R1: Round, R2: Round, const B: Word> PartialOrd<CachedFBig<R2, B>> for CachedFBig<R1, B> {
449 #[inline]
450 fn partial_cmp(&self, other: &CachedFBig<R2, B>) -> Option<Ordering> {
451 self.fbig.partial_cmp(&other.fbig)
452 }
453}
454
455impl<R: Round, const B: Word> Ord for CachedFBig<R, B> {
456 #[inline]
457 fn cmp(&self, other: &Self) -> Ordering {
458 self.fbig.cmp(&other.fbig)
459 }
460}
461
462impl<R: Round, const B: Word> AbsOrd for CachedFBig<R, B> {
463 #[inline]
464 fn abs_cmp(&self, other: &Self) -> Ordering {
465 AbsOrd::abs_cmp(&self.fbig, &other.fbig)
466 }
467}
468
469impl<R: Round, const B: Word> AbsOrd<UBig> for CachedFBig<R, B> {
470 #[inline]
471 fn abs_cmp(&self, other: &UBig) -> Ordering {
472 AbsOrd::abs_cmp(&self.fbig, other)
473 }
474}
475impl<R: Round, const B: Word> AbsOrd<CachedFBig<R, B>> for UBig {
476 #[inline]
477 fn abs_cmp(&self, other: &CachedFBig<R, B>) -> Ordering {
478 AbsOrd::abs_cmp(self, &other.fbig)
479 }
480}
481impl<R: Round, const B: Word> AbsOrd<IBig> for CachedFBig<R, B> {
482 #[inline]
483 fn abs_cmp(&self, other: &IBig) -> Ordering {
484 AbsOrd::abs_cmp(&self.fbig, other)
485 }
486}
487impl<R: Round, const B: Word> AbsOrd<CachedFBig<R, B>> for IBig {
488 #[inline]
489 fn abs_cmp(&self, other: &CachedFBig<R, B>) -> Ordering {
490 AbsOrd::abs_cmp(self, &other.fbig)
491 }
492}
493
494impl<R: Round, const B: Word> Signed for CachedFBig<R, B> {
495 #[inline]
496 fn sign(&self) -> Sign {
497 self.fbig.sign()
498 }
499}
500
501impl<R: Round, const B: Word> EstimatedLog2 for CachedFBig<R, B> {
502 #[inline]
503 fn log2_bounds(&self) -> (f32, f32) {
504 EstimatedLog2::log2_bounds(&self.fbig)
505 }
506
507 #[inline]
508 fn log2_est(&self) -> f32 {
509 EstimatedLog2::log2_est(&self.fbig)
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516 use crate::round::mode;
517 use alloc::format;
518
519 fn handle() -> Rc<RefCell<ConstCache>> {
520 Rc::new(RefCell::new(ConstCache::new()))
521 }
522
523 fn fbig(n: i32, prec: usize) -> FBig<mode::HalfAway, 10> {
526 FBig::from_repr(Repr::new(n.into(), 0), Context::new(prec))
527 }
528
529 #[test]
530 fn test_pi_matches_fbig() {
531 for &precision in &[10usize, 50, 100] {
532 let h = handle();
533 let cached = CachedFBig::<mode::HalfAway, 10>::pi(precision, &h).into_fbig();
534 let direct = FBig::<mode::HalfAway, 10>::pi(precision);
535 assert_eq!(cached, direct, "pi mismatch at precision {precision}");
536 }
537 }
538
539 #[test]
540 fn test_transcendentals_match_fbig() {
541 let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
542 Repr::new(1234.into(), -3), Context::new(50),
544 );
545 let y = FBig::<mode::HalfAway, 10>::from_repr(Repr::new(1234.into(), -3), Context::new(50));
546
547 assert_eq!(x.clone().ln().into_fbig(), y.clone().ln());
548 assert_eq!(x.clone().exp().into_fbig(), y.clone().exp());
549 assert_eq!(x.clone().sin().into_fbig(), y.clone().sin());
550 assert_eq!(x.clone().cos().into_fbig(), y.clone().cos());
551 assert_eq!(x.clone().exp_m1().into_fbig(), y.clone().exp_m1());
552 assert_eq!(x.clone().ln_1p().into_fbig(), y.clone().ln_1p());
553 assert_eq!(x.clone().log2().into_fbig(), y.clone().log2());
554 assert_eq!(x.powf(&x.clone()).into_fbig(), y.clone().powf(&y));
555 }
556
557 #[test]
558 fn test_cache_extension_matches_scratch() {
559 let h = handle();
561 let _pi_100 = CachedFBig::<mode::HalfAway, 10>::pi(100, &h);
562 let pi_1000 = CachedFBig::<mode::HalfAway, 10>::pi(1000, &h).into_fbig();
563 let direct = Context::<mode::HalfAway>::new(1000).pi::<10>(None).value();
564 assert_eq!(pi_1000, direct);
565 }
566
567 #[test]
568 fn test_cache_survives_arithmetic() {
569 let h = handle();
572 let a = CachedFBig::<mode::HalfAway, 10>::from_repr(
573 Repr::new(2.into(), 0),
574 Context::new(30),
575 h.clone(),
576 );
577 let b = CachedFBig::<mode::HalfAway, 10>::from_repr(
578 Repr::new(3.into(), 0),
579 Context::new(30),
580 h.clone(),
581 );
582 let sum_ln = (a.clone() + b.clone()).ln().into_fbig();
583 let expected = (fbig(2, 30) + fbig(3, 30)).ln();
584 assert_eq!(sum_ln, expected);
585 }
586
587 #[test]
588 fn test_arithmetic_matches_fbig() {
589 let a =
590 CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(2.into(), 0), Context::new(20));
591 let b =
592 CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(3.into(), 0), Context::new(20));
593
594 assert_eq!((a.clone() + b.clone()).into_fbig(), fbig(2, 20) + fbig(3, 20));
595 assert_eq!((a.clone() - b.clone()).into_fbig(), fbig(2, 20) - fbig(3, 20));
596 assert_eq!((a.clone() * b.clone()).into_fbig(), fbig(2, 20) * fbig(3, 20));
597 assert_eq!((a.clone() / b.clone()).into_fbig(), fbig(2, 20) / fbig(3, 20));
598 }
599
600 #[test]
601 fn test_debug_compiles() {
602 let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
603 Repr::new(1234.into(), -3),
604 Context::new(50),
605 );
606 let s = format!("{:?}", x);
607 assert!(s.contains("CachedFBig"));
608 }
609
610 #[test]
611 fn test_arithmetic_with_fbig() {
612 let a =
613 CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(2.into(), 0), Context::new(20));
614 let b = fbig(3, 20);
615
616 let c = a.clone() + b.clone();
618 assert_eq!(c.into_fbig(), fbig(2, 20) + fbig(3, 20));
619
620 let d = b.clone() + a.clone();
622 assert_eq!(d.into_fbig(), fbig(3, 20) + fbig(2, 20));
623
624 assert_eq!((a.clone() - b.clone()).into_fbig(), fbig(2, 20) - fbig(3, 20));
626 assert_eq!((a.clone() * b.clone()).into_fbig(), fbig(2, 20) * fbig(3, 20));
627 assert_eq!((a.clone() / b.clone()).into_fbig(), fbig(2, 20) / fbig(3, 20));
628 }
629
630 #[test]
631 fn test_arithmetic_with_primitives() {
632 let a =
633 CachedFBig::<mode::HalfAway, 10>::with_cache(Repr::new(2.into(), 0), Context::new(20));
634
635 assert_eq!((a.clone() + 3u8).into_fbig(), fbig(2, 20) + 3u8);
637 assert_eq!((a.clone() - 3i32).into_fbig(), fbig(2, 20) - 3i32);
638 assert_eq!((a.clone() * 4u64).into_fbig(), fbig(2, 20) * 4u64);
639
640 assert_eq!((3u8 + a.clone()).into_fbig(), 3u8 + fbig(2, 20));
642 assert_eq!((10i32 - a.clone()).into_fbig(), 10i32 - fbig(2, 20));
643 }
644
645 #[test]
646 fn test_cache_size() {
647 let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
648 Repr::new(1234.into(), -3),
649 Context::new(50),
650 );
651 let _ = x.ln();
652 assert!(x.cache().total_terms() > 0);
654 assert!(x.cache().total_words() > 0);
655 }
656
657 #[test]
658 fn test_cache_clear() {
659 let x = CachedFBig::<mode::HalfAway, 10>::with_cache(
660 Repr::new(1234.into(), -3),
661 Context::new(50),
662 );
663 let before_clear = x.ln().into_fbig();
664 assert!(x.cache().total_terms() > 0);
665
666 x.clear_cache();
667 assert_eq!(x.cache().total_terms(), 0);
668 assert_eq!(x.cache().total_words(), 0);
669
670 let after_clear = x.ln().into_fbig();
672 assert_eq!(after_clear, before_clear);
673 }
674}