fast_posit/posit/ops/
add.rs

1use super::*;
2
3impl<
4  const N: u32,
5  const ES: u32,
6  Int: crate::Int,
7> Posit<N, ES, Int> {
8  /// `x` and `y` cannot be symmetrical, or calling this function is *undefined behaviour*.
9  #[inline]
10  pub(crate) unsafe fn add_kernel(x: Decoded<N, ES, Int>, y: Decoded<N, ES, Int>) -> (Decoded<N, ES, Int>, Int) {
11    // Adding two numbers in the form `x.frac × 2^x.exp` and `y.exp × 2^y.exp` is easy, if only
12    // `x.exp` = `y.exp`: then the result would be just `(x.frac + y.exp) × 2^x.exp`. Therefore,
13    // to add two numbers we just have to (1) reduce to the same exponent, and (2) add the
14    // fractions. The remaining complications are to do with detecting over/underflows, and
15    // rounding correctly.
16
17    // First align the exponents. That is: to add `x.frac + 2^x.exp` and `y.frac + 2^y.exp` let
18    // `shift` be the difference between the exponents, add `shift` to the smallest exp, and
19    // divide the corresponding frac by `2^shift` to compensate. For example:
20    //
21    //     0b01_0110 × 2⁰
22    //   + 0b01_1000 × 2³
23    //
24    // becomes
25    //
26    //     0b00_0010110 × 2³
27    //   + 0b01_1000    × 2³
28    //
29    // because the first number has the smallest `exp`, so we add 3 to it and divide its `frac` by
30    // 2³.
31    let shift = x.exp - y.exp;
32    let (x, y) = if shift.is_positive() { (x, y) } else { (y, x) };
33    let shift = shift.abs().as_u32();
34    // One thing to keep in mind is that `shift` can exceed the width of `Int`. If this happens,
35    // then the *entire* contents of `y.frac` are shifted out, and thus the answer is just `x`.
36    if shift >= Int::BITS {  // TODO mark unlikely?
37      return (x, Int::ZERO)
38    };
39    let xfrac = x.frac;
40    let yfrac = y.frac >> shift;
41    let exp = x.exp;
42
43    // Adding two positive or two negative values: an overflow by *1 place* may occur. For example
44    //
45    //     1.25 = 0b01_0100
46    //   + 1.0  = 0b01_0000
47    //   = 2.25 = 0b10_0100
48    //
49    // If this happens, we must detect this, shift the `frac` right by 1 (i.e. divide by 2), and
50    // add 1 to exponent to compensate
51    //
52    //   = 1.125 × 2¹ = 0b01_0010, add +1 to `exp`
53    //
54    // To do this we use `overflowing_add_shift`, which may have a specialised implementation e.g.
55    // using "rotate" instructions; see [crate::underlying].
56    let (frac, overflow) = xfrac.overflowing_add_shift(yfrac);
57    let exp = exp + overflow.into();
58    // If an overflow occurs, then remember to also accumulate the shifted out bit of xfrac and
59    // yfrac into sticky.
60    let sticky_overflow = (xfrac | yfrac) & overflow.into();
61
62    // Adding a positive and a negative value: an underflow by *n places* may occur. For example
63    //
64    //     -1.25 = 0b10_1100
65    //   +  1.0  = 0b01_0000
66    //   = -0.25 = 0b11_1100
67    //
68    // If this happens, we must detect this, shift the `frac` left by `n` (i.e. multiply by 2^n),
69    // and subtract `n` to the exponent to compensate.
70    //
71    //   = -1.00 × 2¯³ = 0b10_0000
72    //
73    // To do this we use our trusty `leading_run_minus_one`, since we want to detect that the
74    // number starts with n 0s followed by a 1 or n 1s followed by a 0, and shift them so that
75    // it's just a 01 or a 10.
76    // SAFETY: x and y are not symmetrical (precondition), so `frac` cannot be 0
77    let underflow = unsafe { frac.leading_run_minus_one() };
78    let frac = frac << underflow as u32;
79    let exp = exp - Int::of_u32(underflow);
80    // If an underflow by `n` occurs, then we need to "recover" `n` of the bits we have shifted out
81    // in `yfrac`, and add them onto the result, because we have set `yfrac = y.frac >> shift`,
82    // but actually should have set `= y.frac >> (shift - underflow)`.
83    //
84    // For example, say `y.frac = 0b11110101`, `shift = 4`, `underflow = 3`. Then
85    //
86    //    y.frac                        = 0b11110101|
87    //    y.frac >> shift               = 0b00001111|0101    ← discarded 4 bits
88    //    y.frac >> (shift - underflow) = 0b01111010|1       ← but should only discard 1
89    //
90    // Here only 1 bit should be shifted out to sticky.
91    let true_shift = shift.checked_sub(underflow).unwrap_or(0);  // TODO ver
92    let recovered = y.frac.mask_lsb(shift) >> true_shift;
93    let sticky = y.frac.mask_lsb(true_shift);
94    let frac = frac | recovered;
95
96    (Decoded{frac, exp}, (sticky | sticky_overflow))
97  }
98
99  pub(crate) fn add(self, other: Self) -> Self {
100    let sum = self.0.wrapping_add(other.0);
101    if self == Self::NAR || other == Self::NAR {
102      Self::NAR
103    } else if sum == Int::ZERO || sum == self.0 || sum == other.0 {
104      Self(sum)
105    } else {
106      // SAFETY: neither `self` nor `other` are 0 or NaR
107      let a = unsafe { self.decode_regular() };
108      let b = unsafe { other.decode_regular() };
109      // SAFETY: `self` and `other` aren't symmetrical
110      let (result, sticky) = unsafe { Self::add_kernel(a, b) };
111      // SAFETY: `result` does not have an underflowing `frac`
112      unsafe { result.encode_regular_round(sticky) }
113    }
114  }
115
116  pub(crate) fn sub(self, other: Self) -> Self {
117    self.add(-other)
118  }
119}
120
121use core::ops::{Add, AddAssign, Sub, SubAssign};
122
123super::mk_ops!{Add, AddAssign, add, add_assign}
124super::mk_ops!{Sub, SubAssign, sub, sub_assign}
125
126#[cfg(test)]
127mod tests {
128  use super::*;
129
130  mod add {
131    use super::*;
132    use malachite::rational::Rational;
133
134    #[allow(dead_code)]
135    fn ops() {
136      let mut a = crate::p32::ONE;
137      let mut b = crate::p32::MINUS_ONE;
138      let _ = a + b;
139      let _ = &a + b;
140      let _ = a + &b;
141      let _ = &a + &b;
142      a += b;
143      b += &a;
144    }
145
146    /// Aux function: check that `a + b` is rounded correctly.
147    fn is_correct_rounded<const N: u32, const ES: u32, Int: crate::Int>(
148      a: Posit<N, ES, Int>,
149      b: Posit<N, ES, Int>,
150    ) -> bool
151    where
152      Rational: TryFrom<Posit<N, ES, Int>>,
153      <Rational as TryFrom<Posit<N, ES, Int>>>::Error: core::fmt::Debug
154    {
155      let sum_posit = a + b;
156      if let (Ok(a), Ok(b)) = (Rational::try_from(a), Rational::try_from(b)) {
157        let sum_exact = a + b;
158        super::rational::is_correct_rounded(sum_exact, sum_posit)
159      } else {
160        sum_posit == Posit::<N, ES, Int>::NAR
161      }
162    }
163
164    #[test]
165    fn posit_10_0_exhaustive() {
166      for a in Posit::<10, 0, i16>::cases_exhaustive_all() {
167        for b in Posit::<10, 0, i16>::cases_exhaustive_all() {
168          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
169        }
170      }
171    }
172
173    #[test]
174    fn posit_10_1_exhaustive() {
175      for a in Posit::<10, 1, i16>::cases_exhaustive_all() {
176        for b in Posit::<10, 1, i16>::cases_exhaustive_all() {
177          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
178        }
179      }
180    }
181
182    #[test]
183    fn posit_10_2_exhaustive() {
184      for a in Posit::<10, 2, i16>::cases_exhaustive_all() {
185        for b in Posit::<10, 2, i16>::cases_exhaustive_all() {
186          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
187        }
188      }
189    }
190
191    #[test]
192    fn posit_10_3_exhaustive() {
193      for a in Posit::<10, 3, i16>::cases_exhaustive_all() {
194        for b in Posit::<10, 3, i16>::cases_exhaustive_all() {
195          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
196        }
197      }
198    }
199
200    #[test]
201    fn posit_8_0_exhaustive() {
202      for a in Posit::<8, 0, i8>::cases_exhaustive_all() {
203        for b in Posit::<8, 0, i8>::cases_exhaustive_all() {
204          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
205        }
206      }
207    }
208
209    #[test]
210    fn p8_exhaustive() {
211      for a in crate::p8::cases_exhaustive_all() {
212        for b in crate::p8::cases_exhaustive_all() {
213          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
214        }
215      }
216    }
217
218    use proptest::prelude::*;
219    const PROPTEST_CASES: u32 = if cfg!(debug_assertions) {0x1_0000} else {0x80_0000};
220    proptest!{
221      #![proptest_config(ProptestConfig::with_cases(PROPTEST_CASES))]
222
223      #[test]
224      fn p16_proptest(
225        a in crate::p16::cases_proptest(),
226        b in crate::p16::cases_proptest(),
227      ) {
228        assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
229      }
230
231      #[test]
232      fn p32_proptest(
233        a in crate::p32::cases_proptest(),
234        b in crate::p32::cases_proptest(),
235      ) {
236        assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
237      }
238
239      #[test]
240      fn p64_proptest(
241        a in crate::p64::cases_proptest(),
242        b in crate::p64::cases_proptest(),
243      ) {
244        assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
245      }
246    }
247
248    #[test]
249    fn posit_3_0_exhaustive() {
250      for a in Posit::<3, 0, i8>::cases_exhaustive_all() {
251        for b in Posit::<3, 0, i8>::cases_exhaustive_all() {
252          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
253        }
254      }
255    }
256    #[test]
257    fn posit_4_0_exhaustive() {
258      for a in Posit::<4, 0, i8>::cases_exhaustive_all() {
259        for b in Posit::<4, 0, i8>::cases_exhaustive_all() {
260          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
261        }
262      }
263    }
264    #[test]
265    fn posit_4_1_exhaustive() {
266      for a in Posit::<4, 1, i8>::cases_exhaustive_all() {
267        for b in Posit::<4, 1, i8>::cases_exhaustive_all() {
268          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
269        }
270      }
271    }
272  }
273
274  mod sub {
275    use super::*;
276    use malachite::rational::Rational;
277
278    #[allow(dead_code)]
279    fn ops() {
280      let mut a = crate::p32::ONE;
281      let mut b = crate::p32::MINUS_ONE;
282      let _ = a - b;
283      let _ = &a - b;
284      let _ = a - &b;
285      let _ = &a - &b;
286      a -= b;
287      b -= &a;
288    }
289
290    /// Aux function: check that `a - b` is rounded correctly.
291    fn is_correct_rounded<const N: u32, const ES: u32, Int: crate::Int>(
292      a: Posit<N, ES, Int>,
293      b: Posit<N, ES, Int>,
294    ) -> bool
295    where
296      Rational: TryFrom<Posit<N, ES, Int>>,
297      <Rational as TryFrom<Posit<N, ES, Int>>>::Error: core::fmt::Debug
298    {
299      let sub_posit = a - b;
300      if let (Ok(a), Ok(b)) = (Rational::try_from(a), Rational::try_from(b)) {
301        let sub_exact = a - b;
302        super::rational::is_correct_rounded(sub_exact, sub_posit)
303      } else {
304        sub_posit == Posit::<N, ES, Int>::NAR
305      }
306    }
307
308    #[test]
309    fn posit_10_0_exhaustive() {
310      for a in Posit::<10, 0, i16>::cases_exhaustive_all() {
311        for b in Posit::<10, 0, i16>::cases_exhaustive_all() {
312          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
313        }
314      }
315    }
316
317    #[test]
318    fn posit_10_1_exhaustive() {
319      for a in Posit::<10, 1, i16>::cases_exhaustive_all() {
320        for b in Posit::<10, 1, i16>::cases_exhaustive_all() {
321          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
322        }
323      }
324    }
325
326    #[test]
327    fn posit_10_2_exhaustive() {
328      for a in Posit::<10, 2, i16>::cases_exhaustive_all() {
329        for b in Posit::<10, 2, i16>::cases_exhaustive_all() {
330          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
331        }
332      }
333    }
334
335    #[test]
336    fn posit_10_3_exhaustive() {
337      for a in Posit::<10, 3, i16>::cases_exhaustive_all() {
338        for b in Posit::<10, 3, i16>::cases_exhaustive_all() {
339          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
340        }
341      }
342    }
343
344    #[test]
345    fn posit_8_0_exhaustive() {
346      for a in Posit::<8, 0, i8>::cases_exhaustive_all() {
347        for b in Posit::<8, 0, i8>::cases_exhaustive_all() {
348          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
349        }
350      }
351    }
352
353    #[test]
354    fn p8_exhaustive() {
355      for a in crate::p8::cases_exhaustive_all() {
356        for b in crate::p8::cases_exhaustive_all() {
357          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
358        }
359      }
360    }
361
362    use proptest::prelude::*;
363    const PROPTEST_CASES: u32 = if cfg!(debug_assertions) {0x1_0000} else {0x80_0000};
364    proptest!{
365      #![proptest_config(ProptestConfig::with_cases(PROPTEST_CASES))]
366
367      #[test]
368      fn p16_proptest(
369        a in crate::p16::cases_proptest(),
370        b in crate::p16::cases_proptest(),
371      ) {
372        assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
373      }
374
375      #[test]
376      fn p32_proptest(
377        a in crate::p32::cases_proptest(),
378        b in crate::p32::cases_proptest(),
379      ) {
380        assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
381      }
382
383      #[test]
384      fn p64_proptest(
385        a in crate::p64::cases_proptest(),
386        b in crate::p64::cases_proptest(),
387      ) {
388        assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
389      }
390    }
391
392    #[test]
393    fn posit_3_0_exhaustive() {
394      for a in Posit::<3, 0, i8>::cases_exhaustive_all() {
395        for b in Posit::<3, 0, i8>::cases_exhaustive_all() {
396          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
397        }
398      }
399    }
400    #[test]
401    fn posit_4_0_exhaustive() {
402      for a in Posit::<4, 0, i8>::cases_exhaustive_all() {
403        for b in Posit::<4, 0, i8>::cases_exhaustive_all() {
404          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
405        }
406      }
407    }
408    #[test]
409    fn posit_4_1_exhaustive() {
410      for a in Posit::<4, 1, i8>::cases_exhaustive_all() {
411        for b in Posit::<4, 1, i8>::cases_exhaustive_all() {
412          assert!(is_correct_rounded(a, b), "{:?}: {:?}", a, b)
413        }
414      }
415    }
416  }
417}