bit_int/
bit_int.rs

1// SPDX-FileCopyrightText: 2024 Shun Sakai
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! An arbitrary fixed bit-width signed integer.
6
7mod cmp;
8mod consts;
9mod convert;
10mod fmt;
11mod ops;
12
13use num_traits::{PrimInt, Signed};
14
15/// `BitInt` is a type that represents a `N`-bit signed integer.
16///
17/// `N` is the number of bits in the value, including the sign bit. The largest
18/// size of `N` is equal to the size of the underlying type in bits.
19///
20/// # Examples
21///
22/// ```
23/// # use bit_int::BitInt;
24/// #
25/// type Int = BitInt<i8, 7>;
26///
27/// let n = Int::new(-64).unwrap();
28/// assert_eq!(n, Int::MIN);
29///
30/// assert_eq!(n.checked_sub(1), None);
31/// assert_eq!(n.get().checked_sub(1), Some(-65));
32/// ```
33///
34/// In this case, `N` must be less than or equal to [`i32::BITS`]:
35///
36/// ```compile_fail
37/// # use bit_int::BitInt;
38/// #
39/// let _ = BitInt::<i32, 33>::new(42);
40/// ```
41///
42/// `N` must be greater than `0`:
43///
44/// ```compile_fail
45/// # use bit_int::BitInt;
46/// #
47/// let _ = BitInt::<i64, 0>::new(0);
48/// ```
49#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
50#[repr(transparent)]
51pub struct BitInt<T: Signed + PrimInt, const N: u32>(T);
52
53macro_rules! impl_bit_int {
54    ($T:ty, $alias:ident) => {
55        impl<const N: u32> BitInt<$T, N> {
56            /// Creates a new `BitInt` with the given signed integer value.
57            ///
58            /// Returns [`None`] if the value is not a valid `N`-bit signed integer.
59            ///
60            /// # Examples
61            ///
62            /// ```
63            /// # use bit_int::BitInt;
64            /// #
65            #[doc = concat!("let n = BitInt::<", stringify!($T), ", 7>::new(42);")]
66            /// assert_eq!(n.map(BitInt::get), Some(42));
67            #[doc = ""]
68            #[doc = concat!("let m = BitInt::<", stringify!($T), ", 6>::new(42);")]
69            /// assert_eq!(m, None);
70            /// ```
71            #[must_use]
72            #[inline]
73            pub const fn new(n: $T) -> Option<Self> {
74                if n >= Self::MIN.get() && n <= Self::MAX.get() {
75                    // SAFETY: `n` is checked to be a valid `N`-bit signed integer.
76                    let n = unsafe { Self::new_unchecked(n) };
77                    Some(n)
78                } else {
79                    None
80                }
81            }
82
83            /// Returns [`true`] if `self` is positive and [`false`] if the number is
84            /// zero or negative.
85            ///
86            /// # Examples
87            ///
88            /// ```
89            /// # use bit_int::BitInt;
90            /// #
91            #[doc = concat!("assert_eq!(BitInt::<", stringify!($T), ", 7>::MIN.is_positive(), false);")]
92            #[doc = concat!("assert_eq!(BitInt::<", stringify!($T), ", 7>::MAX.is_positive(), true);")]
93            /// ```
94            #[must_use]
95            #[inline]
96            pub const fn is_positive(self) -> bool {
97                self.get().is_positive()
98            }
99
100            /// Returns [`true`] if `self` is negative and [`false`] if the number is
101            /// zero or positive.
102            ///
103            /// # Examples
104            ///
105            /// ```
106            /// # use bit_int::BitInt;
107            /// #
108            #[doc = concat!("assert_eq!(BitInt::<", stringify!($T), ", 7>::MIN.is_negative(), true);")]
109            #[doc = concat!("assert_eq!(BitInt::<", stringify!($T), ", 7>::MAX.is_negative(), false);")]
110            /// ```
111            #[must_use]
112            #[inline]
113            pub const fn is_negative(self) -> bool {
114                self.get().is_negative()
115            }
116        }
117
118        /// A specialized [`BitInt`] type whose the underlying type is restricted to
119        #[doc = concat!("[`", stringify!($T), "`].")]
120        ///
121        #[doc = concat!("The largest size of `N` is equal to [`", stringify!($T), "::BITS`].")]
122        pub type $alias<const N: u32> = BitInt<$T, N>;
123    };
124}
125impl_bit_int!(i8, BitI8);
126impl_bit_int!(i16, BitI16);
127impl_bit_int!(i32, BitI32);
128impl_bit_int!(i64, BitI64);
129impl_bit_int!(i128, BitI128);
130impl_bit_int!(isize, BitIsize);
131
132impl<T: Signed + PrimInt, const N: u32> BitInt<T, N> {
133    /// Creates a new `BitInt` with the given signed integer value.
134    ///
135    /// This method does not check whether the value is a valid `N`-bit signed
136    /// integer. This results in undefined behaviour if the value is not a valid
137    /// `N`-bit signed integer.
138    ///
139    /// # Safety
140    ///
141    /// The value must be a valid `N`-bit signed integer.
142    #[must_use]
143    #[inline]
144    pub const unsafe fn new_unchecked(n: T) -> Self {
145        Self(n)
146    }
147
148    /// Returns the contained value as the underlying type.
149    #[must_use]
150    #[inline]
151    pub const fn get(self) -> T {
152        self.0
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use core::{any, mem};
159
160    use super::*;
161
162    #[test]
163    fn alias() {
164        assert_eq!(
165            any::type_name::<BitI8::<7>>(),
166            any::type_name::<BitInt::<i8, 7>>()
167        );
168        assert_eq!(
169            any::type_name::<BitI16::<15>>(),
170            any::type_name::<BitInt::<i16, 15>>()
171        );
172        assert_eq!(
173            any::type_name::<BitI32::<31>>(),
174            any::type_name::<BitInt::<i32, 31>>()
175        );
176        assert_eq!(
177            any::type_name::<BitI64::<63>>(),
178            any::type_name::<BitInt::<i64, 63>>()
179        );
180        assert_eq!(
181            any::type_name::<BitI128::<127>>(),
182            any::type_name::<BitInt::<i128, 127>>()
183        );
184        assert_eq!(
185            any::type_name::<BitIsize::<31>>(),
186            any::type_name::<BitInt::<isize, 31>>()
187        );
188    }
189
190    #[test]
191    fn layout() {
192        assert_eq!(mem::size_of::<BitI32::<31>>(), mem::size_of::<i32>());
193        assert_eq!(mem::align_of::<BitI32::<31>>(), mem::align_of::<i32>());
194    }
195
196    #[test]
197    fn clone() {
198        assert_eq!(BitI32::<31>::MIN.clone(), BitI32::<31>::MIN);
199    }
200
201    #[test]
202    fn copy() {
203        let a = BitI32::<31>::MIN;
204        let b = a;
205        assert_eq!(a, b);
206    }
207
208    #[test]
209    fn default() {
210        assert_eq!(
211            BitI32::<31>::default(),
212            BitI32::<31>::new(i32::default()).unwrap()
213        );
214    }
215
216    #[test]
217    fn new() {
218        assert_eq!(
219            BitI8::<7>::new(i8::MAX >> 1).map(BitI8::get),
220            Some(i8::MAX >> 1)
221        );
222        assert_eq!(
223            BitI16::<15>::new(i16::MAX >> 1).map(BitI16::get),
224            Some(i16::MAX >> 1)
225        );
226        assert_eq!(
227            BitI32::<31>::new(i32::MAX >> 1).map(BitI32::get),
228            Some(i32::MAX >> 1)
229        );
230        assert_eq!(
231            BitI64::<63>::new(i64::MAX >> 1).map(BitI64::get),
232            Some(i64::MAX >> 1)
233        );
234        assert_eq!(
235            BitI128::<127>::new(i128::MAX >> 1).map(BitI128::get),
236            Some(i128::MAX >> 1)
237        );
238        assert_eq!(
239            BitIsize::<{ isize::BITS - 1 }>::new(isize::MAX >> 1).map(BitIsize::get),
240            Some(isize::MAX >> 1)
241        );
242    }
243
244    #[test]
245    fn new_with_invalid_value() {
246        assert!(BitI8::<7>::new((i8::MAX >> 1) + 1).is_none());
247        assert!(BitI16::<15>::new((i16::MAX >> 1) + 1).is_none());
248        assert!(BitI32::<31>::new((i32::MAX >> 1) + 1).is_none());
249        assert!(BitI64::<63>::new((i64::MAX >> 1) + 1).is_none());
250        assert!(BitI128::<127>::new((i128::MAX >> 1) + 1).is_none());
251        assert!(BitIsize::<{ isize::BITS - 1 }>::new((isize::MAX >> 1) + 1).is_none());
252    }
253
254    #[test]
255    fn new_when_one_bit_value() {
256        assert!(BitI32::<1>::new(-2).is_none());
257        assert_eq!(BitI32::<1>::new(-1).map(BitI32::get), Some(-1));
258        assert_eq!(BitI32::<1>::new(0).map(BitI32::get), Some(0));
259        assert!(BitI32::<1>::new(1).is_none());
260    }
261
262    #[test]
263    fn new_when_max_bits_value() {
264        assert_eq!(
265            BitI32::<{ i32::BITS }>::new(i32::MIN).map(BitI32::get),
266            Some(i32::MIN)
267        );
268        assert_eq!(
269            BitI32::<{ i32::BITS }>::new(i32::default()).map(BitI32::get),
270            Some(i32::default())
271        );
272        assert_eq!(
273            BitI32::<{ i32::BITS }>::new(i32::MAX).map(BitI32::get),
274            Some(i32::MAX)
275        );
276    }
277
278    #[test]
279    const fn new_is_const_fn() {
280        const _: Option<BitI32<31>> = BitI32::<31>::new(i32::MAX >> 1);
281    }
282
283    #[test]
284    fn new_unchecked() {
285        assert_eq!(
286            unsafe { BitI8::<7>::new_unchecked(i8::MAX >> 1) }.get(),
287            i8::MAX >> 1
288        );
289        assert_eq!(
290            unsafe { BitI16::<15>::new_unchecked(i16::MAX >> 1) }.get(),
291            i16::MAX >> 1
292        );
293        assert_eq!(
294            unsafe { BitI32::<31>::new_unchecked(i32::MAX >> 1) }.get(),
295            i32::MAX >> 1
296        );
297        assert_eq!(
298            unsafe { BitI64::<63>::new_unchecked(i64::MAX >> 1) }.get(),
299            i64::MAX >> 1
300        );
301        assert_eq!(
302            unsafe { BitI128::<127>::new_unchecked(i128::MAX >> 1) }.get(),
303            i128::MAX >> 1
304        );
305        assert_eq!(
306            unsafe { BitIsize::<{ isize::BITS - 1 }>::new_unchecked(isize::MAX >> 1) }.get(),
307            isize::MAX >> 1
308        );
309    }
310
311    #[test]
312    const fn new_unchecked_is_const_fn() {
313        const _: BitI32<31> = unsafe { BitI32::<31>::new_unchecked(i32::MAX >> 1) };
314    }
315
316    #[test]
317    fn get() {
318        assert_eq!(BitI8::<7>::MAX.get(), i8::MAX >> 1);
319        assert_eq!(BitI16::<15>::MAX.get(), i16::MAX >> 1);
320        assert_eq!(BitI32::<31>::MAX.get(), i32::MAX >> 1);
321        assert_eq!(BitI64::<63>::MAX.get(), i64::MAX >> 1);
322        assert_eq!(BitI128::<127>::MAX.get(), i128::MAX >> 1);
323        assert_eq!(BitIsize::<{ isize::BITS - 1 }>::MAX.get(), isize::MAX >> 1);
324    }
325
326    #[test]
327    const fn get_is_const_fn() {
328        const _: i32 = BitI32::<31>::MIN.get();
329    }
330
331    #[test]
332    fn is_positive() {
333        assert!(!BitI32::<31>::MIN.is_positive());
334        assert!(!BitI32::<31>::default().is_positive());
335        assert!(BitI32::<31>::MAX.is_positive());
336    }
337
338    #[test]
339    const fn is_positive_is_const_fn() {
340        const _: bool = BitI32::<31>::MIN.is_positive();
341    }
342
343    #[test]
344    fn is_negative() {
345        assert!(BitI32::<31>::MIN.is_negative());
346        assert!(!BitI32::<31>::default().is_negative());
347        assert!(!BitI32::<31>::MAX.is_negative());
348    }
349
350    #[test]
351    const fn is_negative_is_const_fn() {
352        const _: bool = BitI32::<31>::MIN.is_positive();
353    }
354}