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