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}