fast_posit/posit/convert/
int.rs

1use super::*;
2
3use crate::underlying::const_as;
4
5/// The kernel for converting a _signed_ int to a posit.
6///
7/// # Safety
8///
9/// `int` cannot be `FromInt::ZERO` or `FromInt::MIN`, or calling this function is *undefined
10/// behaviour*.
11#[inline]
12unsafe fn round_from_kernel<
13  FromInt: crate::Int,
14  const N: u32,
15  const ES: u32,
16  Int: crate::Int,
17>(int: FromInt) -> (Decoded<N, ES, Int>, Int) {
18  // If converting into a narrower type (`FromInt` → `Int`), we need to shift right, *before* we
19  // convert to the narrower type. Some bits will be lost in this conversion; we will accumulate
20  // them into `sticky`.
21  let shift_right = if const { Int::BITS >= FromInt::BITS } {0} else {FromInt::BITS - Int::BITS};
22
23  // If converting into a wider type (`FromInt` → `Int`), we need to shift left, *after* we convert
24  // to the wider type.
25  let shift_left = if const { Int::BITS <= FromInt::BITS } {0} else {Int::BITS - FromInt::BITS};
26
27  // To turn the `int` into a `frac` that starts with `0b01` or `0b10`, find the number of leading
28  // 0s or 1s, and shift left by that number of places minus one. To compensate, the `exp` has to
29  // be `FRAC_WIDTH` subtracted by the number of places we shifted. The `sticky` bits are the bits
30  // lost when shifting right.
31  //
32  // Examples:
33  //
34  //   value: 0b00010011 (= 19)
35  //    frac: 0b01001100
36  //     exp: +4 (= (8 - 2) frac width - 2 underflow)
37  //
38  //   value: 0b11111111 (= -1)
39  //    frac: 0b10000000
40  //     exp: -1 (= (8 - 2) frac width - 7 underflow)
41  //
42  // SAFETY: `int` is not 0 nor MIN (function precondition)
43  let underflow = unsafe { int.leading_run_minus_one() };
44  let frac = const_as::<FromInt, Int>(int << underflow >> shift_right) << shift_left;
45  let exp = {
46    let exp = Decoded::<N, ES, FromInt>::FRAC_WIDTH.wrapping_sub(underflow);
47    const_as::<i32, Int>(exp as i32)
48  };
49  let sticky = {
50    let true_shift = shift_right.saturating_sub(underflow);
51    Int::from(int.mask_lsb(true_shift) != FromInt::ZERO)
52  };
53
54  (Decoded{frac, exp}, sticky)
55}
56
57/// The kernel for converting a posit to a _signed_ int.
58#[inline]
59fn round_into_kernel<
60  ToInt: crate::Int,
61  const N: u32,
62  const ES: u32,
63  Int: crate::Int,
64>(decoded: Decoded<N, ES, Int>) -> ToInt {
65  // If converting into a narrower type (`Int` → `ToInt`), we need to shift right, *before* we
66  // convert to the narrower type. Some bits will be lost in this conversion; we will accumulate
67  // them into `sticky`.
68  let diff_right = if const { ToInt::BITS >= Int::BITS } {0} else {Int::BITS - ToInt::BITS};
69
70  // If converting into a wider type (`Int` → `ToInt`), we may shift left, *after* we convert to
71  // the wider type.
72  let diff_left = if const { ToInt::BITS <= Int::BITS } {0} else {ToInt::BITS - Int::BITS};
73
74  // To convert `decoded` into an int, note that the value it represents is
75  //
76  //   frac × 2^-FRAC_WIDTH × 2^exp
77  //
78  // The integer part of this is the result, the fractional part determines the direction of the
79  // rounding (as always, the rounding is "round to nearest, ties to even"; to understand in more
80  // detail how this is implemented using `round` and `sticky` bits, see the comments in
81  // [`encode_regular_round`]).
82  //
83  // Examples:
84  //
85  //   19.0:
86  //      frac: 0b01_001100
87  //       exp: +4
88  //     shift: 6 frac width - 4 exp = 2
89  //     value: 0b00010011 = 19
90  //
91  //   -1.0:
92  //      frac: 0b10_000000
93  //       exp: -1 (= (8 - 2) frac width - 7 underflow)
94  //     shift: 6 frac width + 1 exp = 7
95  //     value: 0b11111111 = -1
96  //
97  //   19.5:
98  //      frac: 0b01_001110
99  //       exp: +4
100  //     shift: 6 frac width - 4 exp = 2
101  //     value: 0b00010100 = 20 (rounded up)
102  //
103  // Two points to keep in mind
104  //
105  //   - `ToInt` may be wider or narrower than the source posit's `Int`, so we must be careful in
106  //     doing the shift and casts in the right order
107  //   - The shift may be too big in either direction: if too big to the left it leads to overflow
108  //     (in which case `ToInt::MIN` is returned), and if too big to the right it leads to loss of
109  //     all bits (in which case `ToInt::ZERO` is returned).
110
111  // This is the amount that `frac` needs to be shifted right (or left, if negative).
112  let shift = Int::of_u32(Decoded::<N, ES, Int>::FRAC_WIDTH).wrapping_sub(decoded.exp);
113
114  // If `shift` is negative: the `ToInt` type needs to be wide enough to hold the value.
115  if shift < Int::ZERO {
116    let shift_left = (-shift).as_u32();
117    if shift_left > diff_left {
118      // Too narrow, return `0b10000…`.
119      return ToInt::MIN;
120    }
121    const_as::<Int, ToInt>(decoded.frac) << shift_left
122  }
123  // If `shift` is exactly zero: the `ToInt` type cannot be any narrower.
124  else if shift == Int::ZERO {
125    if diff_right != 0 {
126      // Too narrow, return `0b10000…`.
127      return ToInt::MIN;
128    }
129    const_as::<Int, ToInt>(decoded.frac)
130  }
131  // If `shift` is greater than zero: the `ToInt` type needs to be wide enough to hold the value;
132  // also since we shift left we need to take care of rounding depending on what bits were lost.
133  else {
134    let shift_right = shift.as_u32();
135    if shift_right > Int::BITS {
136      // The whole thing is shifted out, return `0`.
137      return ToInt::ZERO;
138    }
139    if shift_right < diff_right {
140      // Too narrow, return `0b10000…`.
141      return ToInt::MIN;
142    }
143    // Rounding formula with round bit and sticky bit, for details see [`encode_regular_round`].
144    let sticky = decoded.frac.mask_lsb(shift_right - 1);
145    let int = decoded.frac >> (shift_right - 1);
146    let round = int.get_lsb();
147    let int = int >> 1;
148    let odd = int.get_lsb();
149    // Assemble the number, with the rounding rules.
150    //
151    // One detail: we use wrapping_add on round_up because, if the rounded value exceeds
152    // `ToInt::MAX`, when adding 1 we wrap around to `ToInt::MIN` = `0b1000…`, which is exactly
153    // what we want (example: if `i8::MAX` == 127, but the posit is 127.5, rounding up to 128
154    // exceeds the representable irange of `i8`, so we return `i8::MIN`).
155    let round_up: bool = round & (odd | (sticky != Int::ZERO));
156    const_as::<Int, ToInt>(int).wrapping_add(ToInt::from(round_up))
157  }
158}
159
160macro_rules! make_impl {
161  ($t:ty) => {
162    impl<
163      const N: u32,
164      const ES: u32,
165      Int: crate::Int,
166    > RoundFrom<$t> for Posit<N, ES, Int> {
167      #[doc = concat!("Convert an `", stringify!($t), "` into a `Posit`, [rounding according to the standard]:")]
168      ///
169      #[doc = concat!("  - If the value is [`", stringify!($t), "::MIN`] (i.e. the value where the most significant bit is 1 and the rest are 0), it converts to [NaR](Posit::NAR).")]
170      ///   - Otherwise, the integer value is rounded (if necessary).
171      ///
172      /// [rounding according to the standard]: https://posithub.org/docs/posit_standard-2.pdf#subsection.6.4
173      fn round_from(value: $t) -> Self {
174        // Handle 0 and MIN. Remember that according to the standard, MIN (i.e. bit pattern
175        // 0b1000…), is converted to NaR, and NaR is converted to MIN.
176        if value == 0 { return Posit::ZERO }
177        if value == <$t>::MIN { return Posit::NAR }
178
179        // This piece of code is only necessary in really extreme cases, like converting i128::MAX
180        // to an 8-bit posit. But in those cases, we do need to guard against overflow on `exp`.
181        if const { <$t>::BITS as i128 > 1 << Decoded::<N, ES, Int>::FRAC_WIDTH } {
182          let limit = 1 << (1 << Decoded::<N, ES, Int>::FRAC_WIDTH);
183          if value >=  limit { return Posit::MAX }
184          if value <= -limit { return Posit::MIN }
185        }
186
187        // SAFETY: `value` is not 0 or MIN
188        let (result, sticky) = unsafe { round_from_kernel(value) };
189        // SAFETY: `frac` is not underflowing and `exp` cannot be greater than `FromInt::BITS`
190        unsafe { result.encode_regular_round(sticky) }
191      }
192    }
193
194    impl<
195      const N: u32,
196      const ES: u32,
197      Int: crate::Int,
198    > RoundFrom<Posit<N, ES, Int>> for $t {
199      #[doc = concat!("Convert a `Posit` into an `", stringify!($t), "`, [rounding according to the standard]:")]
200      ///
201      #[doc = concat!("  - If the value is [NaR](Posit::NAR), or if overflows the target type, then it converts to [`", stringify!($t), "::MIN`] (i.e. the value where the most significant bit is 1 and the rest are 0).")]
202      ///   - Otherwise, it returns the nearest integer to `value`, rounding ties to even.
203      ///
204      /// [rounding according to the standard]: https://posithub.org/docs/posit_standard-2.pdf#subsection.6.4
205      // TODO examples? here and in the other conversions
206      fn round_from(value: Posit<N, ES, Int>) -> Self {
207        if value == Posit::ZERO { return 0 }
208        if value == Posit::NAR { return <$t>::MIN }
209
210        // SAFETY: `value` is not 0 or NaR
211        let decoded = unsafe { value.decode_regular() };
212        round_into_kernel(decoded)
213      }
214    }
215  }
216}
217
218make_impl!{i8}
219make_impl!{i16}
220make_impl!{i32}
221make_impl!{i64}
222make_impl!{i128}
223
224#[cfg(test)]
225mod tests {
226  use super::*;
227  use malachite::rational::Rational;
228  use proptest::prelude::*;
229
230  mod int_to_posit {
231    use super::*;
232
233    /// Aux function: check that `int` is converted to a posit with the correct rounding.
234    fn is_correct_rounded<FromInt: crate::Int, const N: u32, const ES: u32, Int: crate::Int>(
235      int: FromInt,
236    ) -> bool
237    where
238      FromInt: Into<Rational> + RoundInto<Posit<N, ES, Int>>,
239      Rational: TryFrom<Posit<N, ES, Int>, Error = super::rational::IsNaR>,
240    {
241      let posit: Posit<N, ES, Int> = int.round_into();
242      if int == FromInt::MIN {
243        posit == Posit::NAR
244      } else {
245        let exact: Rational = int.into();
246        super::rational::is_correct_rounded(exact, posit)
247      }
248    }
249
250    macro_rules! make_exhaustive {
251      ($t:ident) => {
252        mod $t {
253          use super::*;
254
255          #[test]
256          fn posit_10_0_exhaustive() {
257            for int in $t::MIN ..= $t::MAX {
258              assert!(is_correct_rounded::<$t, 10, 0, i16>(int), "{:?}", int);
259            }
260          }
261
262          #[test]
263          fn posit_10_1_exhaustive() {
264            for int in $t::MIN ..= $t::MAX {
265              assert!(is_correct_rounded::<$t, 10, 1, i16>(int), "{:?}", int);
266            }
267          }
268
269          #[test]
270          fn posit_10_2_exhaustive() {
271            for int in $t::MIN ..= $t::MAX {
272              assert!(is_correct_rounded::<$t, 10, 2, i16>(int), "{:?}", int);
273            }
274          }
275
276          #[test]
277          fn posit_10_3_exhaustive() {
278            for int in $t::MIN ..= $t::MAX {
279              assert!(is_correct_rounded::<$t, 10, 3, i16>(int), "{:?}", int);
280            }
281          }
282
283          #[test]
284          fn posit_8_0_exhaustive() {
285            for int in $t::MIN ..= $t::MAX {
286              assert!(is_correct_rounded::<$t, 8, 0, i8>(int), "{:?}", int);
287            }
288          }
289
290          #[test]
291          fn p8_exhaustive() {
292            for int in $t::MIN ..= $t::MAX {
293              assert!(is_correct_rounded::<$t, 8, 2, i8>(int), "{:?}", int);
294            }
295          }
296
297          #[test]
298          fn p16_exhaustive() {
299            for int in $t::MIN ..= $t::MAX {
300              assert!(is_correct_rounded::<$t, 16, 2, i16>(int), "{:?}", int);
301            }
302          }
303
304          #[test]
305          fn p32_exhaustive() {
306            for int in $t::MIN ..= $t::MAX {
307              assert!(is_correct_rounded::<$t, 32, 2, i32>(int), "{:?}", int);
308            }
309          }
310
311          #[test]
312          fn p64_exhaustive() {
313            for int in $t::MIN ..= $t::MAX {
314              assert!(is_correct_rounded::<$t, 64, 2, i64>(int), "{:?}", int);
315            }
316          }
317
318          #[test]
319          fn posit_3_0_exhaustive() {
320            for int in $t::MIN ..= $t::MAX {
321              assert!(is_correct_rounded::<$t, 3, 0, i8>(int), "{:?}", int);
322            }
323          }
324
325          #[test]
326          fn posit_4_0_exhaustive() {
327            for int in $t::MIN ..= $t::MAX {
328              assert!(is_correct_rounded::<$t, 4, 0, i8>(int), "{:?}", int);
329            }
330          }
331
332          #[test]
333          fn posit_4_1_exhaustive() {
334            for int in $t::MIN ..= $t::MAX {
335              assert!(is_correct_rounded::<$t, 4, 1, i8>(int), "{:?}", int);
336            }
337          }
338        }
339      }
340    }
341
342    macro_rules! make_proptest {
343      ($t:ident) => {
344        mod $t {
345          use super::*;
346
347          proptest!{
348            #![proptest_config(ProptestConfig::with_cases(crate::PROPTEST_CASES))]
349
350            #[test]
351            fn posit_10_0_proptest(int in any::<$t>()) {
352              assert!(is_correct_rounded::<$t, 10, 0, i16>(int), "{:?}", int);
353            }
354
355            #[test]
356            fn posit_10_1_proptest(int in any::<$t>()) {
357              assert!(is_correct_rounded::<$t, 10, 1, i16>(int), "{:?}", int);
358            }
359
360            #[test]
361            fn posit_10_2_proptest(int in any::<$t>()) {
362              assert!(is_correct_rounded::<$t, 10, 2, i16>(int), "{:?}", int);
363            }
364
365            #[test]
366            fn posit_10_3_proptest(int in any::<$t>()) {
367              assert!(is_correct_rounded::<$t, 10, 3, i16>(int), "{:?}", int);
368            }
369
370            #[test]
371            fn posit_8_0_proptest(int in any::<$t>()) {
372              assert!(is_correct_rounded::<$t, 8, 0, i8>(int), "{:?}", int);
373            }
374
375            #[test]
376            fn p8_proptest(int in any::<$t>()) {
377              assert!(is_correct_rounded::<$t, 8, 2, i8>(int), "{:?}", int);
378            }
379
380            #[test]
381            fn p16_proptest(int in any::<$t>()) {
382              assert!(is_correct_rounded::<$t, 16, 2, i16>(int), "{:?}", int);
383            }
384
385            #[test]
386            fn p32_proptest(int in any::<$t>()) {
387              assert!(is_correct_rounded::<$t, 32, 2, i32>(int), "{:?}", int);
388            }
389
390            #[test]
391            fn p64_proptest(int in any::<$t>()) {
392              assert!(is_correct_rounded::<$t, 64, 2, i64>(int), "{:?}", int);
393            }
394
395            #[test]
396            fn posit_3_0_proptest(int in any::<$t>()) {
397              assert!(is_correct_rounded::<$t, 3, 0, i8>(int), "{:?}", int);
398            }
399
400            #[test]
401            fn posit_4_0_proptest(int in any::<$t>()) {
402              assert!(is_correct_rounded::<$t, 4, 0, i8>(int), "{:?}", int);
403            }
404
405            #[test]
406            fn posit_4_1_proptest(int in any::<$t>()) {
407              assert!(is_correct_rounded::<$t, 4, 1, i8>(int), "{:?}", int);
408            }
409          }
410        }
411      }
412    }
413
414    make_exhaustive!{i8}
415    make_exhaustive!{i16}
416    make_proptest!{i32}
417    make_proptest!{i64}
418    make_proptest!{i128}
419  }
420
421  mod posit_to_int {
422    use super::*;
423
424    /// Aux function: check that `posit` rounds to `int`.
425    fn is_correct_rounded<ToInt: crate::Int, const N: u32, const ES: u32, Int: crate::Int>(
426      posit: Posit<N, ES, Int>,
427      int: ToInt,
428    ) -> bool
429    where
430      ToInt: RoundFrom<Posit<N, ES, Int>>,
431      Rational: From<i32> + From<ToInt> + TryFrom<Posit<N, ES, Int>, Error = super::rational::IsNaR>,
432      // TODO Why do I need the `From<i32>` bound hereeeeeee, rust pls fix
433    {
434      match Rational::try_from(posit) {
435        Ok(exact) => {
436          use malachite::base::num::arithmetic::traits::RoundToMultiple;
437          use malachite::base::rounding_modes::RoundingMode;
438          let rounded = exact.round_to_multiple(Rational::from(1), RoundingMode::Nearest).0;
439          if rounded > Rational::from(ToInt::MAX) {
440            int == ToInt::MIN
441          } else if rounded < Rational::from(ToInt::MIN) {
442            int == ToInt::MIN
443          } else {
444            Rational::from(int) == rounded
445          }
446        },
447        Err(super::rational::IsNaR) => {
448          int == ToInt::MIN
449        }
450      }
451    }
452
453    macro_rules! make_exhaustive {
454      ($name:ident, $t:ty) => {
455        mod $name {
456          use super::*;
457
458          #[test]
459          fn i8_exhaustive() {
460            for p in <$t>::cases_exhaustive_all() {
461              let int: i8 = p.round_into();
462              assert!(is_correct_rounded(p, int), "{p:?} {int}");
463            }
464          }
465
466          #[test]
467          fn i16_exhaustive() {
468            for p in <$t>::cases_exhaustive_all() {
469              let int: i16 = p.round_into();
470              assert!(is_correct_rounded(p, int), "{p:?} {int}");
471            }
472          }
473
474          #[test]
475          fn i32_exhaustive() {
476            for p in <$t>::cases_exhaustive_all() {
477              let int: i32 = p.round_into();
478              assert!(is_correct_rounded(p, int), "{p:?} {int}");
479            }
480          }
481
482          #[test]
483          fn i64_exhaustive() {
484            for p in <$t>::cases_exhaustive_all() {
485              let int: i64 = p.round_into();
486              assert!(is_correct_rounded(p, int), "{p:?} {int}");
487            }
488          }
489
490          #[test]
491          fn i128_exhaustive() {
492            for p in <$t>::cases_exhaustive_all() {
493              let int: i128 = p.round_into();
494              assert!(is_correct_rounded(p, int), "{p:?} {int}");
495            }
496          }
497        }
498      }
499    }
500
501    macro_rules! make_proptest {
502      ($name:ident, $t:ty) => {
503        mod $name {
504          use super::*;
505
506          proptest!{
507            #![proptest_config(ProptestConfig::with_cases(crate::PROPTEST_CASES))]
508
509            #[test]
510            fn i8_proptest(p in <$t>::cases_proptest_all()) {
511              let int: i8 = p.round_into();
512              assert!(is_correct_rounded(p, int), "{p:?} {int}");
513            }
514
515            #[test]
516            fn i16_proptest(p in <$t>::cases_proptest_all()) {
517              let int: i16 = p.round_into();
518              assert!(is_correct_rounded(p, int), "{p:?} {int}");
519            }
520
521            #[test]
522            fn i32_proptest(p in <$t>::cases_proptest_all()) {
523              let int: i32 = p.round_into();
524              assert!(is_correct_rounded(p, int), "{p:?} {int}");
525            }
526
527            #[test]
528            fn i64_proptest(p in <$t>::cases_proptest_all()) {
529              let int: i64 = p.round_into();
530              assert!(is_correct_rounded(p, int), "{p:?} {int}");
531            }
532
533            #[test]
534            fn i128_proptest(p in <$t>::cases_proptest_all()) {
535              let int: i128 = p.round_into();
536              assert!(is_correct_rounded(p, int), "{p:?} {int}");
537            }
538          }
539        }
540      }
541    }
542
543    make_exhaustive!{posit_10_0, Posit<10, 0, i16>}
544    make_exhaustive!{posit_10_1, Posit<10, 1, i16>}
545    make_exhaustive!{posit_10_2, Posit<10, 2, i16>}
546    make_exhaustive!{posit_10_3, Posit<10, 3, i16>}
547    make_exhaustive!{posit_8_0,  Posit<8,  0, i8 >}
548    make_exhaustive!{p8, crate::p8}
549    make_exhaustive!{p16, crate::p16}
550    make_proptest!{p32, crate::p32}
551    make_proptest!{p64, crate::p64}
552    make_exhaustive!{posit_3_0, Posit::<3, 0, i8>}
553    make_exhaustive!{posit_4_0, Posit::<4, 0, i8>}
554    make_exhaustive!{posit_4_1, Posit::<4, 1, i8>}
555  }
556}