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/// If `value` is `FromInt::ZERO` or `FromInt::MIN`, calling this function is *undefined behaviour*.
8#[inline]
9unsafe fn round_from_signed_kernel<
10  FromInt: crate::Int,
11  const N: u32,
12  const ES: u32,
13  Int: crate::Int,
14>(int: FromInt) -> (Decoded<N, ES, Int>, Int) {
15  // If converting into a narrower type (`FromInt` → `Int`), we need to shift right, *before* we
16  // convert to the narrower type. Some bits will be lost in this conversion; we will accumulate
17  // them into `sticky`.
18  let shift_right = if const { Int::BITS >= FromInt::BITS } {0} else {FromInt::BITS - Int::BITS};
19
20  // If converting into a wider type (`FromInt` → `Int`), we need to shift left, *after* we convert
21  // to the wider type.
22  let shift_left = if const { Int::BITS <= FromInt::BITS } {0} else {Int::BITS - FromInt::BITS};
23
24  // To turn the `int` into a `frac` that starts with `0b01` or `0b10`, find the number of leading
25  // 0s or 1s, and shift left by that number of places minus one. To compensate, the `exp` has to
26  // be `FRAC_WIDTH` subtracted by the number of places we shifted. The `sticky` bits are the bits
27  // lost when shifting right.
28  //
29  // Examples:
30  //
31  //   value: 0b00010011 (= 19)
32  //    frac: 0b01001100
33  //     exp: +4 (= (8 - 2) frac width - 2 underflow)
34  //
35  //   value: 0b11111111 (= -1)
36  //    frac: 0b10000000
37  //     exp: -1 (= (8 - 2) frac width - 7 underflow)
38  //
39  // SAFETY: `int` is not 0 (we checked) and not MIN (function precondition)
40  let underflow = unsafe { int.leading_run_minus_one() };
41  let frac = const_as::<FromInt, Int>(int << underflow >> shift_right) << shift_left;
42  let exp = const_as::<i32, Int>(Decoded::<N, ES, FromInt>::FRAC_WIDTH.wrapping_sub(underflow) as i32);
43  let sticky = Int::from(int.mask_lsb(shift_right.saturating_sub(underflow)) != FromInt::ZERO);
44
45  (Decoded{frac, exp}, sticky)
46}
47
48#[inline]
49fn round_from_signed<
50  FromInt: crate::Int,
51  const N: u32,
52  const ES: u32,
53  Int: crate::Int,
54>(int: FromInt) -> Posit<N, ES, Int> {
55  // Handle 0 and MIN. Remember that according to the standard, MIN (i.e. bit pattern 0b1000…), is
56  // converted to NaR, and NaR is converted MIN.
57  if int == FromInt::ZERO { return Posit::ZERO }
58  if int == FromInt::MIN { return Posit::NAR }
59
60  // This piece of code is only necessary in really extreme cases, like converting i128::MAX into
61  // an 8-bit posit. But in those cases, we do need to guard against overflow on `exp`.
62  if const { FromInt::BITS as i128 > 1 << Decoded::<N, ES, Int>::FRAC_WIDTH } {
63    let limit = FromInt::ONE << (1 << Decoded::<N, ES, Int>::FRAC_WIDTH);
64    if int >=  limit { return Posit::MAX }
65    if int <= -limit { return Posit::MIN }
66  }
67
68  // SAFETY: `value` is not 0 or MIN
69  let (result, sticky) = unsafe { round_from_signed_kernel(int) };
70  // SAFETY: `frac` is not underflowing and `exp` cannot be greater than `FromInt::BITS`
71  unsafe { result.encode_regular_round(sticky) }
72}
73
74macro_rules! make_impl {
75  ($t:ty) => {
76    impl<
77      const N: u32,
78      const ES: u32,
79      Int: crate::Int,
80    > RoundFrom<$t> for Posit<N, ES, Int> {
81      #[doc = concat!("Convert an `", stringify!($t), "` into a `Posit`, [rounding according to the standard]:")]
82      #[doc = ""]
83      #[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).")]
84      #[doc = "  - Otherwise, the integer value is rounded (if necessary)."]
85      #[doc = ""]
86      #[doc = "[rounding according to the standard]: https://posithub.org/docs/posit_standard-2.pdf#subsection.6.4"]
87      fn round_from(value: $t) -> Self {
88        round_from_signed(value)
89      }
90    }
91  }
92}
93
94make_impl!{i8}
95make_impl!{i16}
96make_impl!{i32}
97make_impl!{i64}
98make_impl!{i128}
99
100// TODO unsigned
101
102#[cfg(test)]
103mod tests {
104  use super::*;
105  use malachite::rational::Rational;
106  use proptest::prelude::*;
107
108  /// Aux function: check that `int` is converted to a posit with the correct rounding.
109  fn is_correct_rounded<FromInt: crate::Int, const N: u32, const ES: u32, Int: crate::Int>(
110    int: FromInt,
111  ) -> bool
112  where
113    FromInt: Into<Rational> + RoundInto<Posit<N, ES, Int>>,
114    Rational: TryFrom<Posit<N, ES, Int>>,
115    <Rational as TryFrom<Posit<N, ES, Int>>>::Error: core::fmt::Debug
116  {
117    let posit: Posit<N, ES, Int> = int.round_into();
118    if int == FromInt::MIN {
119      posit == Posit::NAR
120    } else {
121      let exact: Rational = int.into();
122      super::rational::is_correct_rounded(exact, posit)
123    }
124  }
125
126  macro_rules! make_exhaustive {
127    ($t:ident) => {
128      mod $t {
129        use super::*;
130
131        #[test]
132        fn posit_10_0_exhaustive() {
133          for int in $t::MIN ..= $t::MAX {
134            assert!(is_correct_rounded::<$t, 10, 0, i16>(int), "{:?}", int)
135          }
136        }
137
138        #[test]
139        fn posit_10_1_exhaustive() {
140          for int in $t::MIN ..= $t::MAX {
141            assert!(is_correct_rounded::<$t, 10, 1, i16>(int), "{:?}", int)
142          }
143        }
144
145        #[test]
146        fn posit_10_2_exhaustive() {
147          for int in $t::MIN ..= $t::MAX {
148            assert!(is_correct_rounded::<$t, 10, 2, i16>(int), "{:?}", int)
149          }
150        }
151
152        #[test]
153        fn posit_10_3_exhaustive() {
154          for int in $t::MIN ..= $t::MAX {
155            assert!(is_correct_rounded::<$t, 10, 3, i16>(int), "{:?}", int)
156          }
157        }
158
159        #[test]
160        fn posit_8_0_exhaustive() {
161          for int in $t::MIN ..= $t::MAX {
162            assert!(is_correct_rounded::<$t, 8, 0, i8>(int), "{:?}", int)
163          }
164        }
165
166        #[test]
167        fn p8_exhaustive() {
168          for int in $t::MIN ..= $t::MAX {
169            assert!(is_correct_rounded::<$t, 8, 2, i8>(int), "{:?}", int)
170          }
171        }
172
173        #[test]
174        fn p16_exhaustive() {
175          for int in $t::MIN ..= $t::MAX {
176            assert!(is_correct_rounded::<$t, 16, 2, i16>(int), "{:?}", int)
177          }
178        }
179
180        #[test]
181        fn p32_exhaustive() {
182          for int in $t::MIN ..= $t::MAX {
183            assert!(is_correct_rounded::<$t, 32, 2, i32>(int), "{:?}", int)
184          }
185        }
186
187        #[test]
188        fn p64_exhaustive() {
189          for int in $t::MIN ..= $t::MAX {
190            assert!(is_correct_rounded::<$t, 64, 2, i64>(int), "{:?}", int)
191          }
192        }
193      }
194    }
195  }
196
197  macro_rules! make_proptest {
198    ($t:ident) => {
199      mod $t {
200        use super::*;
201
202        const PROPTEST_CASES: u32 = if cfg!(debug_assertions) {0x1_0000} else {0x80_0000};
203        proptest!{
204          #![proptest_config(ProptestConfig::with_cases(PROPTEST_CASES))]
205
206          #[test]
207          fn posit_10_0_proptest(int in any::<$t>()) {
208            assert!(is_correct_rounded::<$t, 10, 0, i16>(int), "{:?}", int)
209          }
210
211          #[test]
212          fn posit_10_1_proptest(int in any::<$t>()) {
213            assert!(is_correct_rounded::<$t, 10, 1, i16>(int), "{:?}", int)
214          }
215
216          #[test]
217          fn posit_10_2_proptest(int in any::<$t>()) {
218            assert!(is_correct_rounded::<$t, 10, 2, i16>(int), "{:?}", int)
219          }
220
221          #[test]
222          fn posit_10_3_proptest(int in any::<$t>()) {
223            assert!(is_correct_rounded::<$t, 10, 3, i16>(int), "{:?}", int)
224          }
225
226          #[test]
227          fn posit_8_0_proptest(int in any::<$t>()) {
228            assert!(is_correct_rounded::<$t, 8, 0, i8>(int), "{:?}", int)
229          }
230
231          #[test]
232          fn p8_proptest(int in any::<$t>()) {
233            assert!(is_correct_rounded::<$t, 8, 2, i8>(int), "{:?}", int)
234          }
235
236          #[test]
237          fn p16_proptest(int in any::<$t>()) {
238            assert!(is_correct_rounded::<$t, 16, 2, i16>(int), "{:?}", int)
239          }
240
241          #[test]
242          fn p32_proptest(int in any::<$t>()) {
243            assert!(is_correct_rounded::<$t, 32, 2, i32>(int), "{:?}", int)
244          }
245
246          #[test]
247          fn p64_proptest(int in any::<$t>()) {
248            assert!(is_correct_rounded::<$t, 64, 2, i64>(int), "{:?}", int)
249          }
250        }
251      }
252    }
253  }
254
255  make_exhaustive!{i8}
256  make_exhaustive!{i16}
257  make_proptest!{i32}
258  make_proptest!{i64}
259  make_proptest!{i128}
260}