Skip to main content

dashu_cmplx/
cbig_cached.rs

1//! A cached complex number — [`CBig`] with a shared constant cache attached.
2//!
3//! This is the complex twin of [`dashu_float::CachedFBig`]: the in-memory value is a [`CBig`], plus
4//! an [`Rc<RefCell<ConstCache>>`] handle shared across a computation chain. The complex
5//! transcendentals are built entirely from real [`FBig`] operations, so the *same* [`ConstCache`]
6//! (π, ln2, ln10, …) is reused unchanged — there are no complex-specific constants to store.
7
8use alloc::rc::Rc;
9use core::cell::RefCell;
10use core::cmp::Ordering;
11use core::str::FromStr;
12
13use dashu_base::{AbsOrd, ConversionError, ParseError};
14use dashu_float::round::{mode, Round};
15use dashu_float::{CachedFBig, ConstCache, FBig, Repr};
16use dashu_int::{IBig, UBig, Word};
17
18use crate::cbig::CBig;
19use crate::repr::Context;
20
21/// A complex number that carries a shared handle to a [`ConstCache`].
22///
23/// It is functionally a [`CBig`]: same in-memory representation (`cbig`),
24/// plus an [`Rc<RefCell<ConstCache>>`] handle. The difference is that the
25/// transcendental operations (`ln`, `exp`, `sin`, `cos`, …) thread that handle
26/// into the underlying [`Context`] methods, so they reuse and progressively
27/// extend the cached exact binary-splitting state for the real constants
28/// (π, ln2, ln10, …) instead of recomputing them from scratch on every call.
29///
30/// `Context`/`CBig` themselves stay `Copy` + `Send` + `Sync` + `no_std` (so
31/// `static_cbig!` keeps producing a `CBig`); only this cached wrapper is
32/// `!Send + !Sync`, because it shares state through an `Rc<RefCell<..>>`.
33/// To share one cache across threads, build an analogous type over
34/// `Arc<Mutex<ConstCache>>` instead (the [`Context`] methods accept
35/// `Option<&mut ConstCache>`, independent of the container).
36///
37/// Every value-producing operation returns a `CachedCBig` that preserves the
38/// handle, so `(z + w).ln().exp()` stays cached throughout — no silent cache
39/// loss. When two `CachedCBig` values with different cache handles interact in
40/// a binary operation, the LHS (left-hand-side) cache is preserved in the
41/// result. For `CBig op CachedCBig` (and `FBig op CachedCBig`), the
42/// `CachedCBig` operand's cache is preserved.
43///
44/// # Examples
45///
46/// ```
47/// use core::cell::RefCell;
48/// use std::rc::Rc;
49/// use dashu_cmplx::{CBig, CachedCBig, ConstCache};
50/// use dashu_float::{FBig, round::mode::HalfAway};
51///
52/// type C = CBig<HalfAway, 10>;
53/// // build a cached 1+1i from a plain CBig
54/// let z = CachedCBig::from(C::from_parts(FBig::from(1), FBig::from(1)));
55///
56/// // ln / exp reuse the same shared cache handle
57/// let _ = z.clone().ln().exp();
58/// ```
59pub struct CachedCBig<R: Round = mode::Zero, const B: Word = 2> {
60    pub(crate) cbig: CBig<R, B>,
61    pub(crate) cache: Rc<RefCell<ConstCache>>,
62}
63
64impl<R: Round, const B: Word> CachedCBig<R, B> {
65    /// Wrap a [`CBig`], sharing the given cache handle.
66    #[inline]
67    pub fn new(value: CBig<R, B>, cache: Rc<RefCell<ConstCache>>) -> Self {
68        Self { cbig: value, cache }
69    }
70
71    /// Create a [`CachedCBig`] from its real and imaginary parts, attaching a *fresh* cache.
72    /// Drop-in for [`CBig::from_parts`].
73    #[inline]
74    pub fn from_parts(re: FBig<R, B>, im: FBig<R, B>) -> Self {
75        Self::new(CBig::from_parts(re, im), Rc::new(RefCell::new(ConstCache::new())))
76    }
77
78    /// Build a `CachedCBig` from a [`CBig`] result, re-attaching this value's
79    /// shared cache handle (cloned cheaply via `Rc`).
80    #[inline]
81    pub(crate) fn from_cbig(cbig: CBig<R, B>, cache: &Rc<RefCell<ConstCache>>) -> Self {
82        Self {
83            cbig,
84            cache: Rc::clone(cache),
85        }
86    }
87
88    /// Borrow the inner [`CBig`].
89    #[inline]
90    pub fn as_cbig(&self) -> &CBig<R, B> {
91        &self.cbig
92    }
93
94    /// Drop the cache handle and return the underlying [`CBig`].
95    #[inline]
96    pub fn into_cbig(self) -> CBig<R, B> {
97        self.cbig
98    }
99
100    /// Borrow the shared constant cache immutably.
101    ///
102    /// Use this to inspect cache state, e.g. `cached.cache().total_terms()`.
103    #[inline]
104    pub fn cache(&self) -> impl core::ops::Deref<Target = ConstCache> + '_ {
105        self.cache.borrow()
106    }
107
108    /// Clear all cached constant state, freeing the underlying memory.
109    ///
110    /// The next transcendental operation will recompute constants from scratch.
111    #[inline]
112    pub fn clear_cache(&self) {
113        self.cache.borrow_mut().clear();
114    }
115
116    // ----- accessors (delegate to the inner CBig) -----
117
118    /// Get the shared [`Context`] of the complex number (see [`CBig::context`]).
119    #[inline]
120    pub const fn context(&self) -> Context<R> {
121        self.cbig.context
122    }
123
124    /// Get the precision limit (`0` = unlimited); both parts share it (see [`CBig::precision`]).
125    #[inline]
126    pub const fn precision(&self) -> usize {
127        self.cbig.context.precision()
128    }
129
130    /// Get a reference to the real part's raw representation (see [`CBig::re`]).
131    #[inline]
132    pub const fn re(&self) -> &Repr<B> {
133        &self.cbig.re
134    }
135
136    /// Get a reference to the imaginary part's raw representation (see [`CBig::im`]).
137    #[inline]
138    pub const fn im(&self) -> &Repr<B> {
139        &self.cbig.im
140    }
141
142    /// Decompose into the real and imaginary parts as [`CachedFBig`]s, **sharing this value's
143    /// cache handle** so transcendentals on either part stay cached.
144    ///
145    /// This is an intentional divergence from [`CBig::into_parts`], which returns `(FBig, FBig)`:
146    /// here both parts carry the original shared cache (cheaply, via `Rc`), preserving the
147    /// accumulated constant state across the decomposition.
148    #[inline]
149    pub fn into_parts(self) -> (CachedFBig<R, B>, CachedFBig<R, B>) {
150        let (re, im) = self.cbig.into_parts();
151        (CachedFBig::new(re, Rc::clone(&self.cache)), CachedFBig::new(im, self.cache))
152    }
153
154    /// Determine if the complex number is numerically zero (both parts `±0`) (see [`CBig::is_zero`]).
155    #[inline]
156    pub fn is_zero(&self) -> bool {
157        self.cbig.is_zero()
158    }
159
160    /// Determine if either part is infinite (see [`CBig::is_infinite`]).
161    #[inline]
162    pub fn is_infinite(&self) -> bool {
163        self.cbig.is_infinite()
164    }
165
166    /// Determine if the complex number is finite (neither part infinite) (see [`CBig::is_finite`]).
167    #[inline]
168    pub fn is_finite(&self) -> bool {
169        self.cbig.is_finite()
170    }
171}
172
173// ---------------------------------------------------------------------------
174// From / Into
175// ---------------------------------------------------------------------------
176
177impl<R: Round, const B: Word> From<CBig<R, B>> for CachedCBig<R, B> {
178    #[inline]
179    fn from(cbig: CBig<R, B>) -> Self {
180        Self::new(cbig, Rc::new(RefCell::new(ConstCache::new())))
181    }
182}
183
184impl<R: Round, const B: Word> From<CachedCBig<R, B>> for CBig<R, B> {
185    #[inline]
186    fn from(cached: CachedCBig<R, B>) -> Self {
187        cached.into_cbig()
188    }
189}
190
191impl<R: Round, const B: Word> CBig<R, B> {
192    /// Attach a shared cache handle, turning this [`CBig`] into a [`CachedCBig`].
193    #[inline]
194    pub fn into_cached(self, cache: Rc<RefCell<ConstCache>>) -> CachedCBig<R, B> {
195        CachedCBig::new(self, cache)
196    }
197}
198
199// ---------------------------------------------------------------------------
200// FromStr / From / TryFrom
201//
202// Construction from an external value (string, FBig, integer, primitive float) attaches
203// a *fresh* cache, exactly like `From<CBig>` above — there is no existing handle to share.
204// ---------------------------------------------------------------------------
205
206impl<R: Round, const B: Word> From<FBig<R, B>> for CachedCBig<R, B> {
207    #[inline]
208    fn from(re: FBig<R, B>) -> Self {
209        CBig::from(re).into()
210    }
211}
212
213impl<R: Round, const B: Word> From<UBig> for CachedCBig<R, B> {
214    #[inline]
215    fn from(v: UBig) -> Self {
216        CBig::from(v).into()
217    }
218}
219
220impl<R: Round, const B: Word> From<IBig> for CachedCBig<R, B> {
221    #[inline]
222    fn from(v: IBig) -> Self {
223        CBig::from(v).into()
224    }
225}
226
227macro_rules! impl_from_int_for_cached_cbig {
228    ($($t:ty)*) => {$(
229        impl<R: Round, const B: Word> From<$t> for CachedCBig<R, B> {
230            #[inline]
231            fn from(value: $t) -> Self {
232                CBig::from(value).into()
233            }
234        }
235    )*};
236}
237impl_from_int_for_cached_cbig!(u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);
238
239impl<R: Round> TryFrom<f32> for CachedCBig<R, 2> {
240    type Error = ConversionError;
241
242    #[inline]
243    fn try_from(value: f32) -> Result<Self, Self::Error> {
244        CBig::try_from(value).map(Self::from)
245    }
246}
247
248impl<R: Round> TryFrom<f64> for CachedCBig<R, 2> {
249    type Error = ConversionError;
250
251    #[inline]
252    fn try_from(value: f64) -> Result<Self, Self::Error> {
253        CBig::try_from(value).map(Self::from)
254    }
255}
256
257impl<R: Round, const B: Word> FromStr for CachedCBig<R, B> {
258    type Err = ParseError;
259
260    #[inline]
261    fn from_str(s: &str) -> Result<Self, ParseError> {
262        Ok(CBig::from_str(s)?.into())
263    }
264}
265
266macro_rules! impl_try_from_cached_cbig_for_int {
267    ($($t:ty)*) => {$(
268        impl<R: Round, const B: Word> TryFrom<CachedCBig<R, B>> for $t {
269            type Error = ConversionError;
270
271            #[inline]
272            fn try_from(value: CachedCBig<R, B>) -> Result<Self, Self::Error> {
273                value.cbig.try_into()
274            }
275        }
276    )*};
277}
278impl_try_from_cached_cbig_for_int!(
279    u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize UBig IBig
280);
281
282impl<R: Round, const B: Word> TryFrom<CachedCBig<R, B>> for FBig<R, B> {
283    type Error = ConversionError;
284
285    #[inline]
286    fn try_from(value: CachedCBig<R, B>) -> Result<Self, Self::Error> {
287        value.cbig.try_into()
288    }
289}
290
291impl<R: Round> TryFrom<CachedCBig<R, 2>> for f32 {
292    type Error = ConversionError;
293
294    #[inline]
295    fn try_from(value: CachedCBig<R, 2>) -> Result<Self, Self::Error> {
296        value.cbig.try_into()
297    }
298}
299
300impl<R: Round> TryFrom<CachedCBig<R, 2>> for f64 {
301    type Error = ConversionError;
302
303    #[inline]
304    fn try_from(value: CachedCBig<R, 2>) -> Result<Self, Self::Error> {
305        value.cbig.try_into()
306    }
307}
308
309// ---------------------------------------------------------------------------
310// Clone / Default / Debug / Display
311// ---------------------------------------------------------------------------
312
313impl<R: Round, const B: Word> Clone for CachedCBig<R, B> {
314    #[inline]
315    fn clone(&self) -> Self {
316        Self {
317            cbig: self.cbig.clone(),
318            cache: Rc::clone(&self.cache),
319        }
320    }
321}
322
323impl<R: Round, const B: Word> Default for CachedCBig<R, B> {
324    /// Default value: `0 + 0i` with a fresh cache.
325    #[inline]
326    fn default() -> Self {
327        Self::from(CBig::default())
328    }
329}
330
331impl<R: Round, const B: Word> core::fmt::Debug for CachedCBig<R, B> {
332    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
333        core::fmt::Debug::fmt(&self.cbig, f)
334    }
335}
336
337// Display delegates to the inner CBig so the rendered string is identical (algebraic `a+bi`).
338impl<R: Round, const B: Word> core::fmt::Display for CachedCBig<R, B> {
339    #[inline]
340    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
341        core::fmt::Display::fmt(&self.cbig, f)
342    }
343}
344
345// ---------------------------------------------------------------------------
346// PartialEq / Eq / Ord / AbsOrd (delegate to the inner CBig — value semantics)
347// ---------------------------------------------------------------------------
348
349impl<R1: Round, R2: Round, const B: Word> PartialEq<CachedCBig<R2, B>> for CachedCBig<R1, B> {
350    #[inline]
351    fn eq(&self, other: &CachedCBig<R2, B>) -> bool {
352        // value equality, mirroring CBig (compares the representations only).
353        self.cbig.re == other.cbig.re && self.cbig.im == other.cbig.im
354    }
355}
356
357impl<R: Round, const B: Word> Eq for CachedCBig<R, B> {}
358
359impl<R1: Round, R2: Round, const B: Word> PartialOrd<CachedCBig<R2, B>> for CachedCBig<R1, B> {
360    #[inline]
361    fn partial_cmp(&self, other: &CachedCBig<R2, B>) -> Option<Ordering> {
362        self.cbig.partial_cmp(&other.cbig)
363    }
364}
365
366impl<R: Round, const B: Word> Ord for CachedCBig<R, B> {
367    #[inline]
368    fn cmp(&self, other: &Self) -> Ordering {
369        self.cbig.cmp(&other.cbig)
370    }
371}
372
373impl<R: Round, const B: Word> AbsOrd for CachedCBig<R, B> {
374    #[inline]
375    fn abs_cmp(&self, other: &Self) -> Ordering {
376        AbsOrd::abs_cmp(&self.cbig, &other.cbig)
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383    use alloc::format;
384    use dashu_float::round::mode;
385
386    type F = FBig<mode::HalfAway, 10>;
387    type C = CBig<mode::HalfAway, 10>;
388    type CC = CachedCBig<mode::HalfAway, 10>;
389
390    fn handle() -> Rc<RefCell<ConstCache>> {
391        Rc::new(RefCell::new(ConstCache::new()))
392    }
393
394    /// A cached `re+im·i` at precision 53 sharing `h`.
395    fn cached(re: i32, im: i32, h: &Rc<RefCell<ConstCache>>) -> CC {
396        let mk = |v: i32| -> F { F::from(v).with_precision(53).value() };
397        CachedCBig::new(CBig::from_parts(mk(re), mk(im)), h.clone())
398    }
399
400    /// The matching plain CBig (precision 53) for value comparison.
401    fn c(re: i32, im: i32) -> C {
402        let mk = |v: i32| -> F { F::from(v).with_precision(53).value() };
403        CBig::from_parts(mk(re), mk(im))
404    }
405
406    #[test]
407    fn constructors_and_accessors() {
408        let h = handle();
409        let z = cached(3, 4, &h);
410        assert_eq!(z.as_cbig().re().significand(), &3.into());
411        assert_eq!(z.as_cbig().im().significand(), &4.into());
412        assert_eq!(z.precision(), 53);
413        assert_eq!(z.context().precision(), 53);
414        assert!(!z.is_zero());
415        assert!(!z.is_infinite());
416        assert!(z.is_finite());
417
418        // Default = 0+0i; From<CBig> / into_cbig round-trip; from_parts attaches a fresh cache.
419        assert!(CC::default().is_zero());
420        let plain = c(3, 4);
421        assert_eq!(CC::from(plain.clone()).into_cbig(), plain);
422        let fp = CC::from_parts(F::from(3), F::from(4));
423        assert_eq!(fp.as_cbig().re().significand(), &3.into());
424    }
425
426    #[test]
427    fn fmt_matches_cbig() {
428        for &(re, im) in &[
429            (0, 0),
430            (5, 0),
431            (0, 1),
432            (0, -1),
433            (1, 2),
434            (-3, -4),
435            (1, 1),
436            (2, -1),
437        ] {
438            let cval = c(re, im);
439            let ccval = cached(re, im, &handle());
440            assert_eq!(format!("{}", ccval), format!("{}", cval), "Display {re}+{im}i");
441            assert_eq!(format!("{:?}", ccval), format!("{:?}", cval), "Debug {re}+{im}i");
442        }
443    }
444
445    #[test]
446    fn fromstr_matches_cbig() {
447        for s in &["0", "1", "i", "-i", "3+4i", "-3-4i", "1+i", "2-i"] {
448            assert_eq!(CC::from_str(s).unwrap().as_cbig(), &C::from_str(s).unwrap());
449        }
450    }
451
452    #[test]
453    fn ordering_matches_cbig() {
454        let (a, b) = (c(1, 9), c(2, 0));
455        let (ca, cb) = (cached(1, 9, &handle()), cached(2, 0, &handle()));
456        assert_eq!(ca.cmp(&cb), a.cmp(&b));
457        assert_eq!(ca.partial_cmp(&cb), a.partial_cmp(&b));
458        assert_eq!(AbsOrd::abs_cmp(&ca, &cb), AbsOrd::abs_cmp(&a, &b));
459        // PartialEq / Eq
460        assert!(cached(3, 4, &handle()) == cached(3, 4, &handle()));
461        assert!(cached(3, 4, &handle()) != cached(3, 5, &handle()));
462    }
463
464    #[test]
465    fn conversions_match_cbig() {
466        // From<FBig> / From<UBig> / From<IBig> / From<primitive>
467        assert_eq!(CC::from(F::from(7)).as_cbig(), &C::from(F::from(7)));
468        assert_eq!(CC::from(UBig::from(123u32)).as_cbig(), &C::from(UBig::from(123u32)));
469        assert_eq!(CC::from(IBig::from(-456)).as_cbig(), &C::from(IBig::from(-456)));
470        assert_eq!(CC::from(7u8).as_cbig(), &C::from(7u8));
471        assert_eq!(CC::from(-9i32).as_cbig(), &C::from(-9i32));
472
473        // TryFrom<CachedCBig> for ints / FBig delegates to the inner CBig.
474        let ccval = cached(9, 0, &handle());
475        let cval = c(9, 0);
476        assert_eq!(IBig::try_from(ccval.clone()).ok(), IBig::try_from(cval.clone()).ok());
477        assert_eq!(i32::try_from(ccval.clone()).ok(), i32::try_from(cval.clone()).ok());
478        assert_eq!(F::try_from(ccval).ok(), F::try_from(cval).ok());
479        // nonzero imaginary → LossOfPrecision on both.
480        assert_eq!(F::try_from(cached(9, 1, &handle())).err(), F::try_from(c(9, 1)).err());
481    }
482
483    #[test]
484    fn float_conversions_match_cbig() {
485        // TryFrom<f64> / TryFrom<CachedCBig<R,2>> for f64 (base 2 only).
486        type C2 = CBig<mode::HalfAway, 2>;
487        type CC2 = CachedCBig<mode::HalfAway, 2>;
488        let cv = C2::try_from(2.5f64).unwrap();
489        let ccv = CC2::try_from(2.5f64).unwrap();
490        assert_eq!(ccv.as_cbig(), &cv);
491        assert_eq!(f64::try_from(ccv).unwrap(), 2.5f64);
492        assert!(CC2::try_from(f32::NAN).is_err());
493    }
494
495    #[test]
496    fn into_parts_shares_cache() {
497        let h = handle();
498        let z = cached(2, 3, &h);
499        // populate the cache, then decompose — both parts must carry the shared handle
500        let _ = z.clone().ln();
501        let (re, im) = z.into_parts();
502        assert!(re.cache().total_terms() > 0);
503        assert!(im.cache().total_terms() > 0);
504        // and the parts' values match CBig::into_parts
505        let (cre, cim) = c(2, 3).into_parts();
506        assert_eq!(re.as_fbig(), &cre);
507        assert_eq!(im.as_fbig(), &cim);
508    }
509
510    #[test]
511    fn cache_clear_zeros_and_recomputes() {
512        let h = handle();
513        let z = cached(2, 0, &h);
514        let before = z.ln().into_cbig();
515        assert!(z.cache().total_terms() > 0);
516
517        z.clear_cache();
518        assert_eq!(z.cache().total_terms(), 0);
519        assert_eq!(z.cache().total_words(), 0);
520
521        // After clearing, recomputation still produces the same result.
522        assert_eq!(z.ln().into_cbig(), before);
523    }
524}