Skip to main content

fast_posit/posit/convert/
posit.rs

1use super::*;
2use crate::underlying::const_as;
3
4impl<
5  const N1: u32,
6  const ES1: u32,
7  Int1: crate::Int,
8  const RS1: u32,
9> Posit<N1, ES1, Int1, RS1> {
10  /// Very fast, the `ES` and `RS` have to be the same.
11  fn round_from_fast<
12    const N2: u32,
13    const ES2: u32,
14    Int2: crate::Int,
15    const RS2: u32,
16  >(self) -> Posit<N2, ES2, Int2, RS2> {
17    if const { ES1 != ES2 || RS1 != RS2 } { unimplemented!() }
18    if const { N1 <= N2 } {
19      // Converting to a wider posit is even simpler: no rounding, just pad with zeroes.
20      let bits = const_as::<Int1, Int2>(self.to_bits()) << (N2 - N1);
21      // SAFETY: `bits` is a valid bit pattern for `N1`, so `bits << (N2 - N1)` must also be a
22      // valid bit pattern for `N2` > `N1`.
23      unsafe { Posit::from_bits_unchecked(bits) }
24    } else {
25      // Converting to a narrower posit is somewhat tricker: we may need to round, depending on
26      // which bits were lost. The chopped off bits except the last (leftmost bit among those
27      // chopped off) are stored in `sticky`, the last chopped bit is stored in the lsb of `round`.
28      let sticky: Int2 = Int2::from(self.to_bits().mask_lsb(N1 - N2 - 1) != Int1::ZERO);
29      let round: Int2 = const_as(self.to_bits() >> (N1 - N2 - 1));
30      // Recall the rounding rule: "round to nearest, if tied round to even bit pattern".
31      //
32      //   bits | round | sticky | result
33      //   ...x | 0     | x      | round down (+0)
34      //   ...0 | 1     | 0      | round down to even (+0)
35      //   ...1 | 1     | 0      | round up to even (+1)
36      //   ...x | 1     | 1      | round up (+1)
37      //
38      // This results in the following boolean formula for determining whether to round up:
39      //
40      //   round & (bits | sticky)
41      //
42      let bits = const_as::<Int1, Int2>(self.to_bits() >> (N1 - N2));
43      let round_up = round & (bits | sticky) & Int2::ONE;
44      // But then again, we need to be careful of the following:
45      //
46      // - Not to round up from `0b1111…` to `0b0000…` or from `0b0111…` to `0b1000…`.
47      // - Not to chop bits so that `0b000…1…` truncates to `0b0000…` and `0b100…1…` to `0b1000…`.
48      //
49      // Therefore: if the result is 0 or NaR, we have to "round up" if `sticky != 0`, and
50      // to "round down" if there was a sign change in the bits.
51      let is_special = Posit::<N2, ES2, Int2, RS2>::from_bits(bits).is_special();
52      let round_up = round_up | ((round | sticky) & Int2::from(is_special));
53      let bits_rounded = Posit::<N2, ES2, Int2, RS2>::sign_extend(bits.wrapping_add(round_up));
54      let overflow = !(bits_rounded ^ bits).is_positive();
55      Posit::from_bits(bits_rounded.wrapping_sub(Int2::from(overflow)))
56    }
57  }
58
59  /// Slower, the `ES` and `RS` may be different.
60  fn round_from_slow<
61    const N2: u32,
62    const ES2: u32,
63    Int2: crate::Int,
64    const RS2: u32,
65  >(self) -> Posit<N2, ES2, Int2, RS2> {
66    if self == Self::ZERO {
67      Posit::ZERO
68    } else if self == Self::NAR {
69      Posit::NAR
70    } else {
71      // SAFETY: `self` is not 0 or NaR
72      let decoded = unsafe { self.decode_regular() };
73      // Cast `frac` and `exp` to the target `Int2`; `frac` must also be shifted.
74      let shift_right = if const { Int1::BITS <= Int2::BITS } {0} else {Int1::BITS - Int2::BITS};
75      let shift_left = if const { Int1::BITS >= Int2::BITS } {0} else {Int2::BITS - Int1::BITS};
76      let frac = const_as::<Int1, Int2>(decoded.frac >> shift_right) << shift_left;
77      let exp = const_as::<Int1, Int2>(decoded.exp);
78      // Lost bits, if any, must be collected into `sticky`.
79      let sticky = Int2::from(decoded.frac.mask_lsb(shift_right) != Int1::ZERO);
80      // Corner-case: if Self::MAX_EXP may overflow the destination type `Int2`, we must check
81      // whether the exponent *does* overflow the destination type.
82      if Int1::BITS > Int2::BITS
83      && Self::MAX_EXP >= const_as(Decoded::<N2, ES2, N2, Int2>::FRAC_DENOM)
84      && decoded.exp.abs() >= const_as(Decoded::<N2, ES2, N2, Int2>::FRAC_DENOM) {
85        // TODO remove branch? It's exceedingly rare anyways
86        let exp = Decoded::<N2, ES2, N2, Int2>::FRAC_DENOM - Int2::ONE;
87        let exp = if decoded.exp.is_positive() {exp} else {-exp};
88        // SAFETY: `decoded.frac` starts with `0b01` or `0b10`, so `decoded.frac >> shift_right <<
89        // shift_left` also does; `exp` is the max/min exponent.
90        return unsafe { Decoded{frac, exp}.encode_regular() }
91      }
92      // SAFETY: `decoded.frac` starts with `0b01` or `0b10`, so `decoded.frac >> shift_right <<
93      // shift_left` also does; `exp` is in bounds (if there is a risk of overflow, it's handled
94      // above).
95      unsafe { Decoded{frac, exp}.encode_regular_round(sticky) }
96    }
97  }
98}
99
100// Cannot impl RoundFrom trait because it conflicts with blanket impl... Has to be a function.
101/*impl<
102  const N1: u32,
103  const ES1: u32,
104  Int1: crate::Int,
105  const N2: u32,
106  const ES2: u32,
107  Int2: crate::Int,
108> RoundFrom<Posit<N1, ES1, Int1>> for Posit<N2, ES2, Int2>*/
109
110impl<
111  const N: u32,
112  const ES: u32,
113  Int: crate::Int,
114  const RS: u32,
115> Posit<N, ES, Int, RS> {
116  /// Convert a posit into a different one, [rounding according to the standard].
117  ///
118  /// If the source and target types have the same ES (i.e. `ES == ES2`), such as is the case with
119  /// the standard types, this is especially fast. This enables easy and seamless mixed-precision
120  /// arithmetic.
121  ///
122  /// [rounding according to the standard]: https://posithub.org/docs/posit_standard-2.pdf#subsection.6.1
123  ///
124  /// # Examples
125  ///
126  /// ```
127  /// # use fast_posit::{p8, p64, RoundFrom, RoundInto};
128  /// let pi: p64 = core::f64::consts::PI.round_into();
129  /// let two: p8 = 2.round_into();
130  /// let tau: p64 = pi * two.convert();
131  /// assert_eq!(tau, core::f64::consts::TAU.round_into())
132  /// ```
133  pub fn convert<
134    const N2: u32,
135    const ES2: u32,
136    Int2: crate::Int,
137    const RS2: u32,
138  >(self) -> Posit<N2, ES2, Int2, RS2> {
139    if const { ES == ES2 && RS == RS2 } {
140      // If the two ES and RS values are the same, converting posit values is incredibly simple:
141      // just append 0s or truncate the bit pattern.
142      self.round_from_fast()
143    } else {
144      // Otherwise, we need to decode and re-encode.
145      self.round_from_slow()
146    }
147  }
148}
149
150#[cfg(test)]
151#[allow(non_camel_case_types)]
152mod tests {
153  use super::*;
154  use malachite::rational::Rational;
155  use proptest::prelude::*;
156
157  macro_rules! test_exhaustive {
158    ($name: ident, $src:ty, $dst:ty) => {
159      #[test]
160      fn $name() {
161        for src in <$src>::cases_exhaustive_all() {
162          let dst: $dst = src.convert();
163          assert!(super::rational::try_is_correct_rounded(Rational::try_from(src), dst))
164        }
165      }
166    };
167  }
168
169  macro_rules! test_proptest {
170    ($name: ident, $src:ty, $dst:ty) => {
171      proptest!{
172        #![proptest_config(ProptestConfig::with_cases(crate::PROPTEST_CASES))]
173        #[test]
174        fn $name(src in <$src>::cases_proptest_all()) {
175          let dst: $dst = src.convert();
176          assert!(super::rational::try_is_correct_rounded(Rational::try_from(src), dst))
177        }
178      }
179    };
180  }
181
182  macro_rules! suite_exhaustive {
183    ($name_src:ident, $src:ty) => {
184      mod $name_src {
185        use super::*;
186        test_exhaustive!{posit_10_0_exhaustive, $src, Posit<10, 0, i16>}
187        test_exhaustive!{posit_10_1_exhaustive, $src, Posit<10, 1, i16>}
188        test_exhaustive!{posit_10_2_exhaustive, $src, Posit<10, 2, i16>}
189        test_exhaustive!{posit_10_3_exhaustive, $src, Posit<10, 3, i16>}
190        test_exhaustive!{posit_8_0_exhaustive,  $src, Posit<8, 0, i8>}
191        test_exhaustive!{posit_20_4_exhaustive, $src, Posit<20, 4, i32>}
192        test_exhaustive!{p8_exhaustive,         $src, crate::p8}
193        test_exhaustive!{p16_exhaustive,        $src, crate::p16}
194        test_exhaustive!{p32_exhaustive,        $src, crate::p32}
195        test_exhaustive!{p64_exhaustive,        $src, crate::p64}
196        test_exhaustive!{posit_3_0_exhaustive,  $src, Posit<3, 0, i8>}
197        test_exhaustive!{posit_4_0_exhaustive,  $src, Posit<4, 0, i8>}
198        test_exhaustive!{posit_4_1_exhaustive,  $src, Posit<4, 1, i8>}
199        test_exhaustive!{bposit_8_3_6_exhaustive,  $src, Posit::<8, 3, i8, 6>}
200        test_exhaustive!{bposit_16_5_6_exhaustive, $src, Posit::<16, 5, i16, 6>}
201        test_exhaustive!{bposit_32_5_6_exhaustive, $src, Posit::<32, 5, i32, 6>}
202        test_exhaustive!{bposit_64_5_6_exhaustive, $src, Posit::<64, 5, i64, 6>}
203        test_exhaustive!{bposit_10_2_6_exhaustive, $src, Posit::<10, 2, i16, 6>}
204        test_exhaustive!{bposit_10_2_7_exhaustive, $src, Posit::<10, 2, i16, 7>}
205        test_exhaustive!{bposit_10_2_8_exhaustive, $src, Posit::<10, 2, i16, 8>}
206        test_exhaustive!{bposit_10_2_9_exhaustive, $src, Posit::<10, 2, i16, 9>}
207      }
208    };
209  }
210
211  macro_rules! suite_proptest {
212    ($name_src:ident, $src:ty) => {
213      mod $name_src {
214        use super::*;
215        test_proptest!{posit_10_0_proptest, $src, Posit<10, 0, i16>}
216        test_proptest!{posit_10_1_proptest, $src, Posit<10, 1, i16>}
217        test_proptest!{posit_10_2_proptest, $src, Posit<10, 2, i16>}
218        test_proptest!{posit_10_3_proptest, $src, Posit<10, 3, i16>}
219        test_proptest!{posit_8_0_proptest,  $src, Posit<8, 0, i8>}
220        test_proptest!{posit_20_4_proptest, $src, Posit<20, 4, i32>}
221        test_proptest!{p8_proptest,         $src, crate::p8}
222        test_proptest!{p16_proptest,        $src, crate::p16}
223        test_proptest!{p32_proptest,        $src, crate::p32}
224        test_proptest!{p64_proptest,        $src, crate::p64}
225        test_proptest!{posit_3_0_proptest,  $src, Posit<3, 0, i8>}
226        test_proptest!{posit_4_0_proptest,  $src, Posit<4, 0, i8>}
227        test_proptest!{posit_4_1_proptest,  $src, Posit<4, 1, i8>}
228        test_proptest!{bposit_8_3_6_proptest,  $src, Posit::<8, 3, i8, 6>}
229        test_proptest!{bposit_16_5_6_proptest, $src, Posit::<16, 5, i16, 6>}
230        test_proptest!{bposit_32_5_6_proptest, $src, Posit::<32, 5, i32, 6>}
231        test_proptest!{bposit_64_5_6_proptest, $src, Posit::<64, 5, i64, 6>}
232        test_proptest!{bposit_10_2_6_proptest, $src, Posit::<10, 2, i16, 6>}
233        test_proptest!{bposit_10_2_7_proptest, $src, Posit::<10, 2, i16, 7>}
234        test_proptest!{bposit_10_2_8_proptest, $src, Posit::<10, 2, i16, 8>}
235        test_proptest!{bposit_10_2_9_proptest, $src, Posit::<10, 2, i16, 9>}
236      }
237    };
238  }
239
240  suite_exhaustive!{posit_10_0, Posit<10, 0, i16>}
241  suite_exhaustive!{posit_10_1, Posit<10, 1, i16>}
242  suite_exhaustive!{posit_10_2, Posit<10, 2, i16>}
243  suite_exhaustive!{posit_10_3, Posit<10, 3, i16>}
244
245  suite_exhaustive!{posit_8_0, Posit<8, 0, i8>}
246  suite_exhaustive!{posit_20_4, Posit<20, 4, i32>}
247
248  suite_exhaustive!{p8, crate::p8}
249  suite_exhaustive!{p16, crate::p16}
250  suite_proptest!{p32, crate::p32}
251  suite_proptest!{p64, crate::p64}
252
253  suite_exhaustive!{posit_3_0, Posit<3, 0, i8>}
254  suite_exhaustive!{posit_4_0, Posit<4, 0, i8>}
255  suite_exhaustive!{posit_4_1, Posit<4, 1, i8>}
256
257  suite_exhaustive!{bposit_8_3_6_exhaustive, Posit::<8, 3, i8, 6>}
258  suite_exhaustive!{bposit_16_5_6_exhaustive, Posit::<16, 5, i16, 6>}
259  suite_proptest!{bposit_32_5_6_proptest, Posit::<32, 5, i32, 6>}
260  suite_proptest!{bposit_64_5_6_proptest, Posit::<64, 5, i64, 6>}
261
262  suite_exhaustive!{bposit_10_2_6_exhaustive, Posit::<10, 2, i16, 6>}
263  suite_exhaustive!{bposit_10_2_7_exhaustive, Posit::<10, 2, i16, 7>}
264  suite_exhaustive!{bposit_10_2_8_exhaustive, Posit::<10, 2, i16, 8>}
265  suite_exhaustive!{bposit_10_2_9_exhaustive, Posit::<10, 2, i16, 9>}
266}