Skip to main content

fast_posit/posit/
round_int.rs

1use super::*;
2
3impl<
4  const N: u32,
5  const ES: u32,
6  Int: crate::Int,
7  const RS: u32,
8> Posit<N, ES, Int, RS> {
9  // TODO note that these functions can probably be made more efficient if we don't call
10  // encode/decode, since all we have to do is manipulate the `frac` field without touching the
11  // exponents (not quite, because of overflow, but that overflow of frac into exp and regime is
12  // handled elegantly due to the posit bit format).
13
14  /// Returns the integer-valued posit nearest to `self`, and the nearest even integer-valued posit
15  /// if two integers are equally near.
16  ///
17  /// Standard: "[**nearestInt**](https://posithub.org/docs/posit_standard-2.pdf#subsection.5.2)".
18  ///
19  /// # Example
20  ///
21  /// ```
22  /// # use fast_posit::*;
23  /// assert_eq!(p32::round_from(3.1).nearest_int(), p32::round_from(3));
24  /// assert_eq!(p32::round_from(3.5).nearest_int(), p32::round_from(4));
25  /// assert_eq!(p32::round_from(3.9).nearest_int(), p32::round_from(4));
26  /// ```
27  pub fn nearest_int(self) -> Self {
28    if self.is_special() { return self }
29    // SAFETY: `self` is not 0 or NaR
30    let decoded = unsafe { self.decode_regular() };
31
32    // Let's split `decoded.frac` into an `integral` (left of the decimal dot) and `fractional`
33    // (right of the decimal dot) part. For an `exp` of 0, the decimal dot is `FRAC_WIDTH` places
34    // from the right / 2 places from the left. If the `exp` is bigger or smaller, the dot moves
35    // right or left respectively.
36    //
37    // Examples:
38    //
39    //         frac: 0b01_1101
40    //          exp: +0
41    //     integral: 0b01
42    //   fractional: 0b110100
43    //
44    //         frac: 0b01_1101
45    //          exp: +2
46    //     integral: 0b0111
47    //   fractional: 0b010000
48    //
49    //         frac: 0b01_1101
50    //          exp: -1
51    //     integral: 0b0
52    //   fractional: 0b101110
53    let integral_bits = (Int::ONE + Int::ONE).wrapping_add(decoded.exp);
54
55    // If there are no bits in the integral part, then the result is just 0.
56    //
57    //   - Positive case: 0b01_xxxx ×2^-2 = [+0.25,+0.50[ ⇒ rounds to 0
58    //   - Negative case: 0b10_xxxx ×2^-2 = [-0.50,-0.25[ ⇒ rounds to 0
59    if integral_bits <= Int::ZERO {
60      return Posit::ZERO
61    }
62    // If the are no bits in the fractional part, then the result is the posit itself, which is
63    // already an integer.
64    if integral_bits >= Int::of_u32(Int::BITS) {
65      return self;
66    }
67
68    // Otherwise, grab the integral and fractional part. The fractional part tells us whether we
69    // need to round the integral part up or not: we do round up if the fractional part is greater
70    // than 0.5, or if it is exactly 0.5 and the integral part is odd (so that we round to an even
71    // number).
72    let integral_bits = integral_bits.as_u32();
73    let fractional_bits = Int::BITS - integral_bits;
74    let integral = decoded.frac >> fractional_bits;
75    let fractional = decoded.frac << integral_bits;
76
77    // If `fractional` is `0b0xxx…`, then we round down. If it's `0b1xxx…`, then we round up,
78    // unless it's exactly `0b1000…`, in which case we round up if `integral` is odd.
79    let round = !fractional.is_positive();
80    let sticky = fractional << 1 != Int::ZERO;
81    let odd = integral.get_lsb();
82    let round_up: bool = round & (odd | sticky);
83
84    // TODO This is an interesting alternative formulation of the round_up formula, maybe we can
85    // use it elsewhere too.
86    /*let round_up = !(fractional | Int::from(odd)).is_positive();*/
87
88    // Tricky detail! If we do round up, we might overflow to the next exponent (e.g. from 0b01_11
89    // to 0b01_000); if this happens then we must subtract 1 from `fractional_bits` and add 1 to
90    // `exp`. In the case of a negative `frac`, the reverse might happen. To handle both cases
91    // without branching, we turn to our trusty `leading_run_minus_one`.
92    let integral_rounded = integral + Int::from(round_up);
93    if integral_rounded == Int::ZERO {
94      return Posit::ZERO
95    }
96    // SAFETY: `integral_rounded` is not 0 (checked) nor Int::MIN (impossible)
97    let true_fractional_bits = unsafe { integral_rounded.leading_run_minus_one() };
98    let frac = integral_rounded << true_fractional_bits;
99    let exp = decoded.exp + (Int::of_u32(fractional_bits) - Int::of_u32(true_fractional_bits));
100
101    // SAFETY: `frac` can only be underflowing if it's 0, which we checked above.
102    unsafe { Decoded { frac, exp }.encode_regular() }
103  }
104
105  /// Returns the largest integer-valued posit less than or equal to `self`.
106  ///
107  /// Standard: "[**floor**](https://posithub.org/docs/posit_standard-2.pdf#subsection.5.2)".
108  ///
109  /// # Example
110  ///
111  /// ```
112  /// # use fast_posit::*;
113  /// assert_eq!(p32::round_from(3.1).floor(), p32::round_from(3));
114  /// assert_eq!(p32::round_from(3.5).floor(), p32::round_from(3));
115  /// assert_eq!(p32::round_from(3.9).floor(), p32::round_from(3));
116  /// ```
117  pub fn floor(self) -> Self {
118    // `floor` follows the same template as `nearest_int`, only it is considerably simpler because
119    // we don't care about rounding. For details, see the comments in the code of [`nearest_int`].
120
121    if self.is_special() { return self }
122    // SAFETY: `self` is not 0 or NaR
123    let decoded = unsafe { self.decode_regular() };
124
125    let integral_bits = (Int::ONE + Int::ONE).wrapping_add(decoded.exp);
126
127    // If there are no bits in the integral part, then the result is 0 or -1.
128    //
129    //   - Positive case: 0b01_xxxx ×2^-2 = [+0.25,+0.50[ ⇒ rounds to 0
130    //   - Negative case: 0b10_xxxx ×2^-2 = [-0.50,-0.25[ ⇒ rounds to -1
131    if integral_bits <= Int::ZERO {
132      return if self >= Posit::ZERO {Posit::ZERO} else {Posit::MINUS_ONE}
133    }
134    // If the are no bits in the fractional part, then the result is the posit itself, which is
135    // already an integer.
136    if integral_bits >= Int::of_u32(Int::BITS) {
137      return self;
138    }
139
140    let integral_bits = integral_bits.as_u32();
141    let frac = decoded.frac.mask_msb(integral_bits);
142    let exp = decoded.exp;
143    if frac == Int::ZERO {
144      return Posit::ZERO
145    }
146
147    // SAFETY: `frac` can only be underflowing if it's 0, which we checked in the other branch.
148    unsafe { Decoded { frac, exp }.encode_regular() }
149  }
150
151  /// Returns the smallest integer-valued posit greater than or equal to `self`.
152  ///
153  /// Standard: "[**ceil**](https://posithub.org/docs/posit_standard-2.pdf#subsection.5.2)".
154  ///
155  /// # Example
156  ///
157  /// ```
158  /// # use fast_posit::*;
159  /// assert_eq!(p32::round_from(3.1).ceil(), p32::round_from(4));
160  /// assert_eq!(p32::round_from(3.5).ceil(), p32::round_from(4));
161  /// assert_eq!(p32::round_from(3.9).ceil(), p32::round_from(4));
162  /// ```
163  pub fn ceil(self) -> Self {
164    // `floor` follows the same template as `nearest_int`. Once again we care about rounding, but
165    // the rule is considerably simpler: round up if `fractional != 0`. For details, see the
166    // comments in the code of [`nearest_int`].
167
168    if self.is_special() { return self }
169    // SAFETY: `self` is not 0 or NaR
170    let decoded = unsafe { self.decode_regular() };
171
172    let integral_bits = (Int::ONE + Int::ONE).wrapping_add(decoded.exp);
173    // If there are no bits in the integral part, then the result is 0 or +1.
174    //
175    //   - Positive case: 0b01_xxxx ×2^-2 = [+0.25,+0.50[ ⇒ rounds to 1
176    //   - Negative case: 0b10_xxxx ×2^-2 = [-0.50,-0.25[ ⇒ rounds to 0
177    if integral_bits <= Int::ZERO {
178      return if self >= Posit::ZERO {Posit::ONE} else {Posit::ZERO}
179    }
180    // If the are no bits in the fractional part, then the result is the posit itself, which is
181    // already an integer.
182    if integral_bits >= Int::of_u32(Int::BITS) {
183      return self;
184    }
185
186    let integral_bits = integral_bits.as_u32();
187    let fractional_bits = Int::BITS - integral_bits;
188    let integral = decoded.frac >> fractional_bits;
189    let fractional = decoded.frac << integral_bits;
190
191    let round_up: bool = fractional != Int::ZERO;
192
193    let integral_rounded = integral + Int::from(round_up);
194    if integral_rounded == Int::ZERO {
195      return Posit::ZERO
196    }
197    // SAFETY: `integral_rounded` is not 0 (checked) nor Int::MIN (impossible)
198    let true_fractional_bits = unsafe { integral_rounded.leading_run_minus_one() };
199    let frac = integral_rounded << true_fractional_bits;
200    let exp = decoded.exp + (Int::of_u32(fractional_bits) - Int::of_u32(true_fractional_bits));
201
202    // SAFETY: `frac` can only be underflowing if it's 0, which we checked in the other branch.
203    unsafe { Decoded { frac, exp }.encode_regular() }
204  }
205}
206
207#[cfg(test)]
208mod tests {
209  use super::*;
210  use malachite::rational::Rational;
211  use proptest::prelude::*;
212
213  use malachite::base::rounding_modes::RoundingMode;
214
215  /// Aux function: check that `posit` rounded to `rounded_posit` is correct for `rounding_mode`.
216  fn is_correct_rounded<const N: u32, const ES: u32, Int: crate::Int, const RS: u32>(
217    posit: Posit<N, ES, Int, RS>,
218    rounded_posit: Posit<N, ES, Int, RS>,
219    rounding_mode: RoundingMode,
220  ) -> bool
221  where
222    Rational: From<i32> + TryFrom<Posit<N, ES, Int, RS>, Error = super::rational::IsNaR>,
223  {
224    use malachite::base::num::arithmetic::traits::RoundToMultiple;
225    let posit = Rational::try_from(posit)
226      .map(|exact| exact.round_to_multiple(Rational::from(1), rounding_mode).0);
227    let rounded_posit = Rational::try_from(rounded_posit);
228    posit == rounded_posit
229  }
230
231  mod nearest_int {
232    use super::*;
233
234    macro_rules! test_exhaustive {
235      ($name:ident, $posit:ty) => {
236        #[test]
237        fn $name() {
238          for p in <$posit>::cases_exhaustive_all() {
239            let rounded = p.nearest_int();
240            assert!(is_correct_rounded(p, rounded, RoundingMode::Nearest), "{p:?} {rounded:?}")
241          }
242        }
243      };
244    }
245
246    macro_rules! test_proptest {
247      ($name:ident, $posit:ty) => {
248        proptest!{
249          #![proptest_config(ProptestConfig::with_cases(crate::PROPTEST_CASES))]
250          #[test]
251          fn $name(p in <$posit>::cases_proptest_all()) {
252            let rounded = p.nearest_int();
253            assert!(is_correct_rounded(p, rounded, RoundingMode::Nearest), "{p:?} {rounded:?}")
254          }
255        }
256      };
257    }
258
259    test_exhaustive!{posit_10_0_exhaustive, Posit<10, 0, i16>}
260    test_exhaustive!{posit_10_1_exhaustive, Posit<10, 1, i16>}
261    test_exhaustive!{posit_10_2_exhaustive, Posit<10, 2, i16>}
262    test_exhaustive!{posit_10_3_exhaustive, Posit<10, 3, i16>}
263
264    test_exhaustive!{posit_8_0_exhaustive,  Posit<8,  0, i8 >}
265
266    test_exhaustive!{p8_exhaustive, crate::p8}
267    test_exhaustive!{p16_exhaustive, crate::p16}
268    test_proptest!{p32_proptest, crate::p32}
269    test_proptest!{p64_proptest, crate::p64}
270
271    test_exhaustive!{posit_3_0_exhaustive, Posit::<3, 0, i8>}
272    test_exhaustive!{posit_4_0_exhaustive, Posit::<4, 0, i8>}
273    test_exhaustive!{posit_4_1_exhaustive, Posit::<4, 1, i8>}
274
275    test_exhaustive!{bposit_8_3_6_exhaustive, Posit::<8, 3, i8, 6>}
276    test_exhaustive!{bposit_16_5_6_exhaustive, Posit::<16, 5, i16, 6>}
277    test_proptest!{bposit_32_5_6_proptest, Posit::<32, 5, i32, 6>}
278    test_proptest!{bposit_64_5_6_proptest, Posit::<64, 5, i64, 6>}
279
280    test_exhaustive!{bposit_10_2_6_exhaustive, Posit::<10, 2, i16, 7>}
281    test_exhaustive!{bposit_10_2_7_exhaustive, Posit::<10, 2, i16, 7>}
282    test_exhaustive!{bposit_10_2_8_exhaustive, Posit::<10, 2, i16, 8>}
283    test_exhaustive!{bposit_10_2_9_exhaustive, Posit::<10, 2, i16, 9>}
284  }
285
286  mod floor {
287    use super::*;
288
289    macro_rules! test_exhaustive {
290      ($name:ident, $posit:ty) => {
291        #[test]
292        fn $name() {
293          for p in <$posit>::cases_exhaustive_all() {
294            let rounded = p.floor();
295            assert!(is_correct_rounded(p, rounded, RoundingMode::Floor), "{p:?} {rounded:?}")
296          }
297        }
298      };
299    }
300
301    macro_rules! test_proptest {
302      ($name:ident, $posit:ty) => {
303        proptest!{
304          #![proptest_config(ProptestConfig::with_cases(crate::PROPTEST_CASES))]
305          #[test]
306          fn $name(p in <$posit>::cases_proptest_all()) {
307            let rounded = p.floor();
308            assert!(is_correct_rounded(p, rounded, RoundingMode::Floor), "{p:?} {rounded:?}")
309          }
310        }
311      };
312    }
313
314    test_exhaustive!{posit_10_0_exhaustive, Posit<10, 0, i16>}
315    test_exhaustive!{posit_10_1_exhaustive, Posit<10, 1, i16>}
316    test_exhaustive!{posit_10_2_exhaustive, Posit<10, 2, i16>}
317    test_exhaustive!{posit_10_3_exhaustive, Posit<10, 3, i16>}
318
319    test_exhaustive!{posit_8_0_exhaustive,  Posit<8,  0, i8 >}
320
321    test_exhaustive!{p8_exhaustive, crate::p8}
322    test_exhaustive!{p16_exhaustive, crate::p16}
323    test_proptest!{p32_proptest, crate::p32}
324    test_proptest!{p64_proptest, crate::p64}
325
326    test_exhaustive!{posit_3_0_exhaustive, Posit::<3, 0, i8>}
327    test_exhaustive!{posit_4_0_exhaustive, Posit::<4, 0, i8>}
328    test_exhaustive!{posit_4_1_exhaustive, Posit::<4, 1, i8>}
329
330    test_exhaustive!{bposit_8_3_6_exhaustive, Posit::<8, 3, i8, 6>}
331    test_exhaustive!{bposit_16_5_6_exhaustive, Posit::<16, 5, i16, 6>}
332    test_proptest!{bposit_32_5_6_proptest, Posit::<32, 5, i32, 6>}
333    test_proptest!{bposit_64_5_6_proptest, Posit::<64, 5, i64, 6>}
334
335    test_exhaustive!{bposit_10_2_6_exhaustive, Posit::<10, 2, i16, 7>}
336    test_exhaustive!{bposit_10_2_7_exhaustive, Posit::<10, 2, i16, 7>}
337    test_exhaustive!{bposit_10_2_8_exhaustive, Posit::<10, 2, i16, 8>}
338    test_exhaustive!{bposit_10_2_9_exhaustive, Posit::<10, 2, i16, 9>}
339  }
340
341  mod ceil {
342    use super::*;
343
344    macro_rules! test_exhaustive {
345      ($name:ident, $posit:ty) => {
346        #[test]
347        fn $name() {
348          for p in <$posit>::cases_exhaustive_all() {
349            let rounded = p.ceil();
350            assert!(is_correct_rounded(p, rounded, RoundingMode::Ceiling), "{p:?} {rounded:?}")
351          }
352        }
353      };
354    }
355
356    macro_rules! test_proptest {
357      ($name:ident, $posit:ty) => {
358        proptest!{
359          #![proptest_config(ProptestConfig::with_cases(crate::PROPTEST_CASES))]
360          #[test]
361          fn $name(p in <$posit>::cases_proptest_all()) {
362            let rounded = p.ceil();
363            assert!(is_correct_rounded(p, rounded, RoundingMode::Ceiling), "{p:?} {rounded:?}")
364          }
365        }
366      };
367    }
368
369    test_exhaustive!{posit_10_0_exhaustive, Posit<10, 0, i16>}
370    test_exhaustive!{posit_10_1_exhaustive, Posit<10, 1, i16>}
371    test_exhaustive!{posit_10_2_exhaustive, Posit<10, 2, i16>}
372    test_exhaustive!{posit_10_3_exhaustive, Posit<10, 3, i16>}
373
374    test_exhaustive!{posit_8_0_exhaustive,  Posit<8,  0, i8 >}
375
376    test_exhaustive!{p8_exhaustive, crate::p8}
377    test_exhaustive!{p16_exhaustive, crate::p16}
378    test_proptest!{p32_proptest, crate::p32}
379    test_proptest!{p64_proptest, crate::p64}
380
381    test_exhaustive!{posit_3_0_exhaustive, Posit::<3, 0, i8>}
382    test_exhaustive!{posit_4_0_exhaustive, Posit::<4, 0, i8>}
383    test_exhaustive!{posit_4_1_exhaustive, Posit::<4, 1, i8>}
384
385    test_exhaustive!{bposit_8_3_6_exhaustive, Posit::<8, 3, i8, 6>}
386    test_exhaustive!{bposit_16_5_6_exhaustive, Posit::<16, 5, i16, 6>}
387    test_proptest!{bposit_32_5_6_proptest, Posit::<32, 5, i32, 6>}
388    test_proptest!{bposit_64_5_6_proptest, Posit::<64, 5, i64, 6>}
389
390    test_exhaustive!{bposit_10_2_6_exhaustive, Posit::<10, 2, i16, 6>}
391    test_exhaustive!{bposit_10_2_7_exhaustive, Posit::<10, 2, i16, 7>}
392    test_exhaustive!{bposit_10_2_8_exhaustive, Posit::<10, 2, i16, 8>}
393    test_exhaustive!{bposit_10_2_9_exhaustive, Posit::<10, 2, i16, 9>}
394  }
395}