Skip to main content

dashu_cmplx/
cbig_cached_ops.rs

1//! Operators for [`CachedCBig`] — all binary/unary operators and the transcendental convenience
2//! methods, with cache preservation.
3//!
4//! Structured to mirror `dashu-float`'s `fbig_cached_ops.rs`: every repeated operator family is a
5//! macro, and the transcendentals split into cache-threading forwards (which bypass [`CBig`]'s
6//! convenience layer — it hardcodes `None` — and call the complex [`Context`] with `Some(cache)`)
7//! and plain delegations (no cache involved, just preserve the handle).
8
9use alloc::rc::Rc;
10use core::iter::{Product, Sum};
11use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
12
13use dashu_base::Inverse;
14use dashu_float::round::Round;
15use dashu_float::FBig;
16use dashu_int::{IBig, Word};
17
18use crate::cbig::CBig;
19use crate::cbig_cached::CachedCBig;
20
21// ===========================================================================
22// CachedCBig op CachedCBig (preserves LHS cache) — owned + Assign
23// ===========================================================================
24
25macro_rules! impl_cached_cbig_binop {
26    ($Op:ident, $op:ident) => {
27        impl<R: Round, const B: Word> $Op<CachedCBig<R, B>> for CachedCBig<R, B> {
28            type Output = CachedCBig<R, B>;
29            #[inline]
30            fn $op(self, rhs: CachedCBig<R, B>) -> Self::Output {
31                CachedCBig::from_cbig($Op::$op(self.cbig, rhs.cbig), &self.cache)
32            }
33        }
34    };
35}
36macro_rules! impl_cached_cbig_binop_assign {
37    ($OpAssign:ident, $op_assign:ident, $Op:ident, $op:ident) => {
38        impl<R: Round, const B: Word> $OpAssign<CachedCBig<R, B>> for CachedCBig<R, B> {
39            #[inline]
40            fn $op_assign(&mut self, rhs: CachedCBig<R, B>) {
41                self.cbig = $Op::$op(self.cbig.clone(), rhs.cbig);
42            }
43        }
44    };
45}
46impl_cached_cbig_binop!(Add, add);
47impl_cached_cbig_binop!(Sub, sub);
48impl_cached_cbig_binop!(Mul, mul);
49impl_cached_cbig_binop!(Div, div);
50impl_cached_cbig_binop_assign!(AddAssign, add_assign, Add, add);
51impl_cached_cbig_binop_assign!(SubAssign, sub_assign, Sub, sub);
52impl_cached_cbig_binop_assign!(MulAssign, mul_assign, Mul, mul);
53impl_cached_cbig_binop_assign!(DivAssign, div_assign, Div, div);
54
55// ===========================================================================
56// CachedCBig op CBig and CBig op CachedCBig (cache preserved from CachedCBig side)
57// ===========================================================================
58
59macro_rules! impl_cached_binop_one_way_with_cbig {
60    ($Op:ident, $op:ident) => {
61        impl<R: Round, const B: Word> $Op<CBig<R, B>> for CachedCBig<R, B> {
62            type Output = CachedCBig<R, B>;
63            #[inline]
64            fn $op(self, rhs: CBig<R, B>) -> Self::Output {
65                CachedCBig::from_cbig($Op::$op(self.cbig, rhs), &self.cache)
66            }
67        }
68        impl<'l, R: Round, const B: Word> $Op<CBig<R, B>> for &'l CachedCBig<R, B> {
69            type Output = CachedCBig<R, B>;
70            #[inline]
71            fn $op(self, rhs: CBig<R, B>) -> Self::Output {
72                CachedCBig::from_cbig($Op::$op(self.cbig.clone(), rhs), &self.cache)
73            }
74        }
75        impl<'r, R: Round, const B: Word> $Op<&'r CBig<R, B>> for CachedCBig<R, B> {
76            type Output = CachedCBig<R, B>;
77            #[inline]
78            fn $op(self, rhs: &CBig<R, B>) -> Self::Output {
79                CachedCBig::from_cbig($Op::$op(self.cbig, rhs.clone()), &self.cache)
80            }
81        }
82        impl<'l, 'r, R: Round, const B: Word> $Op<&'r CBig<R, B>> for &'l CachedCBig<R, B> {
83            type Output = CachedCBig<R, B>;
84            #[inline]
85            fn $op(self, rhs: &CBig<R, B>) -> Self::Output {
86                CachedCBig::from_cbig($Op::$op(self.cbig.clone(), rhs.clone()), &self.cache)
87            }
88        }
89    };
90}
91
92macro_rules! impl_cached_binop_reverse_with_cbig {
93    ($Op:ident, $op:ident) => {
94        impl<R: Round, const B: Word> $Op<CachedCBig<R, B>> for CBig<R, B> {
95            type Output = CachedCBig<R, B>;
96            #[inline]
97            fn $op(self, rhs: CachedCBig<R, B>) -> Self::Output {
98                CachedCBig::from_cbig($Op::$op(self, rhs.cbig), &rhs.cache)
99            }
100        }
101        impl<'l, R: Round, const B: Word> $Op<CachedCBig<R, B>> for &'l CBig<R, B> {
102            type Output = CachedCBig<R, B>;
103            #[inline]
104            fn $op(self, rhs: CachedCBig<R, B>) -> Self::Output {
105                CachedCBig::from_cbig($Op::$op(self.clone(), rhs.cbig), &rhs.cache)
106            }
107        }
108        impl<'r, R: Round, const B: Word> $Op<&'r CachedCBig<R, B>> for CBig<R, B> {
109            type Output = CachedCBig<R, B>;
110            #[inline]
111            fn $op(self, rhs: &CachedCBig<R, B>) -> Self::Output {
112                CachedCBig::from_cbig($Op::$op(self, rhs.cbig.clone()), &rhs.cache)
113            }
114        }
115        impl<'l, 'r, R: Round, const B: Word> $Op<&'r CachedCBig<R, B>> for &'l CBig<R, B> {
116            type Output = CachedCBig<R, B>;
117            #[inline]
118            fn $op(self, rhs: &CachedCBig<R, B>) -> Self::Output {
119                CachedCBig::from_cbig($Op::$op(self.clone(), rhs.cbig.clone()), &rhs.cache)
120            }
121        }
122    };
123}
124
125// Assign: CachedCBig op= CBig
126macro_rules! impl_cached_binop_assign_with_cbig {
127    ($OpAssign:ident, $op_assign:ident, $Op:ident, $op:ident) => {
128        impl<R: Round, const B: Word> $OpAssign<CBig<R, B>> for CachedCBig<R, B> {
129            #[inline]
130            fn $op_assign(&mut self, rhs: CBig<R, B>) {
131                self.cbig = $Op::$op(self.cbig.clone(), rhs);
132            }
133        }
134        impl<R: Round, const B: Word> $OpAssign<&CBig<R, B>> for CachedCBig<R, B> {
135            #[inline]
136            fn $op_assign(&mut self, rhs: &CBig<R, B>) {
137                self.cbig = $Op::$op(self.cbig.clone(), rhs.clone());
138            }
139        }
140    };
141}
142
143macro_rules! impl_cached_binop_with_cbig {
144    ($Op:ident, $op:ident, $OpAssign:ident, $op_assign:ident) => {
145        impl_cached_binop_one_way_with_cbig!($Op, $op);
146        impl_cached_binop_reverse_with_cbig!($Op, $op);
147        impl_cached_binop_assign_with_cbig!($OpAssign, $op_assign, $Op, $op);
148    };
149}
150
151impl_cached_binop_with_cbig!(Add, add, AddAssign, add_assign);
152impl_cached_binop_with_cbig!(Sub, sub, SubAssign, sub_assign);
153impl_cached_binop_with_cbig!(Mul, mul, MulAssign, mul_assign);
154impl_cached_binop_with_cbig!(Div, div, DivAssign, div_assign);
155
156// ===========================================================================
157// CachedCBig op FBig and FBig op CachedCBig (scalar Mul/Div only — matches CBig's surface)
158// ===========================================================================
159
160macro_rules! impl_cached_binop_one_way_with_fbig {
161    ($Op:ident, $op:ident) => {
162        impl<R: Round, const B: Word> $Op<FBig<R, B>> for CachedCBig<R, B> {
163            type Output = CachedCBig<R, B>;
164            #[inline]
165            fn $op(self, rhs: FBig<R, B>) -> Self::Output {
166                CachedCBig::from_cbig($Op::$op(self.cbig, rhs), &self.cache)
167            }
168        }
169        impl<'l, R: Round, const B: Word> $Op<FBig<R, B>> for &'l CachedCBig<R, B> {
170            type Output = CachedCBig<R, B>;
171            #[inline]
172            fn $op(self, rhs: FBig<R, B>) -> Self::Output {
173                CachedCBig::from_cbig($Op::$op(self.cbig.clone(), rhs), &self.cache)
174            }
175        }
176        impl<'r, R: Round, const B: Word> $Op<&'r FBig<R, B>> for CachedCBig<R, B> {
177            type Output = CachedCBig<R, B>;
178            #[inline]
179            fn $op(self, rhs: &FBig<R, B>) -> Self::Output {
180                CachedCBig::from_cbig($Op::$op(self.cbig, rhs.clone()), &self.cache)
181            }
182        }
183        impl<'l, 'r, R: Round, const B: Word> $Op<&'r FBig<R, B>> for &'l CachedCBig<R, B> {
184            type Output = CachedCBig<R, B>;
185            #[inline]
186            fn $op(self, rhs: &FBig<R, B>) -> Self::Output {
187                CachedCBig::from_cbig($Op::$op(self.cbig.clone(), rhs.clone()), &self.cache)
188            }
189        }
190    };
191}
192
193macro_rules! impl_cached_binop_reverse_with_fbig {
194    ($Op:ident, $op:ident) => {
195        impl<R: Round, const B: Word> $Op<CachedCBig<R, B>> for FBig<R, B> {
196            type Output = CachedCBig<R, B>;
197            #[inline]
198            fn $op(self, rhs: CachedCBig<R, B>) -> Self::Output {
199                CachedCBig::from_cbig($Op::$op(self, rhs.cbig), &rhs.cache)
200            }
201        }
202        impl<'l, R: Round, const B: Word> $Op<CachedCBig<R, B>> for &'l FBig<R, B> {
203            type Output = CachedCBig<R, B>;
204            #[inline]
205            fn $op(self, rhs: CachedCBig<R, B>) -> Self::Output {
206                CachedCBig::from_cbig($Op::$op(self.clone(), rhs.cbig), &rhs.cache)
207            }
208        }
209        impl<'r, R: Round, const B: Word> $Op<&'r CachedCBig<R, B>> for FBig<R, B> {
210            type Output = CachedCBig<R, B>;
211            #[inline]
212            fn $op(self, rhs: &CachedCBig<R, B>) -> Self::Output {
213                CachedCBig::from_cbig($Op::$op(self, rhs.cbig.clone()), &rhs.cache)
214            }
215        }
216        impl<'l, 'r, R: Round, const B: Word> $Op<&'r CachedCBig<R, B>> for &'l FBig<R, B> {
217            type Output = CachedCBig<R, B>;
218            #[inline]
219            fn $op(self, rhs: &CachedCBig<R, B>) -> Self::Output {
220                CachedCBig::from_cbig($Op::$op(self.clone(), rhs.cbig.clone()), &rhs.cache)
221            }
222        }
223    };
224}
225
226impl_cached_binop_one_way_with_fbig!(Mul, mul);
227impl_cached_binop_reverse_with_fbig!(Mul, mul);
228impl_cached_binop_one_way_with_fbig!(Div, div);
229impl_cached_binop_reverse_with_fbig!(Div, div);
230
231// ===========================================================================
232// Unary operators
233// ===========================================================================
234
235impl<R: Round, const B: Word> Neg for CachedCBig<R, B> {
236    type Output = CachedCBig<R, B>;
237    #[inline]
238    fn neg(self) -> Self::Output {
239        CachedCBig::from_cbig(-self.cbig, &self.cache)
240    }
241}
242
243impl<R: Round, const B: Word> Neg for &CachedCBig<R, B> {
244    type Output = CachedCBig<R, B>;
245    #[inline]
246    fn neg(self) -> Self::Output {
247        CachedCBig::from_cbig(-&self.cbig, &self.cache)
248    }
249}
250
251impl<R: Round, const B: Word> Inverse for CachedCBig<R, B> {
252    type Output = CachedCBig<R, B>;
253    #[inline]
254    fn inv(self) -> Self::Output {
255        CachedCBig::from_cbig(Inverse::inv(self.cbig), &self.cache)
256    }
257}
258
259impl<R: Round, const B: Word> Inverse for &CachedCBig<R, B> {
260    type Output = CachedCBig<R, B>;
261    #[inline]
262    fn inv(self) -> Self::Output {
263        CachedCBig::from_cbig(Inverse::inv(&self.cbig), &self.cache)
264    }
265}
266
267// ===========================================================================
268// Sum / Product
269//
270// The value computation delegates to CBig's impls (fold with +/*). The result keeps the first
271// element's cache handle (empty iterator → Default).
272// ===========================================================================
273
274impl<R: Round, const B: Word> Sum for CachedCBig<R, B> {
275    fn sum<I: Iterator<Item = Self>>(mut iter: I) -> Self {
276        match iter.next() {
277            Some(first) => {
278                let cache = Rc::clone(&first.cache);
279                let cbig: CBig<R, B> = core::iter::once(first.cbig)
280                    .chain(iter.map(|c| c.cbig))
281                    .sum();
282                CachedCBig::from_cbig(cbig, &cache)
283            }
284            None => Self::default(),
285        }
286    }
287}
288
289impl<'a, R: Round, const B: Word> Sum<&'a CachedCBig<R, B>> for CachedCBig<R, B> {
290    fn sum<I: Iterator<Item = &'a CachedCBig<R, B>>>(mut iter: I) -> Self {
291        match iter.next() {
292            Some(first) => {
293                let cache = Rc::clone(&first.cache);
294                let cbig: CBig<R, B> = core::iter::once(first.cbig.clone())
295                    .chain(iter.map(|c| c.cbig.clone()))
296                    .sum();
297                CachedCBig::from_cbig(cbig, &cache)
298            }
299            None => Self::default(),
300        }
301    }
302}
303
304impl<R: Round, const B: Word> Product for CachedCBig<R, B> {
305    fn product<I: Iterator<Item = Self>>(mut iter: I) -> Self {
306        match iter.next() {
307            Some(first) => {
308                let cache = Rc::clone(&first.cache);
309                let cbig: CBig<R, B> = core::iter::once(first.cbig)
310                    .chain(iter.map(|c| c.cbig))
311                    .product();
312                CachedCBig::from_cbig(cbig, &cache)
313            }
314            None => Self::default(),
315        }
316    }
317}
318
319impl<'a, R: Round, const B: Word> Product<&'a CachedCBig<R, B>> for CachedCBig<R, B> {
320    fn product<I: Iterator<Item = &'a CachedCBig<R, B>>>(mut iter: I) -> Self {
321        match iter.next() {
322            Some(first) => {
323                let cache = Rc::clone(&first.cache);
324                let cbig: CBig<R, B> = core::iter::once(first.cbig.clone())
325                    .chain(iter.map(|c| c.cbig.clone()))
326                    .product();
327                CachedCBig::from_cbig(cbig, &cache)
328            }
329            None => Self::default(),
330        }
331    }
332}
333
334// ===========================================================================
335// Math functions — macros are defined at module scope and invoked inside the impl block below
336// (macro_rules! cannot be defined inside an `impl`).
337// ===========================================================================
338
339/// Forward a unary transcendental to the complex [`Context`](crate::repr::Context) method, threading
340/// this value's shared cache and panicking on error (mirrors the convenience-layer unwrap).
341macro_rules! forward_cached {
342    ($name:ident => $ctx:ident) => {
343        #[doc = concat!("See [`CBig::", stringify!($name), "`].")]
344        #[inline]
345        pub fn $name(&self) -> CachedCBig<R, B> {
346            let ctx = self.cbig.context();
347            let cbig = {
348                let mut c = self.cache.borrow_mut();
349                ctx.unwrap_cfp(ctx.$ctx::<B>(&self.cbig, Some(&mut *c)))
350            };
351            CachedCBig::from_cbig(cbig, &self.cache)
352        }
353    };
354}
355
356/// Delegate a unary method to the inner [`CBig`] (no cache involved — just preserve the handle).
357macro_rules! delegate_to_cbig {
358    ($name:ident) => {
359        #[doc = concat!("See [`CBig::", stringify!($name), "`].")]
360        #[inline]
361        pub fn $name(&self) -> CachedCBig<R, B> {
362            CachedCBig::from_cbig(self.cbig.$name(), &self.cache)
363        }
364    };
365    ($name:ident($arg:ident: $arg_ty:ty)) => {
366        #[doc = concat!("See [`CBig::", stringify!($name), "`].")]
367        #[inline]
368        pub fn $name(&self, $arg: $arg_ty) -> CachedCBig<R, B> {
369            CachedCBig::from_cbig(self.cbig.$name($arg), &self.cache)
370        }
371    };
372}
373
374impl<R: Round, const B: Word> CachedCBig<R, B> {
375    // cache-threading transcendentals
376    forward_cached!(ln => log);
377    forward_cached!(exp => exp);
378    forward_cached!(sin => sin);
379    forward_cached!(cos => cos);
380    forward_cached!(tan => tan);
381    forward_cached!(asin => asin);
382    forward_cached!(acos => acos);
383    forward_cached!(atan => atan);
384
385    // non-cache delegations
386    delegate_to_cbig!(sqr);
387    delegate_to_cbig!(sqrt);
388    delegate_to_cbig!(conj);
389    delegate_to_cbig!(proj);
390    delegate_to_cbig!(mul_i(negative: bool));
391    delegate_to_cbig!(powi(exp: IBig));
392
393    /// The squared modulus `re² + im²` (a real [`FBig`]) (see [`CBig::norm`]).
394    #[inline]
395    pub fn norm(&self) -> FBig<R, B> {
396        self.cbig.norm()
397    }
398
399    /// The modulus `|z|` (a real [`FBig`]) (see [`CBig::abs`]).
400    #[inline]
401    pub fn abs(&self) -> FBig<R, B> {
402        self.cbig.abs()
403    }
404
405    /// The argument `atan2(im, re)` (a real [`FBig`]); threads the cache into `atan2`
406    /// (see [`CBig::arg`]).
407    #[inline]
408    pub fn arg(&self) -> FBig<R, B> {
409        let ctx = self.cbig.context();
410        let mut c = self.cache.borrow_mut();
411        ctx.float()
412            .unwrap_fp(ctx.arg::<B>(&self.cbig, Some(&mut *c)))
413    }
414
415    /// Complex power `self^w` (see [`CBig::powf`]). Threads the cache into the inner `log`/`exp`.
416    #[inline]
417    pub fn powf(&self, w: &Self) -> Self {
418        let ctx = self.cbig.context();
419        let cbig = {
420            let mut c = self.cache.borrow_mut();
421            ctx.unwrap_cfp(ctx.powf::<B>(&self.cbig, &w.cbig, Some(&mut *c)))
422        };
423        CachedCBig::from_cbig(cbig, &self.cache)
424    }
425
426    /// Sine and cosine together (see [`CBig::sin_cos`]). Threads the cache into the real
427    /// `sin_cos`/`sinh_cosh`.
428    #[inline]
429    pub fn sin_cos(&self) -> (Self, Self) {
430        let ctx = self.cbig.context();
431        let (s, c) = {
432            let mut guard = self.cache.borrow_mut();
433            ctx.sin_cos::<B>(&self.cbig, Some(&mut *guard))
434        };
435        (
436            CachedCBig::from_cbig(ctx.unwrap_cfp(s), &self.cache),
437            CachedCBig::from_cbig(ctx.unwrap_cfp(c), &self.cache),
438        )
439    }
440}
441
442#[cfg(test)]
443mod tests {
444    use super::*;
445    use alloc::vec::Vec;
446    use core::cell::RefCell;
447    use dashu_float::round::mode;
448    use dashu_float::ConstCache;
449
450    type F = FBig<mode::HalfAway, 10>;
451    type C = CBig<mode::HalfAway, 10>;
452    type CC = CachedCBig<mode::HalfAway, 10>;
453
454    fn c(re: i32, im: i32) -> C {
455        let mk = |v: i32| -> F { F::from(v).with_precision(53).value() };
456        CBig::from_parts(mk(re), mk(im))
457    }
458    fn cc(re: i32, im: i32) -> CC {
459        CachedCBig::from(c(re, im))
460    }
461    /// Assert a CachedCBig equals the matching CBig by value.
462    fn eq(ccval: CC, expected: &C) {
463        assert_eq!(ccval.as_cbig(), expected);
464    }
465
466    #[test]
467    fn arithmetic_matches_cbig() {
468        let (a, b) = (c(2, 1), c(3, 4));
469        let (ca, cb) = (cc(2, 1), cc(3, 4));
470        eq(ca.clone() + cb.clone(), &(&a + &b));
471        eq(ca.clone() - cb.clone(), &(&a - &b));
472        eq(ca.clone() * cb.clone(), &(&a * &b));
473        eq(ca.clone() / cb.clone(), &(&a / &b));
474
475        // Cross-type with CBig (both directions): ca == a by value, so ca + a == a + a.
476        eq(ca.clone() + a.clone(), &(&a + &a));
477        eq(a.clone() + ca.clone(), &(&a + &a));
478
479        // Scalar Mul/Div with FBig.
480        let s = F::from(2);
481        eq(ca.clone() * s.clone(), &(&a * &s));
482        eq(s.clone() * ca.clone(), &(&s * &a));
483        eq(ca.clone() / s.clone(), &(&a / &s));
484    }
485
486    #[test]
487    fn neg_inverse_matches_cbig() {
488        let (a, ca) = (c(2, 1), cc(2, 1));
489        eq(-ca.clone(), &(-&a));
490        eq(Inverse::inv(ca.clone()), &Inverse::inv(&a));
491    }
492
493    #[test]
494    fn sum_product_matches_cbig() {
495        let vals = [(1, 2), (3, 4), (-1, 1)];
496        let cvals: Vec<C> = vals.iter().map(|&(r, i)| c(r, i)).collect();
497        let ccvals: Vec<CC> = vals.iter().map(|&(r, i)| cc(r, i)).collect();
498
499        eq(ccvals.iter().sum::<CC>(), &cvals.iter().sum::<C>());
500        eq(ccvals.clone().into_iter().sum::<CC>(), &cvals.clone().into_iter().sum::<C>());
501        eq(ccvals.iter().product::<CC>(), &cvals.iter().product::<C>());
502        eq(
503            ccvals.clone().into_iter().product::<CC>(),
504            &cvals.clone().into_iter().product::<C>(),
505        );
506        // Empty iterator → Default (0+0i).
507        eq(core::iter::empty::<CC>().sum::<CC>(), &C::default());
508    }
509
510    #[test]
511    fn transcendentals_match_cbig() {
512        let (a, ca) = (c(2, 1), cc(2, 1));
513        // cache-threading transcendentals
514        eq(ca.clone().ln(), &a.clone().ln());
515        eq(ca.clone().exp(), &a.clone().exp());
516        eq(ca.clone().sin(), &a.clone().sin());
517        eq(ca.clone().cos(), &a.clone().cos());
518        eq(ca.clone().tan(), &a.clone().tan());
519        eq(ca.clone().asin(), &a.clone().asin());
520        eq(ca.clone().acos(), &a.clone().acos());
521        eq(ca.clone().atan(), &a.clone().atan());
522        eq(ca.clone().powf(&ca.clone()), &a.clone().powf(&a));
523
524        let (s_c, c_c) = ca.clone().sin_cos();
525        let (s, c) = a.clone().sin_cos();
526        eq(s_c, &s);
527        eq(c_c, &c);
528
529        // non-cache delegations
530        eq(ca.clone().sqr(), &a.clone().sqr());
531        eq(ca.clone().sqrt(), &a.clone().sqrt());
532        eq(ca.clone().conj(), &a.clone().conj());
533        eq(ca.clone().proj(), &a.clone().proj());
534        eq(ca.clone().powi(3.into()), &a.clone().powi(3.into()));
535
536        // real-valued decompositions (return FBig, match CBig exactly)
537        assert_eq!(ca.norm(), a.norm());
538        assert_eq!(ca.abs(), a.abs());
539        assert_eq!(ca.arg(), a.arg());
540    }
541
542    #[test]
543    fn cache_populated_and_survives_arithmetic() {
544        // Transcendentals populate the shared cache (the whole point of the wrapper).
545        let z = cc(2, 1);
546        assert_eq!(z.cache().total_terms(), 0);
547        let _ = z.clone().ln();
548        assert!(z.cache().total_terms() > 0);
549        assert!(z.cache().total_words() > 0);
550
551        // a and b share one handle; the sum keeps it so the subsequent ln() reuses it,
552        // and the value still matches the CBig computation.
553        let mk = |v: i32| -> F { F::from(v).with_precision(53).value() };
554        let h = Rc::new(RefCell::new(ConstCache::new()));
555        let a = CC::new(C::from_parts(mk(2), mk(0)), h.clone());
556        let b = CC::new(C::from_parts(mk(3), mk(0)), h.clone());
557        let sum_ln = (a.clone() + b.clone()).ln().into_cbig();
558        let expected = (C::from_parts(mk(2), F::ZERO) + C::from_parts(mk(3), F::ZERO)).ln();
559        assert_eq!(sum_ln, expected);
560        assert!(h.borrow().total_terms() > 0);
561    }
562}