fast_posit/posit/
basics.rs

1use super::*;
2use crate::underlying::const_as;
3
4impl<
5  const N: u32,
6  const ES: u32,
7  Int: crate::Int,
8> Posit<N, ES, Int> {
9  /// The size of this Posit type in bits.
10  ///
11  /// Note: this is the logical size, not necessarily the size of the underlying type.
12  pub const BITS: u32 = {
13    assert!(
14      N >= 3,
15      "A posit cannot have fewer than 3 bits",
16    );
17    assert!(
18      N <= Int::BITS,
19      "Cannot represent an n-bit Posit with an underlying Int machine type with fewer bits.",
20    );
21    N
22  };
23
24  /// The number of exponent bits.
25  pub const ES: u32 = {
26    assert!(
27      ES <= N,
28      "Cannot use a number of exponent bits ES higher than the number of total bits N",
29    );
30    // The value of ES isn't completely arbitrary. Very extreme values of ES would cause the maximum
31    // exponent to overflow the width of the `Int` type. Therefore, we check this at compile-time.
32    //
33    // The maximum exponent is 2 ^ Self::MAX_EXP. However, for guarding against overflow in all
34    // operations in the Posit standard, it's also really helpful to represent quantities up to
35    // (2 ^ Self::MAX_EXP) ^ 3 = 2 ^ (3 * Self::MAX_EXP). Rounding up to a clean number, we require
36    // the number 4 * Self::MAX_EXP (exclusive) to be representable in an `Int`.
37    //
38    // Self::MAX_EXP is (N-2) * 2^ES, so our requirement is 4 * (N-2) * 2^ES < 2 ^ Int::BITS, or
39    // N - 2 < 2 ^ (Int::BITS - ES - 2).
40    //
41    // To make Rust allow this to go in compile-time (const), we round (N-2) down to the nearest
42    // power of two and take the log, i.e. we check 2 ^ floor(log(N-2)) < 2 ^ (Int::BITS - ES - 2)
43    // or finally floor(log(N-2)) < Int::BITS - ES - 2.
44    assert!(
45      (N - 2).ilog2() + ES + 2 < Int::BITS,
46      "The chosen ES is too big for this combination of N and underlying Int type. Consider \
47      lowering the number of exponent bits, or choosing a bigger underlying Int if you really \
48      want this many.",
49    );
50    ES
51  };
52
53  /// When representing an `N`-bit posit using a machine type whose width is `M`, the leftmost
54  /// `N - M` bits are junk; they are always the same as the bit `N-1` (the function
55  /// [`Self::sign_extend`] maintains this invariant).
56  ///
57  /// In other words, the range of the `Int` in `Posit<N, ES, Int>` is from `-2^N` to `+2^N - 1`.
58  ///
59  /// Of course, if [`Self::BITS`] is exactly as wide as the underlying `Int::BITS` (as is vastly
60  /// the more common case), this is `0`.
61  pub(crate) const JUNK_BITS: u32 = Int::BITS - Self::BITS;
62
63  /// Take an `Int` and sign-extend from [`Self::BITS`] (logical width of posit) to `Int::BITS`.
64  #[inline]
65  pub(crate) /*const*/ fn sign_extend(x: Int) -> Int {
66    if const { Self::JUNK_BITS == 0 } {
67      x
68    } else {
69      (x << Self::JUNK_BITS) >> Self::JUNK_BITS
70    }
71  }
72
73  /// Construct a posit from its raw bit representation. Bits higher (more significant) than the
74  /// lowest `N` ([`Self::BITS`]) bits, if any, are ignored.
75  pub /*const*/ fn from_bits(x: Int) -> Self {
76    Self(Self::sign_extend(x))
77  }
78
79  /// As [`Self::from_bits`], but if `x` is not a result of a [`Self::to_bits`] call, then calling
80  /// this function is undefined behaviour.
81  pub const unsafe fn from_bits_unchecked(x: Int) -> Self {
82    Self(x)
83  }
84
85  /// Return the underlying bit representation of `self` as a machine int. Bits higher
86  /// (more significant) than the lowest `N` ([`Self::BITS`]) bits, if any, are set as equal to
87  /// the `N-1`th bit (i.e. sign-extended).
88  pub const fn to_bits(self) -> Int {
89    self.0
90  }
91
92  #[inline]
93  pub(crate) fn from_bits_unsigned(x: Int::Unsigned) -> Self {
94    Self::from_bits(Int::of_unsigned(x))
95  }
96
97  #[inline]
98  pub(crate) fn to_bits_unsigned(self) -> Int::Unsigned {
99    self.to_bits().as_unsigned()
100  }
101}
102
103impl<
104  const N: u32,
105  const ES: u32,
106  Int: crate::Int,
107> Decoded<N, ES, Int> {
108  /// The [Decoded::frac] field has the decimal point [Decoded::FRAC_WIDTH] bits from the right.
109  pub(crate) const FRAC_WIDTH: u32 = Int::BITS - 2;
110
111  /// The [Decoded::frac] field represents the fraction / mantissa of a posit as a fixed-point
112  /// number, with absolute value between 1 and 2.
113  ///
114  /// What this means is that an (integer) number `frac` represents the (rational) number `frac` /
115  /// `FRAC_DENOM`, where `FRAC_DENOM` is the bit pattern `0b01000...`. For instance
116  ///
117  ///   | `frac`        | rational value |
118  ///   | `0b01_000000` | +1             |
119  ///   | `0b01_100000` | +1.5           |
120  ///   | `0b01_010000` | +1.25          |
121  ///   | `0b10_010000` | -1.75          |
122  ///   | `0b10_110000` | -1.25          |
123  ///
124  /// and so on.
125  pub(crate) const FRAC_DENOM: Int = const_as(1i128 << Self::FRAC_WIDTH);
126
127  /// The size of this Posit type in bits.
128  ///
129  /// Note: this is the logical size, not necessarily the size of the underlying type.
130  pub const BITS: u32 = Posit::<N, ES, Int>::BITS;
131
132  /// The number of exponent bits.
133  pub const ES: u32 = Posit::<N, ES, Int>::ES;
134
135  /// Checks whether `self` is normalised, i.e. whether `self.frac` starts with `0b01` or `0b10`,
136  /// and `self.exp >> ES` starts with `0b00` or `0b11` (which is guaranteed if `ES > 0`).
137  pub(crate) fn is_normalised(self) -> bool {
138    let frac = self.frac >> Self::FRAC_WIDTH;
139    let exp = self.exp >> Self::FRAC_WIDTH;
140    (frac == Int::ONE || frac == !Int::ONE) && (ES > 0 || exp == Int::ZERO || exp == !Int::ZERO)
141  }
142}
143
144#[cfg(test)]
145mod tests {
146  use super::*;
147
148  #[test]
149  fn bits() {
150    assert_eq!(Posit::<8, 2, i8>::BITS, 8);
151    assert_eq!(Posit::<16, 2, i16>::BITS, 16);
152    assert_eq!(Posit::<32, 2, i32>::BITS, 32);
153    assert_eq!(Posit::<64, 2, i64>::BITS, 64);
154    assert_eq!(Posit::<128, 2, i128>::BITS, 128);
155
156    assert_eq!(Posit::<8, 0, i8>::BITS, 8);
157    assert_eq!(Posit::<16, 1, i16>::BITS, 16);
158    assert_eq!(Posit::<32, 2, i32>::BITS, 32);
159    assert_eq!(Posit::<64, 3, i64>::BITS, 64);
160    assert_eq!(Posit::<128, 4, i128>::BITS, 128);
161
162    assert_eq!(Posit::<6, 1, i8>::BITS, 6);
163    assert_eq!(Posit::<10, 2, i64>::BITS, 10);
164    assert_eq!(Posit::<32, 2, i64>::BITS, 32);
165  }
166
167  #[test]
168  fn es() {
169    assert_eq!(Posit::<8, 2, i8>::ES, 2);
170    assert_eq!(Posit::<16, 2, i16>::ES, 2);
171    assert_eq!(Posit::<32, 2, i32>::ES, 2);
172    assert_eq!(Posit::<64, 2, i64>::ES, 2);
173    assert_eq!(Posit::<128, 2, i128>::ES, 2);
174
175    assert_eq!(Posit::<8, 0, i8>::ES, 0);
176    assert_eq!(Posit::<16, 1, i16>::ES, 1);
177    assert_eq!(Posit::<32, 2, i32>::ES, 2);
178    assert_eq!(Posit::<64, 3, i64>::ES, 3);
179    assert_eq!(Posit::<128, 4, i128>::ES, 4);
180
181    assert_eq!(Posit::<6, 1, i8>::ES, 1);
182    assert_eq!(Posit::<10, 2, i64>::ES, 2);
183    assert_eq!(Posit::<32, 2, i64>::ES, 2);
184  }
185
186  #[test]
187  fn es_max() {
188    assert_eq!(Posit::<8, 3, i8>::ES, 3);
189    assert_eq!(Posit::<16, 10, i16>::ES, 10);
190    assert_eq!(Posit::<32, 25, i32>::ES, 25);
191    assert_eq!(Posit::<64, 56, i64>::ES, 56);
192    assert_eq!(Posit::<128, 119, i128>::ES, 119);
193
194    assert_eq!(Posit::<8, 8, i16>::ES, 8);
195    assert_eq!(Posit::<16, 16, i32>::ES, 16);
196    assert_eq!(Posit::<32, 32, i64>::ES, 32);
197    assert_eq!(Posit::<64, 64, i128>::ES, 64);
198  }
199
200  #[test]
201  fn from_bits() {
202    fn assert_roundtrip<const N: u32, const ES: u32, Int: crate::Int>(a: Int::Unsigned, b: Int::Unsigned) {
203      use super::*;
204      assert_eq!(
205        Posit::<N, ES, Int>::from_bits_unsigned(a).to_bits(),
206        Int::of_unsigned(b),
207      )
208    }
209
210    // N = width of type
211    assert_roundtrip::<16, 2, i16>(
212      0b0000_0101_0011_1010,
213      0b0000_0101_0011_1010,
214    );
215    assert_roundtrip::<16, 2, i16>(
216      0b1111_0101_0011_1010,
217      0b1111_0101_0011_1010,
218    );
219    assert_roundtrip::<16, 2, i16>(
220      0b0101_0011_0011_1010,
221      0b0101_0011_0011_1010,
222    );
223
224    // N < width of type (needs sign-extension to bits ≥ 10)
225    assert_roundtrip::<10, 2, i16>(
226      0b000001_01_0011_1010,
227      0b000000_01_0011_1010,
228    );
229    assert_roundtrip::<10, 2, i16>(
230      0b111101_01_0011_1010,
231      0b000000_01_0011_1010,
232    );
233    assert_roundtrip::<10, 2, i16>(
234      0b010100_11_0011_1010,
235      0b111111_11_0011_1010,
236    );
237  }
238}
239
240mod tests_compile_fail {
241  /// ```compile_fail
242  /// use fast_posit::Posit;
243  /// pub fn foo() -> u32 { Posit::<2, 0, i8>::BITS }
244  /// ```
245  #[allow(dead_code)]
246  fn bits_fail_8_few() {}
247
248  /// ```compile_fail
249  /// use fast_posit::Posit;
250  /// pub fn foo() -> u32 { Posit::<2, 1, i16>::BITS }
251  /// ```
252  #[allow(dead_code)]
253  fn bits_fail_16_few() {}
254
255  /// ```compile_fail
256  /// use fast_posit::Posit;
257  /// pub fn foo() -> u32 { Posit::<2, 2, i32>::BITS }
258  /// ```
259  #[allow(dead_code)]
260  fn bits_fail_32_few() {}
261
262  /// ```compile_fail
263  /// use fast_posit::Posit;
264  /// pub fn foo() -> u32 { Posit::<2, 3, i64>::BITS }
265  /// ```
266  #[allow(dead_code)]
267  fn bits_fail_64_few() {}
268
269  /// ```compile_fail
270  /// use fast_posit::Posit;
271  /// pub fn foo() -> u32 { Posit::<2, 4, i128>::BITS }
272  /// ```
273  #[allow(dead_code)]
274  fn bits_fail_128_few() {}
275
276  //
277
278  /// ```compile_fail
279  /// use fast_posit::Posit;
280  /// pub fn foo() -> u32 { Posit::<9, 0, i8>::BITS }
281  /// ```
282  #[allow(dead_code)]
283  fn bits_fail_8_many() {}
284
285  /// ```compile_fail
286  /// use fast_posit::Posit;
287  /// pub fn foo() -> u32 { Posit::<17, 1, i16>::BITS }
288  /// ```
289  #[allow(dead_code)]
290  fn bits_fail_16_many() {}
291
292  /// ```compile_fail
293  /// use fast_posit::Posit;
294  /// pub fn foo() -> u32 { Posit::<33, 2, i32>::BITS }
295  /// ```
296  #[allow(dead_code)]
297  fn bits_fail_32_many() {}
298
299  /// ```compile_fail
300  /// use fast_posit::Posit;
301  /// pub fn foo() -> u32 { Posit::<65, 3, i64>::BITS }
302  /// ```
303  #[allow(dead_code)]
304  fn bits_fail_64_many() {}
305
306  /// ```compile_fail
307  /// use fast_posit::Posit;
308  /// pub fn foo() -> u32 { Posit::<129, 4, i128>::BITS }
309  /// ```
310  #[allow(dead_code)]
311  fn bits_fail_128_many() {}
312
313  //
314
315  /// ```compile_fail
316  /// use fast_posit::Posit;
317  /// pub fn foo() -> u32 { Posit::<8, 4, i8>::ES }
318  /// ```
319  #[allow(dead_code)]
320  fn es_fail_8_many() {}
321
322  /// ```compile_fail
323  /// use fast_posit::Posit;
324  /// pub fn foo() -> u32 { Posit::<16, 11, i16>::ES }
325  /// ```
326  #[allow(dead_code)]
327  fn es_fail_16_many() {}
328
329  /// ```compile_fail
330  /// use fast_posit::Posit;
331  /// pub fn foo() -> u32 { Posit::<32, 26, i32>::ES }
332  /// ```
333  #[allow(dead_code)]
334  fn es_fail_32_many() {}
335
336  /// ```compile_fail
337  /// use fast_posit::Posit;
338  /// pub fn foo() -> u32 { Posit::<64, 57, i64>::ES }
339  /// ```
340  #[allow(dead_code)]
341  fn es_fail_64_many() {}
342
343  /// ```compile_fail
344  /// use fast_posit::Posit;
345  /// pub fn foo() -> u32 { Posit::<128, 120, i128>::ES }
346  /// ```
347  #[allow(dead_code)]
348  fn es_fail_128_many() {}
349
350  //
351
352  /// ```compile_fail
353  /// use fast_posit::Posit;
354  /// pub fn foo() -> u32 { Posit::<8, 9, i16>::ES }
355  /// ```
356  #[allow(dead_code)]
357  fn es_fail_8_larger() {}
358
359  /// ```compile_fail
360  /// use fast_posit::Posit;
361  /// pub fn foo() -> u32 { Posit::<16, 17, i32>::ES }
362  /// ```
363  #[allow(dead_code)]
364  fn es_fail_16_larger() {}
365
366  /// ```compile_fail
367  /// use fast_posit::Posit;
368  /// pub fn foo() -> u32 { Posit::<32, 33, i64>::ES }
369  /// ```
370  #[allow(dead_code)]
371  fn es_fail_32_larger() {}
372
373  /// ```compile_fail
374  /// use fast_posit::Posit;
375  /// pub fn foo() -> u32 { Posit::<64, 65, i128>::ES }
376  /// ```
377  #[allow(dead_code)]
378  fn es_fail_64_larger() {}
379}