dashu_float/
add.rs

1use crate::{
2    error::assert_finite_operands,
3    fbig::FBig,
4    helper_macros,
5    repr::{Context, Repr, Word},
6    round::{Round, Rounded},
7    utils::{digit_len, shl_digits, shl_digits_in_place, split_digits, split_digits_ref},
8};
9use core::{
10    cmp::Ordering,
11    ops::{Add, AddAssign, Sub, SubAssign},
12};
13
14use dashu_base::Sign::{self, *};
15use dashu_int::{IBig, UBig};
16
17impl<R: Round, const B: Word> Add for FBig<R, B> {
18    type Output = Self;
19
20    #[inline]
21    fn add(self, rhs: Self) -> Self::Output {
22        add_val_val(self, rhs, Positive)
23    }
24}
25
26impl<'r, R: Round, const B: Word> Add<&'r FBig<R, B>> for FBig<R, B> {
27    type Output = Self;
28
29    #[inline]
30    fn add(self, rhs: &FBig<R, B>) -> Self::Output {
31        add_val_ref(self, rhs, Positive)
32    }
33}
34
35impl<'l, R: Round, const B: Word> Add<FBig<R, B>> for &'l FBig<R, B> {
36    type Output = FBig<R, B>;
37
38    #[inline]
39    fn add(self, rhs: FBig<R, B>) -> Self::Output {
40        add_ref_val(self, rhs, Positive)
41    }
42}
43
44impl<'l, 'r, R: Round, const B: Word> Add<&'r FBig<R, B>> for &'l FBig<R, B> {
45    type Output = FBig<R, B>;
46
47    #[inline]
48    fn add(self, rhs: &FBig<R, B>) -> Self::Output {
49        add_ref_ref(self, rhs, Positive)
50    }
51}
52
53impl<R: Round, const B: Word> Sub for FBig<R, B> {
54    type Output = Self;
55
56    #[inline]
57    fn sub(self, rhs: Self) -> Self::Output {
58        add_val_val(self, rhs, Negative)
59    }
60}
61
62impl<'r, R: Round, const B: Word> Sub<&'r FBig<R, B>> for FBig<R, B> {
63    type Output = Self;
64
65    #[inline]
66    fn sub(self, rhs: &FBig<R, B>) -> Self::Output {
67        add_val_ref(self, rhs, Negative)
68    }
69}
70
71impl<'l, R: Round, const B: Word> Sub<FBig<R, B>> for &'l FBig<R, B> {
72    type Output = FBig<R, B>;
73
74    #[inline]
75    fn sub(self, rhs: FBig<R, B>) -> Self::Output {
76        add_ref_val(self, rhs, Negative)
77    }
78}
79
80impl<'l, 'r, R: Round, const B: Word> Sub<&'r FBig<R, B>> for &'l FBig<R, B> {
81    type Output = FBig<R, B>;
82
83    #[inline]
84    fn sub(self, rhs: &FBig<R, B>) -> Self::Output {
85        add_ref_ref(self, rhs, Negative)
86    }
87}
88
89helper_macros::impl_binop_assign_by_taking!(impl AddAssign<Self>, add_assign, add);
90helper_macros::impl_binop_assign_by_taking!(impl SubAssign<Self>, sub_assign, sub);
91
92macro_rules! impl_add_sub_primitive_with_fbig {
93    ($($t:ty)*) => {$(
94        helper_macros::impl_binop_with_primitive!(impl Add<$t>, add);
95        helper_macros::impl_binop_assign_with_primitive!(impl AddAssign<$t>, add_assign);
96        helper_macros::impl_binop_with_primitive!(impl Sub<$t>, sub);
97        helper_macros::impl_binop_assign_with_primitive!(impl SubAssign<$t>, sub_assign);
98    )*};
99}
100impl_add_sub_primitive_with_fbig!(u8 u16 u32 u64 u128 usize UBig i8 i16 i32 i64 i128 isize IBig);
101
102fn add_val_val<R: Round, const B: Word>(
103    lhs: FBig<R, B>,
104    mut rhs: FBig<R, B>,
105    rhs_sign: Sign,
106) -> FBig<R, B> {
107    assert_finite_operands(&lhs.repr, &rhs.repr);
108
109    let context = Context::max(lhs.context, rhs.context);
110    rhs.repr.significand *= rhs_sign;
111    let sum = if lhs.repr.is_zero() {
112        rhs.repr
113    } else if rhs.repr.is_zero() {
114        lhs.repr
115    } else {
116        match lhs.repr.exponent.cmp(&rhs.repr.exponent) {
117            Ordering::Equal => context.repr_round(Repr::new(
118                lhs.repr.significand + rhs.repr.significand,
119                lhs.repr.exponent,
120            )),
121            Ordering::Greater => context.repr_add_large_small(lhs.repr, &rhs.repr, Positive),
122            Ordering::Less => context.repr_add_small_large(lhs.repr, &rhs.repr, Positive),
123        }
124        .value()
125    };
126    FBig::new(sum, context)
127}
128
129fn add_val_ref<R: Round, const B: Word>(
130    lhs: FBig<R, B>,
131    rhs: &FBig<R, B>,
132    rhs_sign: Sign,
133) -> FBig<R, B> {
134    assert_finite_operands(&lhs.repr, &rhs.repr);
135
136    let context = Context::max(lhs.context, rhs.context);
137    let sum = if lhs.repr.is_zero() {
138        let mut repr = rhs.repr.clone();
139        repr.significand *= rhs_sign;
140        repr
141    } else if rhs.repr.is_zero() {
142        lhs.repr
143    } else {
144        match lhs.repr.exponent.cmp(&rhs.repr.exponent) {
145            Ordering::Equal => {
146                let sum_signif = match rhs_sign {
147                    Positive => lhs.repr.significand + &rhs.repr.significand,
148                    Negative => lhs.repr.significand - &rhs.repr.significand,
149                };
150                context.repr_round(Repr::new(sum_signif, lhs.repr.exponent))
151            }
152            Ordering::Greater => context.repr_add_large_small(lhs.repr, &rhs.repr, rhs_sign),
153            Ordering::Less => context.repr_add_small_large(lhs.repr, &rhs.repr, rhs_sign),
154        }
155        .value()
156    };
157    FBig::new(sum, context)
158}
159
160fn add_ref_val<R: Round, const B: Word>(
161    lhs: &FBig<R, B>,
162    mut rhs: FBig<R, B>,
163    rhs_sign: Sign,
164) -> FBig<R, B> {
165    assert_finite_operands(&lhs.repr, &rhs.repr);
166
167    let context = Context::max(lhs.context, rhs.context);
168    rhs.repr.significand *= rhs_sign;
169    let sum = if lhs.repr.is_zero() {
170        rhs.repr
171    } else if rhs.repr.is_zero() {
172        lhs.repr.clone()
173    } else {
174        match lhs.repr.exponent.cmp(&rhs.repr.exponent) {
175            Ordering::Equal => context.repr_round(Repr::new(
176                &lhs.repr.significand + rhs.repr.significand,
177                lhs.repr.exponent,
178            )),
179            Ordering::Greater => context.repr_add_small_large(rhs.repr, &lhs.repr, Positive),
180            Ordering::Less => context.repr_add_large_small(rhs.repr, &lhs.repr, Positive),
181        }
182        .value()
183    };
184    FBig::new(sum, context)
185}
186
187fn add_ref_ref<R: Round, const B: Word>(
188    lhs: &FBig<R, B>,
189    rhs: &FBig<R, B>,
190    rhs_sign: Sign,
191) -> FBig<R, B> {
192    assert_finite_operands(&lhs.repr, &rhs.repr);
193
194    let context = Context::max(lhs.context, rhs.context);
195    let sum = if lhs.repr.is_zero() {
196        let mut repr = rhs.repr.clone();
197        repr.significand *= rhs_sign;
198        repr
199    } else if rhs.repr.is_zero() {
200        lhs.repr.clone()
201    } else {
202        match lhs.repr.exponent.cmp(&rhs.repr.exponent) {
203            Ordering::Equal => context.repr_round(Repr::new(
204                &lhs.repr.significand + rhs_sign * rhs.repr.significand.clone(),
205                lhs.repr.exponent,
206            )),
207            Ordering::Greater => {
208                context.repr_add_large_small(lhs.repr.clone(), &rhs.repr, rhs_sign)
209            }
210            Ordering::Less => context.repr_add_small_large(lhs.repr.clone(), &rhs.repr, rhs_sign),
211        }
212        .value()
213    };
214    FBig::new(sum, context)
215}
216
217impl<R: Round> Context<R> {
218    /// Round sum = `significand * B ^ exponent` with the low part (value, precision).
219    /// If the sum is actually from a subtraction and the low part is not zero, `is_sub` should be true.
220    fn repr_round_sum<const B: Word>(
221        &self,
222        mut significand: IBig,
223        mut exponent: isize,
224        mut low: (IBig, usize),
225        is_sub: bool,
226    ) -> Rounded<Repr<B>> {
227        if !self.is_limited() {
228            // short cut for unlimited precision
229            return Rounded::Exact(Repr::new(significand, exponent));
230        }
231
232        // use one extra digit to prevent cancellation in rounding
233        let rnd_precision = self.precision + is_sub as usize;
234
235        // align to precision again
236        let digits = digit_len::<B>(&significand);
237        match digits.cmp(&rnd_precision) {
238            Ordering::Equal => {}
239            Ordering::Greater => {
240                // Shrink if the result has more digits than desired precision
241                /*
242                 * lhs:         |=========0000|
243                 * rhs:              |========|xxxxx|
244                 * sum:        |==============|xxxxx|
245                 * precision:  |<----->|
246                 * shrink:     |=======|xxxxxxxxxxxx|
247                 */
248                let shift = digits - rnd_precision;
249                let (signif_hi, mut signif_lo) = split_digits::<B>(significand, shift);
250                significand = signif_hi;
251                exponent += shift as isize;
252                shl_digits_in_place::<B>(&mut signif_lo, low.1);
253                low.0 += signif_lo;
254                low.1 += shift;
255            }
256            Ordering::Less => {
257                // Expand to low parts if the result has less digits than desired precision.
258                /*
259                 * A possible case when lhs and rhs have different sign:
260                 * lhs:  |=========0000|
261                 * rhs:  |=============|xxxxx|
262                 * sum:          |=====|xxxxx|
263                 * precision+1:  |<------>|
264                 * shift:              |<>|
265                 * expanded:     |========|xx|
266                 */
267                if !low.0.is_zero() {
268                    let (low_val, low_prec) = low;
269                    let shift = low_prec.min(rnd_precision - digits);
270                    let (pad, low_val) = split_digits::<B>(low_val, low_prec - shift);
271                    shl_digits_in_place::<B>(&mut significand, shift);
272                    exponent -= shift as isize;
273                    significand += pad;
274                    low = (low_val, low_prec - shift);
275                }
276            }
277        };
278
279        // perform rounding
280        if low.0.is_zero() {
281            Rounded::Exact(Repr::new(significand, exponent))
282        } else {
283            // By now significand should have at least full precision. After adjustment, the digits length
284            // could be one more than the precision. We don't shrink the extra digit.
285            let adjust = R::round_fract::<B>(&significand, low.0, low.1);
286            Rounded::Inexact(Repr::new(significand + adjust, exponent), adjust)
287        }
288    }
289
290    // lhs + rhs_sign * rhs, assuming lhs.exponent >= rhs.exponent
291    fn repr_add_large_small<const B: Word>(
292        &self,
293        mut lhs: Repr<B>,
294        rhs: &Repr<B>,
295        rhs_sign: Sign,
296    ) -> Rounded<Repr<B>> {
297        debug_assert!(lhs.exponent >= rhs.exponent);
298
299        // use one extra digit when subtracting to prevent cancellation in rounding
300        let is_sub = lhs.significand.sign() != rhs_sign * rhs.significand.sign();
301        let rnd_precision = self.precision + is_sub as usize;
302
303        let ediff = (lhs.exponent - rhs.exponent) as usize;
304        let ldigits = lhs.digits();
305        let rdigits_est = rhs.digits_ub(); // overestimate
306
307        // align the exponent
308        let low: (IBig, usize); // (value of low part, precision of the low part)
309        let (significand, exponent) = if self.is_limited()
310            && rdigits_est + 1 < ediff
311            && rdigits_est + 1 + rnd_precision < ldigits + ediff
312        {
313            // if rhs is much smaller than lhs, direct round on the rhs
314            /*
315             * lhs: |=========|
316             * rhs:                  |========|
317             *                |<--- ediff --->|
318             *      |< precision >|
319             */
320
321            // In this case, the actual significand of rhs doesn't matter,
322            // we can just replace it with 1 for correct rounding
323            let low_prec = if ldigits >= rnd_precision {
324                2
325            } else {
326                (rnd_precision - ldigits) + 1
327            }; // low_prec >= 2
328            low = (rhs_sign * rhs.significand.signum(), low_prec);
329            (lhs.significand, lhs.exponent)
330        } else if self.is_limited() && ldigits >= self.precision {
331            // if the lhs already exceeds the desired precision, just align rhs
332            /* Before:
333             * lhs: |==============|
334             * rhs:      |==============|
335             *              ediff  |<-->|
336             *    precision  |<--->|
337             *
338             * After:
339             * lhs: |==============|
340             * rhs:      |=========|xxxx|
341             */
342            let (rhs_signif, r) = split_digits_ref::<B>(&rhs.significand, ediff);
343            low = (rhs_sign * r, ediff);
344            (lhs.significand + rhs_sign * rhs_signif, lhs.exponent)
345        } else if self.is_limited() && ediff + ldigits > self.precision {
346            // if the shifted lhs exceeds the desired precision, align lhs and rhs to precision
347            /* Before:
348             * lhs: |=========|
349             * rhs:      |==============|
350             *                |< ediff >|
351             *      |< precision >|
352             *
353             * After:
354             * lhs: |=========0000|
355             * rhs:      |========|xxxxx|
356             *        lshift  |<->|
357             *            rshift  |<--->|
358             */
359            let lshift = self.precision - ldigits;
360            let rshift = ediff - lshift;
361            let (rhs_signif, r) = split_digits_ref::<B>(&rhs.significand, rshift);
362            shl_digits_in_place::<B>(&mut lhs.significand, lshift);
363
364            low = (rhs_sign * r, rshift);
365            (lhs.significand + rhs_sign * rhs_signif, lhs.exponent - lshift as isize)
366        } else {
367            // otherwise directly shift lhs to required position
368            /* Before:
369             * lhs: |==========|
370             * rhs:       |==============|
371             *                 |< ediff >|
372             *      |<------ precision ------>|
373             *
374             * After:
375             * lhs: |==========0000000000|
376             * rhs:       |==============|
377             */
378            shl_digits_in_place::<B>(&mut lhs.significand, ediff);
379            low = (IBig::ZERO, 0);
380            match rhs_sign {
381                Positive => (lhs.significand + &rhs.significand, rhs.exponent),
382                Negative => (lhs.significand - &rhs.significand, rhs.exponent),
383            }
384        };
385
386        self.repr_round_sum(significand, exponent, low, is_sub)
387    }
388
389    // lhs + rhs_sign * rhs, assuming lhs.exponent <= rhs.exponent
390    fn repr_add_small_large<const B: Word>(
391        &self,
392        lhs: Repr<B>,
393        rhs: &Repr<B>,
394        rhs_sign: Sign,
395    ) -> Rounded<Repr<B>> {
396        debug_assert!(lhs.exponent <= rhs.exponent);
397
398        // the following implementation should be exactly the same as `repr_add_large_small`
399        // other than lhs and rhs are swapped. See `repr_add_large_small` for full documentation
400        let is_sub = lhs.significand.sign() != rhs_sign * rhs.significand.sign();
401        let rnd_precision = self.precision + is_sub as usize;
402
403        let ediff = (rhs.exponent - lhs.exponent) as usize;
404        let rdigits = rhs.digits();
405        let ldigits_est = lhs.digits_ub();
406
407        // align the exponent
408        let low: (IBig, usize);
409        let (significand, exponent) = if self.is_limited()
410            && ldigits_est + 1 < ediff
411            && ldigits_est + 1 + rnd_precision < rdigits + ediff
412        {
413            // if lhs is much smaller than rhs, direct round on the lhs
414            let low_prec = if rdigits >= rnd_precision {
415                2
416            } else {
417                (rnd_precision - rdigits) + 1
418            };
419            low = (lhs.significand.signum(), low_prec);
420            (rhs_sign * rhs.significand.clone(), rhs.exponent)
421        } else if self.is_limited() && rdigits >= self.precision {
422            // if the rhs already exceeds the desired precision, just align lhs
423            let (lhs_signif, r) = split_digits::<B>(lhs.significand, ediff);
424            low = (r, ediff);
425            match rhs_sign {
426                Positive => (lhs_signif + &rhs.significand, rhs.exponent),
427                Negative => (lhs_signif - &rhs.significand, rhs.exponent),
428            }
429        } else if self.is_limited() && ediff + rdigits > self.precision {
430            // if the shifted rhs exceeds the desired precision, align lhs and rhs to precision
431            let lshift = self.precision - rdigits;
432            let rshift = ediff - lshift;
433            let (lhs_signif, r) = split_digits::<B>(lhs.significand, rshift);
434            let rhs_signif = shl_digits::<B>(&rhs.significand, lshift);
435
436            low = (r, rshift);
437            (rhs_sign * rhs_signif + lhs_signif, rhs.exponent - lshift as isize)
438        } else {
439            // otherwise directly shift rhs to required position
440            let rhs_signif = shl_digits::<B>(&rhs.significand, ediff);
441            low = (IBig::ZERO, 0);
442            (rhs_sign * rhs_signif + lhs.significand, lhs.exponent)
443        };
444
445        self.repr_round_sum(significand, exponent, low, is_sub)
446    }
447
448    /// Add two floating point numbers under this context.
449    ///
450    /// # Examples
451    ///
452    /// ```
453    /// # use core::str::FromStr;
454    /// # use dashu_base::ParseError;
455    /// # use dashu_float::DBig;
456    /// use dashu_base::Approximation::*;
457    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
458    ///
459    /// let context = Context::<HalfAway>::new(2);
460    /// let a = DBig::from_str("1.234")?;
461    /// let b = DBig::from_str("6.789")?;
462    /// assert_eq!(context.add(&a.repr(), &b.repr()), Inexact(DBig::from_str("8.0")?, NoOp));
463    /// # Ok::<(), ParseError>(())
464    /// ```
465    pub fn add<const B: Word>(&self, lhs: &Repr<B>, rhs: &Repr<B>) -> Rounded<FBig<R, B>> {
466        assert_finite_operands(lhs, rhs);
467
468        let sum = if lhs.is_zero() {
469            self.repr_round_ref(rhs)
470        } else if rhs.is_zero() {
471            self.repr_round_ref(lhs)
472        } else {
473            match lhs.exponent.cmp(&rhs.exponent) {
474                Ordering::Equal => {
475                    self.repr_round(Repr::new(&lhs.significand + &rhs.significand, lhs.exponent))
476                }
477                Ordering::Greater => self.repr_add_large_small(lhs.clone(), rhs, Positive),
478                Ordering::Less => self.repr_add_small_large(lhs.clone(), rhs, Positive),
479            }
480        };
481        sum.map(|v| FBig::new(v, *self))
482    }
483
484    /// Subtract two floating point numbers under this context.
485    ///
486    /// # Examples
487    ///
488    /// ```
489    /// # use core::str::FromStr;
490    /// # use dashu_base::ParseError;
491    /// # use dashu_float::DBig;
492    /// use dashu_base::Approximation::*;
493    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
494    ///
495    /// let context = Context::<HalfAway>::new(2);
496    /// let a = DBig::from_str("1.234")?;
497    /// let b = DBig::from_str("6.789")?;
498    /// assert_eq!(
499    ///     context.sub(&a.repr(), &b.repr()),
500    ///     Inexact(DBig::from_str("-5.6")?, SubOne)
501    /// );
502    /// # Ok::<(), ParseError>(())
503    /// ```
504    pub fn sub<const B: Word>(&self, lhs: &Repr<B>, rhs: &Repr<B>) -> Rounded<FBig<R, B>> {
505        assert_finite_operands(lhs, rhs);
506
507        let sum = if lhs.is_zero() {
508            self.repr_round_ref(rhs).map(|v| -v)
509        } else if rhs.is_zero() {
510            self.repr_round_ref(lhs)
511        } else {
512            match lhs.exponent.cmp(&rhs.exponent) {
513                Ordering::Equal => {
514                    self.repr_round(Repr::new(&lhs.significand - &rhs.significand, lhs.exponent))
515                }
516                Ordering::Greater => self.repr_add_large_small(lhs.clone(), rhs, Negative),
517                Ordering::Less => self.repr_add_small_large(lhs.clone(), rhs, Negative),
518            }
519        };
520        sum.map(|v| FBig::new(v, *self))
521    }
522}