Skip to main content

dashu_float/
repr_ops.rs

1//! Core trait impls for [`Repr`]: [`PartialEq`]/[`Eq`] (value equality — `+0`/`-0` and same-sign
2//! infinities compare equal), [`Neg`], and exact [`Add`]/[`Sub`]/[`Mul`].
3//!
4//! A [`Repr`] carries no precision limit, so the arithmetic ops are lossless (no rounding). These
5//! are the shared primitives the crate reaches for whenever it needs an exact intermediate — the
6//! Ziv containment test, the correctly-rounded `Sum`, and the `FBig` multiply path.
7//!
8//! Each arithmetic operator's logic lives in the `&Repr`-by-`&Repr` primary impl; the val/ref
9//! forwarders delegate to it without cloning (a `Repr`'s significand is heap-backed, so the ref/ref
10//! form is the no-extra-allocation path). [`Mul`] saturates exponent overflow/underflow to the
11//! signed infinity/zero sentinels so the operator is infallible; the precision-limited
12//! [`Context`](crate::Context) multiply re-derives the [`FpError`] it needs from that saturated
13//! result.
14
15use core::cmp::Ordering;
16use core::ops::{Add, Mul, Neg, Sub};
17
18use dashu_base::Sign;
19
20use crate::error::FpError;
21use crate::repr::{Repr, Word};
22use crate::utils::shl_digits;
23
24impl<const B: Word> PartialEq for Repr<B> {
25    /// Two representations are equal when they denote the same value. In particular `+0`
26    /// and `-0` compare equal, as do two infinities of the same sign.
27    #[inline]
28    fn eq(&self, other: &Self) -> bool {
29        if self.significand.is_zero() && other.significand.is_zero() {
30            let (self_inf, other_inf) = (self.is_infinite(), other.is_infinite());
31            match (self_inf, other_inf) {
32                (true, true) => self.sign() == other.sign(),
33                (false, false) => true, // both are ±0
34                _ => false,             // one is zero, the other is infinite
35            }
36        } else {
37            self.significand == other.significand && self.exponent == other.exponent
38        }
39    }
40}
41
42impl<const B: Word> Eq for Repr<B> {}
43
44impl<const B: Word> Neg for Repr<B> {
45    type Output = Self;
46    #[inline]
47    fn neg(self) -> Self::Output {
48        Repr::neg(self)
49    }
50}
51
52impl<const B: Word> Add<&Repr<B>> for &Repr<B> {
53    type Output = Repr<B>;
54
55    #[inline]
56    fn add(self, rhs: &Repr<B>) -> Repr<B> {
57        debug_assert!(self.is_finite());
58        debug_assert!(rhs.is_finite());
59
60        // Zero operands short-circuit so a `-0` operand's sentinel exponent (-1) can't bleed into
61        // the result exponent — `precise_sum` (the `Sum` impl) relies on this. The aligned path
62        // below would compute the same value but would rebuild via `Repr::new`, normalizing away
63        // the operand's own representation.
64        if self.significand.is_zero() {
65            return rhs.clone();
66        }
67        if rhs.significand.is_zero() {
68            return self.clone();
69        }
70
71        // Result exponent is min(lhs, rhs); shift the larger-exponent significand up (appending
72        // trailing base-`B` zero-digits is lossless) and add. `IBig` add handles opposite signs.
73        match self.exponent.cmp(&rhs.exponent) {
74            Ordering::Equal => Repr::new(&self.significand + &rhs.significand, self.exponent),
75            Ordering::Greater => Repr::new(
76                shl_digits::<B>(&self.significand, (self.exponent - rhs.exponent) as usize)
77                    + &rhs.significand,
78                rhs.exponent,
79            ),
80            Ordering::Less => Repr::new(
81                &self.significand
82                    + shl_digits::<B>(&rhs.significand, (rhs.exponent - self.exponent) as usize),
83                self.exponent,
84            ),
85        }
86    }
87}
88
89impl<const B: Word> Add<&Repr<B>> for Repr<B> {
90    type Output = Repr<B>;
91    #[inline]
92    fn add(self, rhs: &Repr<B>) -> Repr<B> {
93        (&self) + rhs
94    }
95}
96impl<const B: Word> Add<Repr<B>> for &Repr<B> {
97    type Output = Repr<B>;
98    #[inline]
99    fn add(self, rhs: Repr<B>) -> Repr<B> {
100        self + &rhs
101    }
102}
103impl<const B: Word> Add<Repr<B>> for Repr<B> {
104    type Output = Repr<B>;
105    #[inline]
106    fn add(self, rhs: Repr<B>) -> Repr<B> {
107        (&self) + &rhs
108    }
109}
110
111impl<const B: Word> Sub<&Repr<B>> for &Repr<B> {
112    type Output = Repr<B>;
113
114    #[inline]
115    fn sub(self, rhs: &Repr<B>) -> Repr<B> {
116        debug_assert!(self.is_finite());
117        debug_assert!(rhs.is_finite());
118
119        // Zero short-circuits: `x - 0 = x` (keep x), `0 - x = -x` (negate x). As with `Add`, this
120        // keeps a zero operand's own representation rather than rebuilding via `Repr::new`.
121        if rhs.significand.is_zero() {
122            return self.clone();
123        }
124        if self.significand.is_zero() {
125            return rhs.clone().neg();
126        }
127
128        // Mirror `Add`: align to the smaller exponent, then subtract significands.
129        match self.exponent.cmp(&rhs.exponent) {
130            Ordering::Equal => Repr::new(&self.significand - &rhs.significand, self.exponent),
131            Ordering::Greater => Repr::new(
132                shl_digits::<B>(&self.significand, (self.exponent - rhs.exponent) as usize)
133                    - &rhs.significand,
134                rhs.exponent,
135            ),
136            Ordering::Less => Repr::new(
137                &self.significand
138                    - shl_digits::<B>(&rhs.significand, (rhs.exponent - self.exponent) as usize),
139                self.exponent,
140            ),
141        }
142    }
143}
144
145impl<const B: Word> Sub<&Repr<B>> for Repr<B> {
146    type Output = Repr<B>;
147    #[inline]
148    fn sub(self, rhs: &Repr<B>) -> Repr<B> {
149        (&self) - rhs
150    }
151}
152impl<const B: Word> Sub<Repr<B>> for &Repr<B> {
153    type Output = Repr<B>;
154    #[inline]
155    fn sub(self, rhs: Repr<B>) -> Repr<B> {
156        self - &rhs
157    }
158}
159impl<const B: Word> Sub<Repr<B>> for Repr<B> {
160    type Output = Repr<B>;
161    #[inline]
162    fn sub(self, rhs: Repr<B>) -> Repr<B> {
163        (&self) - &rhs
164    }
165}
166
167impl<const B: Word> Mul<&Repr<B>> for &Repr<B> {
168    type Output = Repr<B>;
169
170    #[inline]
171    fn mul(self, rhs: &Repr<B>) -> Repr<B> {
172        debug_assert!(self.is_finite());
173        debug_assert!(rhs.is_finite());
174
175        let significand = &self.significand * &rhs.significand;
176        if significand.is_zero() {
177            // The product significand is `+0`; attach the XOR sign of the operands.
178            return if self.sign() != rhs.sign() {
179                Repr::neg_zero()
180            } else {
181                Repr::zero()
182            };
183        }
184        let sign = if self.sign() != rhs.sign() {
185            Sign::Negative
186        } else {
187            Sign::Positive
188        };
189        // Exponent = lhs + rhs; saturate an `isize` overflow to the signed infinity/zero sentinel
190        // so the operator stays infallible (unreachable for real inputs — it needs operands with
191        // exponents ~±2^62). `Context::mul` re-derives the `FpError` from this saturated result.
192        let exponent = match self.exponent.checked_add(rhs.exponent) {
193            Some(e) => e,
194            None => {
195                debug_assert!(
196                    self.exponent.is_positive() == rhs.exponent.is_positive(),
197                    "checked_add overflow with mixed-sign exponents is impossible"
198                );
199                return if self.exponent > 0 {
200                    Repr::infinity_with_sign(sign)
201                } else {
202                    Repr::zero_with_sign(sign)
203                };
204            }
205        };
206        match Repr::new(significand, exponent).check_finite_exponent() {
207            Ok(r) => r,
208            Err(FpError::Overflow(s)) => Repr::infinity_with_sign(s),
209            Err(FpError::Underflow(s)) => Repr::zero_with_sign(s),
210            Err(_) => unreachable!(),
211        }
212    }
213}
214
215impl<const B: Word> Mul<&Repr<B>> for Repr<B> {
216    type Output = Repr<B>;
217    #[inline]
218    fn mul(self, rhs: &Repr<B>) -> Repr<B> {
219        (&self) * rhs
220    }
221}
222impl<const B: Word> Mul<Repr<B>> for &Repr<B> {
223    type Output = Repr<B>;
224    #[inline]
225    fn mul(self, rhs: Repr<B>) -> Repr<B> {
226        self * &rhs
227    }
228}
229impl<const B: Word> Mul<Repr<B>> for Repr<B> {
230    type Output = Repr<B>;
231    #[inline]
232    fn mul(self, rhs: Repr<B>) -> Repr<B> {
233        (&self) * &rhs
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    use dashu_int::IBig;
241
242    fn r<const B: Word>(sig: i128, exp: isize) -> Repr<B> {
243        Repr::new(IBig::from(sig), exp)
244    }
245
246    #[test]
247    fn add_same_exponent() {
248        assert_eq!(&r::<10>(3, 0) + &r::<10>(4, 0), r::<10>(7, 0));
249    }
250
251    #[test]
252    fn add_aligns_exponents() {
253        // 3 + 0.4 = 3.4
254        assert_eq!(&r::<10>(3, 0) + &r::<10>(4, -1), r::<10>(34, -1));
255        // 1 + 0.00001 = 1.00001 (exact; the small operand's digits are all retained)
256        assert_eq!(&r::<10>(1, 0) + &r::<10>(1, -5), r::<10>(100001, -5));
257    }
258
259    #[test]
260    fn add_neg_zero_is_identity() {
261        let nz = Repr::<10>::neg_zero();
262        let x = r::<10>(5, -2);
263        assert_eq!(&nz + &x, x);
264        assert_eq!(&x + &nz, x);
265        // -0 + -0 = -0: the zero short-circuit returns the other operand unchanged.
266        assert_eq!(&nz + &nz, Repr::<10>::neg_zero());
267    }
268
269    #[test]
270    fn add_cancellation_is_positive_zero() {
271        let z = &r::<10>(1, 0) + &r::<10>(-1, 0);
272        assert_eq!(z, Repr::<10>::zero());
273        assert!(z.is_pos_zero());
274    }
275
276    #[test]
277    fn sub_basic() {
278        assert_eq!(&r::<10>(5, 0) - &r::<10>(3, 0), r::<10>(2, 0));
279        assert_eq!(&r::<10>(3, 0) - &r::<10>(5, 0), r::<10>(-2, 0));
280        // x - 0 = x (subtraction is a + (-0); the zero short-circuit keeps x's representation)
281        assert_eq!(&r::<10>(5, 0) - &Repr::<10>::zero(), r::<10>(5, 0));
282    }
283
284    #[test]
285    fn mul_basic() {
286        assert_eq!(&r::<10>(3, 0) * &r::<10>(4, 0), r::<10>(12, 0));
287        // significands multiply, exponents add: 3e2 * 2e-1 = 6e1
288        assert_eq!(&r::<10>(3, 2) * &r::<10>(2, -1), r::<10>(6, 1));
289        assert_eq!(&r::<2>(3, 0) * &r::<2>(3, 0), r::<2>(9, 0));
290    }
291
292    #[test]
293    fn mul_zero_product_sign() {
294        // (+0) * (+5) = +0
295        assert_eq!(&Repr::<10>::zero() * &r::<10>(5, 0), Repr::<10>::zero());
296        // (+0) * (-5) = -0 (the XOR sign of the operands is attached to the zero product)
297        let prod = &Repr::<10>::zero() * &r::<10>(-5, 0);
298        assert!(prod.is_neg_zero());
299    }
300
301    #[test]
302    fn ref_val_combos() {
303        let a = r::<10>(2, 0);
304        let b = r::<10>(3, 0);
305        assert_eq!(&a + &b, r::<10>(5, 0));
306        assert_eq!(a.clone() + &b, r::<10>(5, 0));
307        assert_eq!(&a + b.clone(), r::<10>(5, 0));
308        assert_eq!(a.clone() + b.clone(), r::<10>(5, 0));
309
310        let c = r::<10>(7, 0);
311        let d = r::<10>(4, 0);
312        assert_eq!(&c - &d, r::<10>(3, 0));
313        assert_eq!(c.clone() - &d, r::<10>(3, 0));
314        assert_eq!(&c - d.clone(), r::<10>(3, 0));
315        assert_eq!(c.clone() - d.clone(), r::<10>(3, 0));
316
317        let e = r::<10>(6, 0);
318        let f = r::<10>(7, 0);
319        assert_eq!(&e * &f, r::<10>(42, 0));
320        assert_eq!(e.clone() * &f, r::<10>(42, 0));
321        assert_eq!(&e * f.clone(), r::<10>(42, 0));
322        assert_eq!(e.clone() * f.clone(), r::<10>(42, 0));
323    }
324}