Skip to main content

dashu_float/
add.rs

1use crate::{
2    error::{assert_finite_operands, FpError, FpResult},
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
17/// Build a `Repr` from a cancellation result, producing `-0` (instead of `+0`) when the
18/// significand is zero and the rounding mode is roundTowardNegative (IEEE 754 §6.3).
19pub(crate) fn cancel_zero<R: Round, const B: Word>(sig: IBig, exp: isize) -> Repr<B> {
20    if sig.is_zero() && R::IS_ROUND_TOWARD_NEGATIVE {
21        Repr::neg_zero()
22    } else {
23        Repr::new(sig, exp)
24    }
25}
26
27impl<R: Round, const B: Word> Add for FBig<R, B> {
28    type Output = Self;
29
30    #[inline]
31    fn add(self, rhs: Self) -> Self::Output {
32        let context = Context::max(self.context, rhs.context);
33        let sum = context.addsub_vv(self.repr, rhs.repr, Positive);
34        FBig::new(sum.value(), context)
35    }
36}
37
38impl<R: Round, const B: Word> Add<&FBig<R, B>> for FBig<R, B> {
39    type Output = Self;
40
41    #[inline]
42    fn add(self, rhs: &FBig<R, B>) -> Self::Output {
43        let context = Context::max(self.context, rhs.context);
44        let sum = context.addsub_vr(self.repr, &rhs.repr, Positive);
45        FBig::new(sum.value(), context)
46    }
47}
48
49impl<R: Round, const B: Word> Add<FBig<R, B>> for &FBig<R, B> {
50    type Output = FBig<R, B>;
51
52    #[inline]
53    fn add(self, rhs: FBig<R, B>) -> Self::Output {
54        let context = Context::max(self.context, rhs.context);
55        let sum = context.addsub_rv(&self.repr, rhs.repr, Positive);
56        FBig::new(sum.value(), context)
57    }
58}
59
60impl<R: Round, const B: Word> Add<&FBig<R, B>> for &FBig<R, B> {
61    type Output = FBig<R, B>;
62
63    #[inline]
64    fn add(self, rhs: &FBig<R, B>) -> Self::Output {
65        let context = Context::max(self.context, rhs.context);
66        let sum = context.addsub_rr(&self.repr, &rhs.repr, Positive);
67        FBig::new(sum.value(), context)
68    }
69}
70
71impl<R: Round, const B: Word> Sub for FBig<R, B> {
72    type Output = Self;
73
74    #[inline]
75    fn sub(self, rhs: Self) -> Self::Output {
76        let context = Context::max(self.context, rhs.context);
77        let sum = context.addsub_vv(self.repr, rhs.repr, Negative);
78        FBig::new(sum.value(), context)
79    }
80}
81
82impl<R: Round, const B: Word> Sub<&FBig<R, B>> for FBig<R, B> {
83    type Output = Self;
84
85    #[inline]
86    fn sub(self, rhs: &FBig<R, B>) -> Self::Output {
87        let context = Context::max(self.context, rhs.context);
88        let sum = context.addsub_vr(self.repr, &rhs.repr, Negative);
89        FBig::new(sum.value(), context)
90    }
91}
92
93impl<R: Round, const B: Word> Sub<FBig<R, B>> for &FBig<R, B> {
94    type Output = FBig<R, B>;
95
96    #[inline]
97    fn sub(self, rhs: FBig<R, B>) -> Self::Output {
98        let context = Context::max(self.context, rhs.context);
99        let sum = context.addsub_rv(&self.repr, rhs.repr, Negative);
100        FBig::new(sum.value(), context)
101    }
102}
103
104impl<R: Round, const B: Word> Sub<&FBig<R, B>> for &FBig<R, B> {
105    type Output = FBig<R, B>;
106
107    #[inline]
108    fn sub(self, rhs: &FBig<R, B>) -> Self::Output {
109        let context = Context::max(self.context, rhs.context);
110        let sum = context.addsub_rr(&self.repr, &rhs.repr, Negative);
111        FBig::new(sum.value(), context)
112    }
113}
114
115helper_macros::impl_binop_assign_by_taking!(impl AddAssign<Self>, add_assign, add);
116helper_macros::impl_binop_assign_by_taking!(impl SubAssign<Self>, sub_assign, sub);
117
118macro_rules! impl_add_sub_primitive_with_fbig {
119    ($($t:ty)*) => {$(
120        helper_macros::impl_binop_with_primitive!(impl Add<$t>, add);
121        helper_macros::impl_binop_assign_with_primitive!(impl AddAssign<$t>, add_assign);
122        helper_macros::impl_binop_with_primitive!(impl Sub<$t>, sub);
123        helper_macros::impl_binop_assign_with_primitive!(impl SubAssign<$t>, sub_assign);
124    )*};
125}
126impl_add_sub_primitive_with_fbig!(u8 u16 u32 u64 u128 usize UBig i8 i16 i32 i64 i128 isize IBig);
127
128impl<R: Round> Context<R> {
129    /// Round sum = `significand * B ^ exponent` with the low part (value, precision).
130    /// If the sum is actually from a subtraction and the low part is not zero, `is_sub` should be true.
131    pub(crate) fn repr_round_sum<const B: Word>(
132        &self,
133        mut significand: IBig,
134        mut exponent: isize,
135        mut low: (IBig, usize),
136        is_sub: bool,
137    ) -> Rounded<Repr<B>> {
138        // A zero produced by exact cancellation is -0 only under roundTowardNegative (Down),
139        // +0 otherwise (IEEE 754 §6.3).
140        let neg_cancel = is_sub && R::IS_ROUND_TOWARD_NEGATIVE;
141        let make_repr = |sig: IBig, exp: isize| -> Repr<B> {
142            if sig.is_zero() && neg_cancel {
143                Repr::neg_zero()
144            } else {
145                Repr::new(sig, exp)
146            }
147        };
148
149        if !self.is_limited() {
150            // short cut for unlimited precision
151            return Rounded::Exact(make_repr(significand, exponent));
152        }
153
154        // use one extra digit to prevent cancellation in rounding
155        let rnd_precision = self.precision + is_sub as usize;
156
157        // align to precision again
158        let digits = digit_len::<B>(&significand);
159        match digits.cmp(&rnd_precision) {
160            Ordering::Equal => {}
161            Ordering::Greater => {
162                // Shrink if the result has more digits than desired precision
163                /*
164                 * lhs:         |=========0000|
165                 * rhs:              |========|xxxxx|
166                 * sum:        |==============|xxxxx|
167                 * precision:  |<----->|
168                 * shrink:     |=======|xxxxxxxxxxxx|
169                 */
170                let shift = digits - rnd_precision;
171                let (signif_hi, mut signif_lo) = split_digits::<B>(significand, shift);
172                significand = signif_hi;
173                exponent += shift as isize;
174                shl_digits_in_place::<B>(&mut signif_lo, low.1);
175                low.0 += signif_lo;
176                low.1 += shift;
177            }
178            Ordering::Less => {
179                // Expand to low parts if the result has less digits than desired precision.
180                /*
181                 * A possible case when lhs and rhs have different sign:
182                 * lhs:  |=========0000|
183                 * rhs:  |=============|xxxxx|
184                 * sum:          |=====|xxxxx|
185                 * precision+1:  |<------>|
186                 * shift:              |<>|
187                 * expanded:     |========|xx|
188                 */
189                if !low.0.is_zero() {
190                    let (low_val, low_prec) = low;
191                    let shift = low_prec.min(rnd_precision - digits);
192                    let (pad, low_val) = split_digits::<B>(low_val, low_prec - shift);
193                    shl_digits_in_place::<B>(&mut significand, shift);
194                    exponent -= shift as isize;
195                    significand += pad;
196                    low = (low_val, low_prec - shift);
197                }
198            }
199        };
200
201        // perform rounding
202        if low.0.is_zero() {
203            Rounded::Exact(make_repr(significand, exponent))
204        } else {
205            // By now significand should have at least full precision. After adjustment, the digits length
206            // could be one more than the precision. We don't shrink the extra digit.
207            let adjust = R::round_fract::<B>(&significand, low.0, low.1);
208            Rounded::Inexact(make_repr(significand + adjust, exponent), adjust)
209        }
210    }
211
212    // lhs + rhs_sign * rhs, assuming lhs.exponent >= rhs.exponent
213    pub(crate) fn repr_add_large_small<const B: Word>(
214        &self,
215        mut lhs: Repr<B>,
216        rhs: &Repr<B>,
217        rhs_sign: Sign,
218    ) -> Rounded<Repr<B>> {
219        debug_assert!(lhs.exponent >= rhs.exponent);
220
221        // use one extra digit when subtracting to prevent cancellation in rounding
222        let is_sub = lhs.significand.sign() != rhs_sign * rhs.significand.sign();
223        let rnd_precision = self.precision + is_sub as usize;
224
225        let ediff = (lhs.exponent - rhs.exponent) as usize;
226        let ldigits = lhs.digits();
227        let rdigits_est = rhs.digits_ub(); // overestimate
228
229        // align the exponent
230        let low: (IBig, usize); // (value of low part, precision of the low part)
231        let (significand, exponent) =
232            if self.is_limited() && is_sub && rdigits_est + self.precision >= ldigits + ediff {
233                // The smaller operand (`rhs`, lower exponent) reaches the larger
234                // operand's `precision`-digit window — its top digit is at or above the
235                // window edge (`rdigits + precision >= ldigits + ediff`, i.e. `rhs`'s top
236                // position `rdigits - ediff` is `>= ldigits - precision`). An effective
237                // subtraction can then cancel and lose leading digits, which the trimmed
238                // path cannot recover (its single re-expand in `repr_round_sum` collapses
239                // a genuinely small difference to the wrong value — e.g. `1.00 -
240                // 0.99999999` at precision 3 to `0` instead of `1e-8`, or `0.5 - 0.4375`
241                // at precision 1 to `0` instead of `0.0625`). So form the exact difference
242                // at full operand width and let the shared `repr_round_sum` round it once
243                // (with the same guard digit as the trimmed path, so no low tail is
244                // needed). The complement (`<`) is the trimmed/negligible region where
245                // `rhs` stays strictly below the window and no cancellation is possible.
246                shl_digits_in_place::<B>(&mut lhs.significand, ediff);
247                low = (IBig::ZERO, 0);
248                match rhs_sign {
249                    Positive => (lhs.significand + &rhs.significand, rhs.exponent),
250                    Negative => (lhs.significand - &rhs.significand, rhs.exponent),
251                }
252            } else if self.is_limited()
253                && rdigits_est + 1 < ediff
254                && rdigits_est + 1 + rnd_precision < ldigits + ediff
255            {
256                // rhs is entirely below lhs's rounding window, so only its sign
257                // contributes to the rounding; replace it with a unit sticky tail
258                // (`|low| = 1`).
259                //
260                // The sticky must be positioned at rhs's *real* magnitude, i.e. `ediff`
261                // digits below lhs's exponent — NOT at `precision - ldigits`. Positioning
262                // by `ediff` keeps the sticky genuinely sub-ULP (|1| << B^ediff, and the
263                // branch guard guarantees ediff >= 3), so it can never land on a rounding
264                // tie. Positioning by `precision - ldigits` instead let the re-expand drag
265                // the sticky up to the LSB, where for base 2 + round-to-nearest it equals
266                // exactly half (1 == B^0 == ½·B^1) and injected a spurious ULP — e.g.
267                // `1 + 2^-100` at precision 10 returned `513·2^-9` instead of `1`.
268                low = (rhs_sign * rhs.significand.signum(), ediff);
269                (lhs.significand, lhs.exponent)
270            } else if self.is_limited() && ldigits >= self.precision {
271                // if the lhs already exceeds the desired precision, just align rhs
272                /* Before:
273                 * lhs: |==============|
274                 * rhs:      |==============|
275                 *              ediff  |<-->|
276                 *    precision  |<--->|
277                 *
278                 * After:
279                 * lhs: |==============|
280                 * rhs:      |=========|xxxx|
281                 */
282                let (rhs_signif, r) = split_digits_ref::<B>(&rhs.significand, ediff);
283                low = (rhs_sign * r, ediff);
284                (lhs.significand + rhs_sign * rhs_signif, lhs.exponent)
285            } else if self.is_limited() && ediff + ldigits > self.precision {
286                // if the shifted lhs exceeds the desired precision, align lhs and rhs to precision
287                /* Before:
288                 * lhs: |=========|
289                 * rhs:      |==============|
290                 *                |< ediff >|
291                 *      |< precision >|
292                 *
293                 * After:
294                 * lhs: |=========0000|
295                 * rhs:      |========|xxxxx|
296                 *        lshift  |<->|
297                 *            rshift  |<--->|
298                 */
299                let lshift = self.precision - ldigits;
300                let rshift = ediff - lshift;
301                let (rhs_signif, r) = split_digits_ref::<B>(&rhs.significand, rshift);
302                shl_digits_in_place::<B>(&mut lhs.significand, lshift);
303
304                low = (rhs_sign * r, rshift);
305                (lhs.significand + rhs_sign * rhs_signif, lhs.exponent - lshift as isize)
306            } else {
307                // otherwise directly shift lhs to required position
308                /* Before:
309                 * lhs: |==========|
310                 * rhs:       |==============|
311                 *                 |< ediff >|
312                 *      |<------ precision ------>|
313                 *
314                 * After:
315                 * lhs: |==========0000000000|
316                 * rhs:       |==============|
317                 */
318                shl_digits_in_place::<B>(&mut lhs.significand, ediff);
319                low = (IBig::ZERO, 0);
320                match rhs_sign {
321                    Positive => (lhs.significand + &rhs.significand, rhs.exponent),
322                    Negative => (lhs.significand - &rhs.significand, rhs.exponent),
323                }
324            };
325
326        self.repr_round_sum(significand, exponent, low, is_sub)
327    }
328
329    // lhs + rhs_sign * rhs, assuming lhs.exponent <= rhs.exponent
330    pub(crate) fn repr_add_small_large<const B: Word>(
331        &self,
332        lhs: Repr<B>,
333        rhs: &Repr<B>,
334        rhs_sign: Sign,
335    ) -> Rounded<Repr<B>> {
336        debug_assert!(lhs.exponent <= rhs.exponent);
337
338        // the following implementation should be exactly the same as `repr_add_large_small`
339        // other than lhs and rhs are swapped. See `repr_add_large_small` for full documentation
340        let is_sub = lhs.significand.sign() != rhs_sign * rhs.significand.sign();
341        let rnd_precision = self.precision + is_sub as usize;
342
343        let ediff = (rhs.exponent - lhs.exponent) as usize;
344        let rdigits = rhs.digits();
345        let ldigits_est = lhs.digits_ub();
346
347        // align the exponent
348        let low: (IBig, usize);
349        let (significand, exponent) =
350            if self.is_limited() && is_sub && ldigits_est + self.precision >= rdigits + ediff {
351                // Symmetric counterpart of the guard in `repr_add_large_small` (see there
352                // for the rationale); here the lower-exponent operand is `lhs`. Form the
353                // exact difference at full operand width and let the shared
354                // `repr_round_sum` round it once.
355                let rhs_signif = shl_digits::<B>(&rhs.significand, ediff);
356                low = (IBig::ZERO, 0);
357                (rhs_sign * rhs_signif + lhs.significand, lhs.exponent)
358            } else if self.is_limited()
359                && ldigits_est + 1 < ediff
360                && ldigits_est + 1 + rnd_precision < rdigits + ediff
361            {
362                // lhs is entirely below rhs's rounding window, so only its sign
363                // contributes; replace it with a unit sticky tail positioned at lhs's
364                // real magnitude (`ediff` digits below rhs's exponent). See
365                // `repr_add_large_small` for why the position must be `ediff` and not
366                // `precision - rdigits`.
367                low = (lhs.significand.signum(), ediff);
368                (rhs_sign * rhs.significand.clone(), rhs.exponent)
369            } else if self.is_limited() && rdigits >= self.precision {
370                // if the rhs already exceeds the desired precision, just align lhs
371                let (lhs_signif, r) = split_digits::<B>(lhs.significand, ediff);
372                low = (r, ediff);
373                match rhs_sign {
374                    Positive => (lhs_signif + &rhs.significand, rhs.exponent),
375                    Negative => (lhs_signif - &rhs.significand, rhs.exponent),
376                }
377            } else if self.is_limited() && ediff + rdigits > self.precision {
378                // if the shifted rhs exceeds the desired precision, align lhs and rhs to precision
379                let lshift = self.precision - rdigits;
380                let rshift = ediff - lshift;
381                let (lhs_signif, r) = split_digits::<B>(lhs.significand, rshift);
382                let rhs_signif = shl_digits::<B>(&rhs.significand, lshift);
383
384                low = (r, rshift);
385                (rhs_sign * rhs_signif + lhs_signif, rhs.exponent - lshift as isize)
386            } else {
387                // otherwise directly shift rhs to required position
388                let rhs_signif = shl_digits::<B>(&rhs.significand, ediff);
389                low = (IBig::ZERO, 0);
390                (rhs_sign * rhs_signif + lhs.significand, lhs.exponent)
391            };
392
393        self.repr_round_sum(significand, exponent, low, is_sub)
394    }
395
396    /// Add or subtract two finite floats, consuming both operands.
397    ///
398    /// Computes `lhs + rhs_sign · rhs`: [`Sign::Positive`] adds, [`Sign::Negative`]
399    /// subtracts. This is the low-level ownership-aware kernel shared by the
400    /// `+`/`-` operators and [`add`](Self::add)/[`sub`](Self::sub); unlike those it
401    /// returns the raw rounded [`Repr`] (no [`FBig`] wrapping, no [`Result`]) and
402    /// reuses the owned significand buffer of `lhs`. The `_vr`/`_rv`/`_rr`
403    /// siblings cover the other ownership combinations and share this contract.
404    ///
405    /// # Panics
406    ///
407    /// Panics if either operand is infinite (matching the other `repr_*` kernels).
408    ///
409    /// # Examples
410    ///
411    /// ```
412    /// use dashu_base::Sign::*;
413    /// use dashu_float::{Context, Repr, round::mode::HalfEven};
414    /// use dashu_int::IBig;
415    ///
416    /// let ctx = Context::<HalfEven>::new(4);
417    /// let a = Repr::<10>::new(IBig::from(1234), -3); // 1.234
418    /// let b = Repr::<10>::new(IBig::from(5678), -4); // 0.5678
419    /// // 1.234 + 0.5678 = 1.8018, rounds (HalfEven, prec 4) to 1.802
420    /// assert_eq!(ctx.addsub_vv(a.clone(), b.clone(), Positive).value(),
421    ///            Repr::<10>::new(IBig::from(1802), -3));
422    /// // 1.234 - 0.5678 = 0.6662 (exact at prec 4)
423    /// assert_eq!(ctx.addsub_vv(a, b, Negative).value(),
424    ///            Repr::<10>::new(IBig::from(6662), -4));
425    /// ```
426    pub fn addsub_vv<const B: Word>(
427        &self,
428        lhs: Repr<B>,
429        rhs: Repr<B>,
430        rhs_sign: Sign,
431    ) -> Rounded<Repr<B>> {
432        assert_finite_operands(&lhs, &rhs);
433        if lhs.is_pos_zero() {
434            // With rhs_sign = Negative, round `-rhs` directly rather than negating
435            // *after* rounding. For the asymmetric modes (Up = toward +∞, Down =
436            // toward −∞), `round(-x) != -round(x)`: rounding `rhs` toward +∞ then
437            // negating rounds in the wrong direction, so `0 - rhs` would land one
438            // ULP off (truncated instead of rounded away from the result). This
439            // applies to every `addsub_*` variant's lhs-zero path.
440            self.repr_round(match rhs_sign {
441                Positive => rhs,
442                Negative => Repr::new(-rhs.significand, rhs.exponent),
443            })
444        } else if rhs.is_pos_zero() {
445            self.repr_round(lhs)
446        } else {
447            match lhs.exponent.cmp(&rhs.exponent) {
448                Ordering::Equal => {
449                    let sig = match rhs_sign {
450                        Positive => lhs.significand + &rhs.significand,
451                        Negative => lhs.significand - &rhs.significand,
452                    };
453                    self.repr_round(cancel_zero::<R, B>(sig, lhs.exponent))
454                }
455                Ordering::Greater => self.repr_add_large_small(lhs, &rhs, rhs_sign),
456                Ordering::Less => self.repr_add_small_large(lhs, &rhs, rhs_sign),
457            }
458        }
459    }
460
461    /// Like [`addsub_vv`](Self::addsub_vv) but with the right operand borrowed
462    /// (the left operand is consumed). See `addsub_vv` for the full contract.
463    pub fn addsub_vr<const B: Word>(
464        &self,
465        lhs: Repr<B>,
466        rhs: &Repr<B>,
467        rhs_sign: Sign,
468    ) -> Rounded<Repr<B>> {
469        assert_finite_operands(&lhs, rhs);
470        if lhs.is_pos_zero() {
471            self.repr_round(match rhs_sign {
472                Positive => rhs.clone(),
473                Negative => Repr::new(-&rhs.significand, rhs.exponent),
474            })
475        } else if rhs.is_pos_zero() {
476            self.repr_round(lhs)
477        } else {
478            match lhs.exponent.cmp(&rhs.exponent) {
479                Ordering::Equal => {
480                    let sig = match rhs_sign {
481                        Positive => lhs.significand + &rhs.significand,
482                        Negative => lhs.significand - &rhs.significand,
483                    };
484                    self.repr_round(cancel_zero::<R, B>(sig, lhs.exponent))
485                }
486                Ordering::Greater => self.repr_add_large_small(lhs, rhs, rhs_sign),
487                Ordering::Less => self.repr_add_small_large(lhs, rhs, rhs_sign),
488            }
489        }
490    }
491
492    /// Like [`addsub_vv`](Self::addsub_vv) but with the left operand borrowed and
493    /// the right operand consumed. See `addsub_vv` for the full contract.
494    pub fn addsub_rv<const B: Word>(
495        &self,
496        lhs: &Repr<B>,
497        mut rhs: Repr<B>,
498        rhs_sign: Sign,
499    ) -> Rounded<Repr<B>> {
500        assert_finite_operands(lhs, &rhs);
501        // Bake the sign into the owned rhs so the kernel — which takes its first
502        // operand by value — can move `rhs` into that slot with `Positive`. This
503        // is the mirror of `addsub_vv`'s owned-lhs path; the value is identical.
504        rhs.significand *= rhs_sign;
505        if lhs.is_pos_zero() {
506            self.repr_round(rhs)
507        } else if rhs.is_pos_zero() {
508            self.repr_round_ref(lhs)
509        } else {
510            match lhs.exponent.cmp(&rhs.exponent) {
511                Ordering::Equal => self.repr_round(cancel_zero::<R, B>(
512                    &lhs.significand + rhs.significand,
513                    lhs.exponent,
514                )),
515                Ordering::Greater => self.repr_add_small_large(rhs, lhs, Positive),
516                Ordering::Less => self.repr_add_large_small(rhs, lhs, Positive),
517            }
518        }
519    }
520
521    /// Like [`addsub_vv`](Self::addsub_vv) but with both operands borrowed (the
522    /// larger-exponent operand is cloned once for in-place alignment). See
523    /// `addsub_vv` for the full contract.
524    pub fn addsub_rr<const B: Word>(
525        &self,
526        lhs: &Repr<B>,
527        rhs: &Repr<B>,
528        rhs_sign: Sign,
529    ) -> Rounded<Repr<B>> {
530        assert_finite_operands(lhs, rhs);
531        if lhs.is_pos_zero() {
532            match rhs_sign {
533                Positive => self.repr_round_ref(rhs),
534                Negative => self.repr_round_ref(&Repr::new(-&rhs.significand, rhs.exponent)),
535            }
536        } else if rhs.is_pos_zero() {
537            self.repr_round_ref(lhs)
538        } else {
539            match lhs.exponent.cmp(&rhs.exponent) {
540                Ordering::Equal => {
541                    let sig = match rhs_sign {
542                        Positive => &lhs.significand + &rhs.significand,
543                        Negative => &lhs.significand - &rhs.significand,
544                    };
545                    self.repr_round(cancel_zero::<R, B>(sig, lhs.exponent))
546                }
547                Ordering::Greater => self.repr_add_large_small(lhs.clone(), rhs, rhs_sign),
548                Ordering::Less => self.repr_add_small_large(lhs.clone(), rhs, rhs_sign),
549            }
550        }
551    }
552
553    /// Add two floating point numbers under this context.
554    ///
555    /// # Examples
556    ///
557    /// ```
558    /// # use core::str::FromStr;
559    /// # use dashu_base::ParseError;
560    /// # use dashu_float::DBig;
561    /// use dashu_base::Approximation::*;
562    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
563    ///
564    /// let context = Context::<HalfAway>::new(2);
565    /// let a = DBig::from_str("1.234")?;
566    /// let b = DBig::from_str("6.789")?;
567    /// assert_eq!(context.add(&a.repr(), &b.repr()), Ok(Inexact(DBig::from_str("8.0")?, NoOp)));
568    /// # Ok::<(), ParseError>(())
569    /// ```
570    #[inline]
571    pub fn add<const B: Word>(&self, lhs: &Repr<B>, rhs: &Repr<B>) -> FpResult<FBig<R, B>> {
572        if lhs.is_infinite() || rhs.is_infinite() {
573            return Err(FpError::InfiniteInput);
574        }
575        Ok(self
576            .addsub_rr(lhs, rhs, Positive)
577            .map(|v| FBig::new(v, *self)))
578    }
579
580    /// Subtract two floating point numbers under this context.
581    ///
582    /// # Examples
583    ///
584    /// ```
585    /// # use core::str::FromStr;
586    /// # use dashu_base::ParseError;
587    /// # use dashu_float::DBig;
588    /// use dashu_base::Approximation::*;
589    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
590    ///
591    /// let context = Context::<HalfAway>::new(2);
592    /// let a = DBig::from_str("1.234")?;
593    /// let b = DBig::from_str("6.789")?;
594    /// assert_eq!(
595    ///     context.sub(&a.repr(), &b.repr()),
596    ///     Ok(Inexact(DBig::from_str("-5.6")?, SubOne))
597    /// );
598    /// # Ok::<(), ParseError>(())
599    /// ```
600    #[inline]
601    pub fn sub<const B: Word>(&self, lhs: &Repr<B>, rhs: &Repr<B>) -> FpResult<FBig<R, B>> {
602        if lhs.is_infinite() || rhs.is_infinite() {
603            return Err(FpError::InfiniteInput);
604        }
605        Ok(self
606            .addsub_rr(lhs, rhs, Negative)
607            .map(|v| FBig::new(v, *self)))
608    }
609}
610
611#[cfg(test)]
612mod tests {
613    use super::*;
614    use crate::round::mode::{HalfAway, HalfEven};
615
616    // Build a normalized Repr from a small integer significand and an exponent.
617    fn r<const B: Word>(sig: i128, exp: isize) -> Repr<B> {
618        Repr::new(IBig::from(sig), exp)
619    }
620
621    // Severe cancellation must not collapse a genuinely small difference to 0.
622    // Pristine returned `0` for the first two rows: the trimmed alignment path
623    // keeps only a bounded low tail and its single re-expand can't recover the
624    // lost leading digits.
625    #[test]
626    fn sub_severe_cancellation_decimal() {
627        let ctx = Context::<HalfAway>::new(3);
628        // 1.00 - 0.99999999 = 1e-8 (exactly representable at precision 3)
629        assert_eq!(
630            ctx.sub(&r::<10>(100, -2), &r::<10>(99999999, -8))
631                .unwrap()
632                .value()
633                .repr(),
634            &r::<10>(1, -8)
635        );
636        // 1.00 - 0.99950001 = 4.9999e-4, rounds to 5.00e-4 (HalfAway)
637        assert_eq!(
638            ctx.sub(&r::<10>(100, -2), &r::<10>(99950001, -8))
639                .unwrap()
640                .value()
641                .repr(),
642            &r::<10>(500, -6)
643        );
644    }
645
646    #[test]
647    fn sub_severe_cancellation_binary() {
648        let ctx = Context::<HalfEven>::new(10);
649        // 2^20 - (2^20 - 1) = 1, with the operands 20 exponent positions apart
650        assert_eq!(
651            ctx.sub(&r::<2>(1, 20), &r::<2>((1i128 << 20) - 1, 0))
652                .unwrap()
653                .value()
654                .repr(),
655            &r::<2>(1, 0)
656        );
657        // same magnitude gap but the smaller-exponent operand is on the left
658        assert_eq!(
659            ctx.sub(&r::<2>((1i128 << 20) - 1, 0), &r::<2>(1, 20))
660                .unwrap()
661                .value()
662                .repr(),
663            &r::<2>(-1, 0)
664        );
665        // 2^30 - (2^30 - 1) = 1
666        assert_eq!(
667            ctx.sub(&r::<2>(1, 30), &r::<2>((1i128 << 30) - 1, 0))
668                .unwrap()
669                .value()
670                .repr(),
671            &r::<2>(1, 0)
672        );
673    }
674
675    // Effective subtraction reached through `Context::add` (opposite signs) must
676    // be fixed as well.
677    #[test]
678    fn add_effective_severe_cancellation() {
679        let ctx = Context::<HalfEven>::new(10);
680        // 2^20 + (-(2^20 - 1)) = 1
681        assert_eq!(
682            ctx.add(&r::<2>(1, 20), &r::<2>(-((1i128 << 20) - 1), 0))
683                .unwrap()
684                .value()
685                .repr(),
686            &r::<2>(1, 0)
687        );
688    }
689
690    // The public operator path (`a - b`) routes through the same kernel.
691    #[test]
692    fn sub_operator_severe_cancellation() {
693        let a = FBig::<HalfEven, 2>::from_parts(IBig::from(1), 20);
694        let b = FBig::<HalfEven, 2>::from_parts(IBig::from((1i128 << 20) - 1), 0);
695        assert_eq!((a - b).repr(), &r::<2>(1, 0));
696    }
697
698    // Mild subtractions — the smaller operand stays below the larger's precision
699    // window — must keep their existing behavior and not be diverted to the
700    // full-width path.
701    #[test]
702    fn sub_mild_unchanged() {
703        let ctx = Context::<HalfAway>::new(3);
704        // 101 - 0.2 = 100.8, kept as 1008 * 10^-1 (one guard digit, as before)
705        assert_eq!(
706            ctx.sub(&r::<10>(101, 0), &r::<10>(2, -1))
707                .unwrap()
708                .value()
709                .repr(),
710            &r::<10>(1008, -1)
711        );
712    }
713
714    // Regression for the branch-1 signum-proxy bug (SUM-BUG.md §2c): when the larger
715    // operand has fewer digits than the precision and a negligible operand is added,
716    // the sticky proxy must be positioned at the operand's *real* magnitude (`ediff`),
717    // not at `precision - ldigits`. The old positioning let the re-expand drag the
718    // sticky up to the LSB, where for base 2 + round-to-nearest it equals exactly half
719    // and injected a spurious ULP: `1 + 2^-100` at precision 10 gave `513*2^-9` (=
720    // 1.00195…) instead of `1`.
721    #[test]
722    fn add_negligible_short_operand_no_spurious_ulp() {
723        // base 2 + HalfAway: the exact tie case
724        let ctx = Context::<HalfAway>::new(10);
725        assert_eq!(
726            ctx.add(&r::<2>(1, 0), &r::<2>(1, -100))
727                .unwrap()
728                .value()
729                .repr(),
730            &r::<2>(1, 0)
731        );
732        assert_eq!(
733            ctx.sub(&r::<2>(1, 0), &r::<2>(1, -100))
734                .unwrap()
735                .value()
736                .repr(),
737            &r::<2>(1, 0)
738        );
739        // larger short operand (digits < precision), negligible addend
740        let ctx = Context::<HalfAway>::new(50);
741        assert_eq!(
742            ctx.add(&r::<2>(0x12345, 0), &r::<2>(1, -200))
743                .unwrap()
744                .value()
745                .repr(),
746            &r::<2>(0x12345, 0)
747        );
748        // base 10 was never affected (1 < ½·10), but check it stays correct
749        let ctx = Context::<HalfAway>::new(10);
750        assert_eq!(
751            ctx.add(&r::<10>(1, 0), &r::<10>(1, -100))
752                .unwrap()
753                .value()
754                .repr(),
755            &r::<10>(1, 0)
756        );
757    }
758
759    // The four ownership-aware `addsub_*` variants must produce the same value as
760    // each other (and therefore as `Context::add`/`sub`, which delegate to
761    // `addsub_rr`) across every input shape. The absolute correctness of
762    // `addsub_rr` is covered by the `ctx.add`/`ctx.sub` tests above; this guards
763    // that consuming vs borrowing an operand never changes the result.
764    fn assert_addsub_variants_agree<R: Round, const B: Word>(
765        ctx: &Context<R>,
766        a: &Repr<B>,
767        b: &Repr<B>,
768    ) {
769        let add = ctx.addsub_rr(a, b, Positive).value();
770        let sub = ctx.addsub_rr(a, b, Negative).value();
771        assert_eq!(&ctx.addsub_vv(a.clone(), b.clone(), Positive).value(), &add);
772        assert_eq!(&ctx.addsub_vr(a.clone(), b, Positive).value(), &add);
773        assert_eq!(&ctx.addsub_rv(a, b.clone(), Positive).value(), &add);
774        assert_eq!(&ctx.addsub_vv(a.clone(), b.clone(), Negative).value(), &sub);
775        assert_eq!(&ctx.addsub_vr(a.clone(), b, Negative).value(), &sub);
776        assert_eq!(&ctx.addsub_rv(a, b.clone(), Negative).value(), &sub);
777    }
778
779    #[test]
780    fn addsub_variants_agree() {
781        let ctx = Context::<HalfAway>::new(3);
782        // lhs has the larger exponent
783        assert_addsub_variants_agree(&ctx, &r::<10>(1234, -2), &r::<10>(567, -5));
784        // rhs has the larger exponent
785        assert_addsub_variants_agree(&ctx, &r::<10>(567, -5), &r::<10>(1234, -2));
786        // equal exponents
787        assert_addsub_variants_agree(&ctx, &r::<10>(123, -2), &r::<10>(456, -2));
788        // lhs is +0 (exercises the lhs-zero short-circuit in every variant)
789        assert_addsub_variants_agree(&ctx, &r::<10>(0, 0), &r::<10>(123, -2));
790        // rhs is +0
791        assert_addsub_variants_agree(&ctx, &r::<10>(123, -2), &r::<10>(0, 0));
792        // a negative operand (effective subtraction reached through `add`)
793        assert_addsub_variants_agree(&ctx, &r::<10>(-123, -2), &r::<10>(456, -2));
794    }
795
796    // Severe cancellation must recover through every ownership path — especially
797    // `addsub_rv`, which bakes the sign into the owned rhs and routes it as the
798    // kernel's by-value (first) operand, a structurally different path from
799    // vv/vr/rr.
800    #[test]
801    fn addsub_severe_cancellation_all_variants() {
802        let ctx = Context::<HalfEven>::new(10);
803        let big = r::<2>(1, 20); // 2^20
804        let near = r::<2>((1i128 << 20) - 1, 0); // 2^20 - 1
805                                                 // 2^20 - (2^20 - 1) = 1
806        assert_eq!(&ctx.addsub_vv(big.clone(), near.clone(), Negative).value(), &r::<2>(1, 0));
807        assert_eq!(&ctx.addsub_vr(big.clone(), &near, Negative).value(), &r::<2>(1, 0));
808        assert_eq!(&ctx.addsub_rr(&big, &near, Negative).value(), &r::<2>(1, 0));
809        // reversed: (2^20 - 1) - 2^20 = -1, through both rhs-owned and lhs-owned paths
810        assert_eq!(&ctx.addsub_rv(&near, big.clone(), Negative).value(), &r::<2>(-1, 0));
811        assert_eq!(&ctx.addsub_vv(near, big, Negative).value(), &r::<2>(-1, 0));
812    }
813}