Skip to main content

bnb/
int.rs

1//! Arbitrary-width unsigned integers (`u1`..`u127`) — the substrate for sub-byte
2//! bitfield fields. Replaces the `arbitrary-int` dependency.
3//!
4//! [`UInt<T, N>`] wraps the smallest primitive `T` that can hold `N` bits and
5//! enforces the `N`-bit range. The type aliases ([`u1`], [`u5`], …) pick the
6//! right backing for you; the native widths (`u8`, `u16`, `u32`, `u64`, `u128`)
7//! are just the standard library's and need no wrapper.
8//!
9//! ```
10//! use bnb::u5;
11//!
12//! let x = u5::new(17);          // panics if > 31
13//! assert_eq!(x.value(), 17);
14//! assert_eq!(u5::MAX.value(), 31);
15//! assert!(u5::try_new(32).is_err());
16//! ```
17
18use core::fmt;
19
20use crate::error::WidthError;
21use crate::field::Bits;
22
23/// An unsigned integer constrained to `N` bits, backed by primitive `T`.
24///
25/// Use the aliases ([`u5`], [`u13`], …) rather than naming `T` directly. The
26/// value is always in `0..=MAX` (`MAX == 2^N - 1`); construct with
27/// [`new`](UInt::new) (panicking) or [`try_new`](UInt::try_new) (checked), and
28/// read it back with [`value`](UInt::value).
29///
30/// # Examples
31///
32/// ```
33/// use bnb::u5;
34///
35/// let a = u5::new(17);              // checked; panics if > 31
36/// assert_eq!(a.value(), 17);
37/// assert!(u5::try_new(32).is_err()); // out of range -> Err, no panic
38/// assert_eq!(u5::from_raw(0xFF).value(), 31); // masks to the low 5 bits
39/// assert_eq!(u5::MAX.value(), 31);
40/// assert_eq!(u5::MIN.value(), 0);
41/// ```
42#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
43pub struct UInt<T, const N: usize> {
44    value: T,
45}
46
47macro_rules! impl_uint {
48    ($($t:ty),* $(,)?) => {
49        $(
50            impl<const N: usize> UInt<$t, N> {
51                /// The number of bits.
52                pub const BITS: u32 = <Self as Bits>::BITS;
53
54                /// A mask with the low `N` bits set.
55                pub const MASK: $t = if N >= <$t>::BITS as usize {
56                    <$t>::MAX
57                } else {
58                    ((1 as $t) << N) - 1
59                };
60
61                /// The largest representable value (`2^N - 1`).
62                pub const MAX: Self = Self { value: Self::MASK };
63
64                /// The zero value.
65                pub const MIN: Self = Self { value: 0 };
66
67                /// Creates a value, panicking if it does not fit in `N` bits.
68                ///
69                /// # Panics
70                /// Panics if `value > MAX`.
71                #[inline]
72                pub const fn new(value: $t) -> Self {
73                    assert!(value <= Self::MASK, "value out of range for this width");
74                    Self { value }
75                }
76
77                /// Creates a value, or [`WidthError::ValueTooLarge`] if it does not
78                /// fit in `N` bits.
79                #[inline]
80                pub fn try_new(value: $t) -> core::result::Result<Self, WidthError> {
81                    if value <= Self::MASK {
82                        Ok(Self { value })
83                    } else {
84                        Err(WidthError::ValueTooLarge {
85                            value: value as u128,
86                            bits: N as u32,
87                        })
88                    }
89                }
90
91                /// Creates a value from the low `N` bits of `value`, discarding
92                /// any higher bits (the unchecked, masking constructor).
93                #[inline]
94                pub const fn from_raw(value: $t) -> Self {
95                    Self { value: value & Self::MASK }
96                }
97
98                /// The underlying value.
99                #[inline]
100                pub const fn value(self) -> $t {
101                    self.value
102                }
103
104                // Const-dispatch seam for macro-generated accessors: the
105                // `Bits::from_bits`/`into_bits` contract as inherent `const fn`s,
106                // callable where trait methods cannot be (const trait impls are
107                // unstable). The `Bits` impl delegates here, so the two can't drift.
108                #[doc(hidden)]
109                #[inline]
110                pub const fn __bnb_from_bits(raw: u128) -> Self {
111                    Self::from_raw(raw as $t)
112                }
113
114                #[doc(hidden)]
115                #[inline]
116                pub const fn __bnb_into_bits(self) -> u128 {
117                    self.value as u128
118                }
119            }
120
121            impl<const N: usize> Default for UInt<$t, N> {
122                #[inline]
123                fn default() -> Self {
124                    Self { value: 0 }
125                }
126            }
127
128            impl<const N: usize> Bits for UInt<$t, N> {
129                const BITS: u32 = N as u32;
130
131                #[inline]
132                fn into_bits(self) -> u128 {
133                    self.__bnb_into_bits()
134                }
135
136                #[inline]
137                fn from_bits(raw: u128) -> Self {
138                    Self::__bnb_from_bits(raw)
139                }
140            }
141
142            impl<const N: usize> fmt::Debug for UInt<$t, N> {
143                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144                    write!(f, "u{N}({})", self.value)
145                }
146            }
147
148            impl<const N: usize> fmt::Display for UInt<$t, N> {
149                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150                    fmt::Display::fmt(&self.value, f)
151                }
152            }
153
154            impl<const N: usize> From<UInt<$t, N>> for $t {
155                #[inline]
156                fn from(v: UInt<$t, N>) -> $t {
157                    v.value
158                }
159            }
160
161            impl<const N: usize> TryFrom<$t> for UInt<$t, N> {
162                type Error = WidthError;
163                #[inline]
164                fn try_from(value: $t) -> core::result::Result<Self, WidthError> {
165                    Self::try_new(value)
166                }
167            }
168        )*
169    };
170}
171
172impl_uint!(u8, u16, u32, u64, u128);
173
174// `macro_rules!` cannot build identifiers like `u5` from a literal, so the
175// aliases are written out explicitly below (grouped by backing width). This is
176// verbose but keeps the crate free of a `paste`-style dependency.
177//
178// The lower-case `uN` names intentionally mirror the standard `u8`/`u16` style
179// (and `arbitrary-int`), so the camel-case lint is silenced here.
180/// Type aliases `u1`..`u127`: each `uN` is an `N`-bit unsigned integer backed by
181/// the smallest sufficient primitive (`UInt<u8, 5>` for `u5`, etc.). The native
182/// widths (`u8`/`u16`/`u32`/`u64`/`u128`) are the standard library's.
183#[rustfmt::skip]
184#[allow(non_camel_case_types, missing_docs)] // the names are self-documenting
185mod aliases {
186    use super::UInt;
187
188    // 1..=7 bits fit in a u8.
189    pub type u1 = UInt<u8, 1>;   pub type u2 = UInt<u8, 2>;   pub type u3 = UInt<u8, 3>;
190    pub type u4 = UInt<u8, 4>;   pub type u5 = UInt<u8, 5>;   pub type u6 = UInt<u8, 6>;
191    pub type u7 = UInt<u8, 7>;
192
193    // 9..=15 bits fit in a u16.
194    pub type u9  = UInt<u16, 9>;  pub type u10 = UInt<u16, 10>; pub type u11 = UInt<u16, 11>;
195    pub type u12 = UInt<u16, 12>; pub type u13 = UInt<u16, 13>; pub type u14 = UInt<u16, 14>;
196    pub type u15 = UInt<u16, 15>;
197
198    // 17..=31 bits fit in a u32.
199    pub type u17 = UInt<u32, 17>; pub type u18 = UInt<u32, 18>; pub type u19 = UInt<u32, 19>;
200    pub type u20 = UInt<u32, 20>; pub type u21 = UInt<u32, 21>; pub type u22 = UInt<u32, 22>;
201    pub type u23 = UInt<u32, 23>; pub type u24 = UInt<u32, 24>; pub type u25 = UInt<u32, 25>;
202    pub type u26 = UInt<u32, 26>; pub type u27 = UInt<u32, 27>; pub type u28 = UInt<u32, 28>;
203    pub type u29 = UInt<u32, 29>; pub type u30 = UInt<u32, 30>; pub type u31 = UInt<u32, 31>;
204
205    // 33..=63 bits fit in a u64.
206    pub type u33 = UInt<u64, 33>; pub type u34 = UInt<u64, 34>; pub type u35 = UInt<u64, 35>;
207    pub type u36 = UInt<u64, 36>; pub type u37 = UInt<u64, 37>; pub type u38 = UInt<u64, 38>;
208    pub type u39 = UInt<u64, 39>; pub type u40 = UInt<u64, 40>; pub type u41 = UInt<u64, 41>;
209    pub type u42 = UInt<u64, 42>; pub type u43 = UInt<u64, 43>; pub type u44 = UInt<u64, 44>;
210    pub type u45 = UInt<u64, 45>; pub type u46 = UInt<u64, 46>; pub type u47 = UInt<u64, 47>;
211    pub type u48 = UInt<u64, 48>; pub type u49 = UInt<u64, 49>; pub type u50 = UInt<u64, 50>;
212    pub type u51 = UInt<u64, 51>; pub type u52 = UInt<u64, 52>; pub type u53 = UInt<u64, 53>;
213    pub type u54 = UInt<u64, 54>; pub type u55 = UInt<u64, 55>; pub type u56 = UInt<u64, 56>;
214    pub type u57 = UInt<u64, 57>; pub type u58 = UInt<u64, 58>; pub type u59 = UInt<u64, 59>;
215    pub type u60 = UInt<u64, 60>; pub type u61 = UInt<u64, 61>; pub type u62 = UInt<u64, 62>;
216    pub type u63 = UInt<u64, 63>;
217
218    // 65..=127 bits fit in a u128.
219    pub type u65 = UInt<u128, 65>;   pub type u66 = UInt<u128, 66>;   pub type u67 = UInt<u128, 67>;
220    pub type u68 = UInt<u128, 68>;   pub type u69 = UInt<u128, 69>;   pub type u70 = UInt<u128, 70>;
221    pub type u71 = UInt<u128, 71>;   pub type u72 = UInt<u128, 72>;   pub type u73 = UInt<u128, 73>;
222    pub type u74 = UInt<u128, 74>;   pub type u75 = UInt<u128, 75>;   pub type u76 = UInt<u128, 76>;
223    pub type u77 = UInt<u128, 77>;   pub type u78 = UInt<u128, 78>;   pub type u79 = UInt<u128, 79>;
224    pub type u80 = UInt<u128, 80>;   pub type u81 = UInt<u128, 81>;   pub type u82 = UInt<u128, 82>;
225    pub type u83 = UInt<u128, 83>;   pub type u84 = UInt<u128, 84>;   pub type u85 = UInt<u128, 85>;
226    pub type u86 = UInt<u128, 86>;   pub type u87 = UInt<u128, 87>;   pub type u88 = UInt<u128, 88>;
227    pub type u89 = UInt<u128, 89>;   pub type u90 = UInt<u128, 90>;   pub type u91 = UInt<u128, 91>;
228    pub type u92 = UInt<u128, 92>;   pub type u93 = UInt<u128, 93>;   pub type u94 = UInt<u128, 94>;
229    pub type u95 = UInt<u128, 95>;   pub type u96 = UInt<u128, 96>;   pub type u97 = UInt<u128, 97>;
230    pub type u98 = UInt<u128, 98>;   pub type u99 = UInt<u128, 99>;   pub type u100 = UInt<u128, 100>;
231    pub type u101 = UInt<u128, 101>; pub type u102 = UInt<u128, 102>; pub type u103 = UInt<u128, 103>;
232    pub type u104 = UInt<u128, 104>; pub type u105 = UInt<u128, 105>; pub type u106 = UInt<u128, 106>;
233    pub type u107 = UInt<u128, 107>; pub type u108 = UInt<u128, 108>; pub type u109 = UInt<u128, 109>;
234    pub type u110 = UInt<u128, 110>; pub type u111 = UInt<u128, 111>; pub type u112 = UInt<u128, 112>;
235    pub type u113 = UInt<u128, 113>; pub type u114 = UInt<u128, 114>; pub type u115 = UInt<u128, 115>;
236    pub type u116 = UInt<u128, 116>; pub type u117 = UInt<u128, 117>; pub type u118 = UInt<u128, 118>;
237    pub type u119 = UInt<u128, 119>; pub type u120 = UInt<u128, 120>; pub type u121 = UInt<u128, 121>;
238    pub type u122 = UInt<u128, 122>; pub type u123 = UInt<u128, 123>; pub type u124 = UInt<u128, 124>;
239    pub type u125 = UInt<u128, 125>; pub type u126 = UInt<u128, 126>; pub type u127 = UInt<u128, 127>;
240}
241
242pub use aliases::*;
243
244#[cfg(test)]
245mod unit {
246    use super::*;
247
248    #[test]
249    fn construction_and_range() {
250        assert_eq!(u5::MAX.value(), 31);
251        assert_eq!(u5::MIN.value(), 0);
252        assert_eq!(u5::new(17).value(), 17);
253        assert!(u5::try_new(31).is_ok());
254        assert!(u5::try_new(32).is_err());
255        // from_raw masks rather than rejects.
256        assert_eq!(u5::from_raw(0xFF).value(), 31);
257    }
258
259    #[test]
260    #[should_panic(expected = "value out of range")]
261    fn new_panics_on_overflow() {
262        let _ = u4::new(16);
263    }
264
265    #[test]
266    fn bits_round_trip() {
267        let v = u13::new(0x1ABC & 0x1FFF);
268        assert_eq!(<u13 as Bits>::BITS, 13);
269        assert_eq!(u13::from_bits(v.into_bits()), v);
270        // into_bits exposes only the low N bits.
271        assert!(v.into_bits() <= u13::MAX.value() as u128);
272    }
273
274    #[test]
275    fn conversions() {
276        let v = u12::new(0xABC);
277        let raw: u16 = v.into();
278        assert_eq!(raw, 0xABC);
279        assert_eq!(u12::try_from(0xABCu16).unwrap(), v);
280        assert!(u12::try_from(0x1000u16).is_err());
281    }
282
283    #[test]
284    fn widest_widths_work() {
285        assert_eq!(u127::MAX.value(), (1u128 << 127) - 1);
286        assert_eq!(u1::MAX.value(), 1);
287    }
288
289    #[test]
290    fn display_renders_the_plain_value() {
291        use alloc::string::ToString;
292        assert_eq!(u12::new(0xABC).to_string(), "2748");
293        assert_eq!(u5::new(0).to_string(), "0");
294    }
295
296    #[test]
297    fn debug_shows_the_width_and_value() {
298        use alloc::format;
299        assert_eq!(format!("{:?}", u4::new(0xA)), "u4(10)");
300        assert_eq!(format!("{:?}", u12::new(0xABC)), "u12(2748)");
301    }
302}