From 3ba9bca50190adf326818514c87b00d68b66d302 Mon Sep 17 00:00:00 2001 From: Daniel Lehmann Date: Thu, 14 Jul 2022 22:20:24 -0700 Subject: [PATCH] Initial code commit --- .gitignore | 3 + Cargo.toml | 7 ++ src/lib.rs | 295 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/tests.rs | 299 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 604 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 tests/tests.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58db1f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target +.idea +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..66071cf --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "arbitrary-int" +version = "1.0.0" +edition = "2021" + +[dependencies] +seq-macro = "0.3.0" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e51fa43 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,295 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +mod lib { + pub mod core { + #[cfg(not(feature = "std"))] + pub use core::*; + #[cfg(feature = "std")] + pub use std::*; + } +} + +use seq_macro::seq; +use lib::core::ops::{ + Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign, +}; + +struct CompileTimeAssert {} + +impl CompileTimeAssert { + pub const SMALLER_OR_EQUAL: () = { assert!(A <= B); }; + pub const SMALLER_THAN: () = { assert!(A <= B); }; +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Ord, PartialOrd)] +pub struct UInt { + value: T, +} + +impl UInt + where T: Copy + BitAnd + Sub + Shl + Shr + From { + pub const fn value(&self) -> T { self.value } + + pub const unsafe fn new_unchecked(value: T) -> Self { Self { value } } + + fn mask() -> T { + // It would be great if we could make this function const, but generic traits aren't compatible with + // const fn + // Also note that we have to use T::from(1) (as opposed to doing that at the end), as we + // only require From(u8) in the generic constraints. + let one = T::from(1); + (one << NUM_BITS) - one + } +} + +// Next are specific implementations for u8, u16, u32, u64 and u128. A couple notes: +// - The existence of MAX also serves as a neat bounds-check for NUM_BITS: If NUM_BITS is too large, +// the left shift overflows which will fail to compile. This simplifies things a lot. +// However, that only works if every constructor also uses MAX somehow (doing let _ = MAX is enough) + +macro_rules! uint_impl { + ($size:expr, $type:ident) => { + + impl UInt<$type, NUM_BITS> { + /// Minimum value that can be represented by this type + pub const MIN: Self = Self { value: 0 }; + + /// Maximum value that can be represented by this type + /// Note that the existence of MAX also serves as a bounds check: If NUM_BITS is >= available bits, + /// we will get a compiler error right here + pub const MAX: Self = Self { value: (1 << NUM_BITS) - 1 }; + + /// Creates an instance. Panics if the given value is outside of the valid range + pub const fn new(value: $type) -> Self { + assert!(value <= Self::MAX.value); + + Self { value } + } + + /// Extracts bits from a given value. The extract is equivalent to: `new((value >> start_bit) & MASK)` + /// Unlike new, extract doesn't perform range-checking so it is slightly more efficient + pub fn extract(value: $type, start_bit: usize) -> Self { + assert!(start_bit + NUM_BITS <= $size); + // Query MAX to ensure that we get a compiler error if the current definition is bogus (e.g. ) + let _ = Self::MAX; + + Self { value: (value >> start_bit) & Self::MAX.value } + } + + /// Returns a UInt with a wider bit depth but with the same base data type + pub const fn widen(&self) -> UInt<$type, NUM_BITS_RESULT> { + let _ = CompileTimeAssert::::SMALLER_THAN; + // Query MAX of the result to ensure we get a compiler error if the current definition is bogus (e.g. ) + let _ = UInt::<$type, NUM_BITS_RESULT>::MAX; + UInt::<$type, NUM_BITS_RESULT> { value: self.value } + } + } + } +} + +uint_impl!(8, u8); +uint_impl!(16, u16); +uint_impl!(32, u32); +uint_impl!(64, u64); +uint_impl!(128, u128); + +// Arithmetic implementations +impl Add for UInt + where T: PartialEq + Eq + Copy + BitAnd + Not + Add + Sub + Shr + Shl + From { + type Output = UInt; + + fn add(self, rhs: Self) -> Self::Output { + let sum = self.value + rhs.value; + #[cfg(debug_assertions)] + if (sum & !Self::mask()) != T::from(0) { + panic!("attempt to add with overflow"); + } + Self { value: sum & Self::mask() } + } +} + +impl AddAssign for UInt + where T: PartialEq + Eq + Not + Copy + AddAssign + BitAnd + BitAndAssign + Sub + Shr + Shl + From { + fn add_assign(&mut self, rhs: Self) { + self.value += rhs.value; + #[cfg(debug_assertions)] + if (self.value & !Self::mask()) != T::from(0) { + panic!("attempt to add with overflow"); + } + self.value &= Self::mask(); + } +} + +impl Sub for UInt + where T: Copy + BitAnd + Sub + Shl + Shr + From { + type Output = UInt; + + fn sub(self, rhs: Self) -> Self::Output { + // No need for extra overflow checking as the regular minus operator already handles it for us + Self { value: (self.value - rhs.value) & Self::mask() } + } +} + +impl SubAssign for UInt + where T: Copy + SubAssign + BitAnd + BitAndAssign + Sub + Shl + Shr + From { + fn sub_assign(&mut self, rhs: Self) { + // No need for extra overflow checking as the regular minus operator already handles it for us + self.value -= rhs.value; + self.value &= Self::mask(); + } +} + +impl BitAnd for UInt + where T: Copy + BitAnd + Sub + Shl + Shr + From { + type Output = UInt; + + fn bitand(self, rhs: Self) -> Self::Output { + Self { value: self.value & rhs.value } + } +} + +impl BitAndAssign for UInt + where T: Copy + BitAndAssign + Sub + Shl + From { + fn bitand_assign(&mut self, rhs: Self) { + self.value &= rhs.value; + } +} + +impl BitOr for UInt + where T: Copy + BitOr + Sub + Shl + From { + type Output = UInt; + + fn bitor(self, rhs: Self) -> Self::Output { + Self { value: self.value | rhs.value } + } +} + +impl BitOrAssign for UInt + where T: Copy + BitOrAssign + Sub + Shl + From { + fn bitor_assign(&mut self, rhs: Self) { + self.value |= rhs.value; + } +} + +impl BitXor for UInt + where T: Copy + BitXor + Sub + Shl + From { + type Output = UInt; + + fn bitxor(self, rhs: Self) -> Self::Output { + Self { value: self.value ^ rhs.value } + } +} + +impl BitXorAssign for UInt + where T: Copy + BitXorAssign + Sub + Shl + From { + fn bitxor_assign(&mut self, rhs: Self) { + self.value ^= rhs.value; + } +} + +impl Not for UInt + where T: Copy + BitAnd + BitXor + Sub + Shl + Shr + From { + type Output = UInt; + + fn not(self) -> Self::Output { + Self { value: self.value ^ Self::mask() } + } +} + +impl Shl for UInt + where T: Copy + BitAnd + Shl + Sub + Shl + Shr + From { + type Output = UInt; + + fn shl(self, rhs: TSHIFTBITS) -> Self::Output { + Self { value: (self.value << rhs) & Self::mask() } + } +} + +impl ShlAssign for UInt + where T: Copy + BitAnd + BitAndAssign + ShlAssign + Sub + Shr + Shl + From { + fn shl_assign(&mut self, rhs: TSHIFTBITS) { + self.value <<= rhs; + self.value &= Self::mask(); + } +} + +impl Shr for UInt + where T: Copy + Shr + Sub + Shl + From { + type Output = UInt; + + fn shr(self, rhs: TSHIFTBITS) -> Self::Output { + Self { value: self.value >> rhs } + } +} + +impl ShrAssign for UInt + where T: Copy + ShrAssign + Sub + Shl + From { + fn shr_assign(&mut self, rhs: TSHIFTBITS) { + self.value >>= rhs; + } +} + +// Conversions +macro_rules! from_impl { + ($target_type:ident, $source_type:ident) => { + + impl From> for UInt<$target_type, NUM_BITS> { + fn from(item: UInt<$source_type, NUM_BITS_FROM>) -> Self { + let _ = CompileTimeAssert::::SMALLER_OR_EQUAL; + Self { value: item.value as $target_type } + } + } + } +} + +from_impl!(u8, u16); +from_impl!(u8, u32); +from_impl!(u8, u64); +from_impl!(u8, u128); + +from_impl!(u16, u8); +from_impl!(u16, u32); +from_impl!(u16, u64); +from_impl!(u16, u128); + +from_impl!(u32, u8); +from_impl!(u32, u16); +from_impl!(u32, u64); +from_impl!(u32, u128); + +from_impl!(u64, u8); +from_impl!(u64, u16); +from_impl!(u64, u32); +from_impl!(u64, u128); + +from_impl!(u128, u8); +from_impl!(u128, u16); +from_impl!(u128, u32); +from_impl!(u128, u64); + +// Define type aliases like u1, u63 and u80 using the smallest possible underlying data type. +// These are for convenience only - UInt is still legal +seq!(N in 1..=7 { + #[allow(non_camel_case_types)] + pub type u~N = UInt; +}); + +seq!(N in 9..=15 { + #[allow(non_camel_case_types)] + pub type u~N = UInt; +}); + +seq!(N in 17..=31 { + #[allow(non_camel_case_types)] + pub type u~N = UInt; +}); + +seq!(N in 33..=63 { + #[allow(non_camel_case_types)] + pub type u~N = UInt; +}); + +seq!(N in 65..=127 { + #[allow(non_camel_case_types)] + pub type u~N = UInt; +}); diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 0000000..6a533a8 --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,299 @@ +extern crate core; + +use arbitrary_int::{u10, u11, u120, u127, u13, u14, u15, u17, u20, u23, u24, u30, u31, u4, u5, u6, u60, u61, u63, u67, u7, u80, u9, UInt}; + +#[test] +fn create_simple() { + let value7 = u7::new(123); + let value13 = u13::new(123); + let value23 = u23::new(123); + let value67 = u67::new(123); + + assert_eq!(value7.value(), 123); + assert_eq!(value13.value(), 123); + assert_eq!(value23.value(), 123); + assert_eq!(value67.value(), 123); +} + +#[test] +#[should_panic] +fn create_panic_u7() { + u7::new(128); +} + +#[test] +#[should_panic] +fn create_panic_u15() { + u15::new(32768); +} + +#[test] +#[should_panic] +fn create_panic_u31() { + u31::new(2147483648); +} + +#[test] +#[should_panic] +fn create_panic_u63() { + u63::new(0x8000_0000_0000_0000); +} + +#[test] +#[should_panic] +fn create_panic_u127() { + u127::new(0x8000_0000_0000_0000_0000_0000_0000_0000); +} + +#[test] +fn add() { + assert_eq!(u7::new(10) + u7::new(20), u7::new(30)); + assert_eq!(u7::new(100) + u7::new(27), u7::new(127)); +} + +#[test] +#[should_panic] +fn add_overflow() { + let _ = u7::new(127) + u7::new(1); +} + +#[test] +fn addassign() { + let mut value = u9::new(500); + value += u9::new(11); + assert_eq!(value, u9::new(511)); +} + +#[test] +#[should_panic] +fn addassign_overflow() { + let mut value = u9::new(500); + value += u9::new(40); +} + +#[test] +fn sub() { + assert_eq!(u7::new(22) - u7::new(10), u7::new(12)); + assert_eq!(u7::new(127) - u7::new(127), u7::new(0)); +} + +#[test] +#[should_panic] +fn sub_overflow() { + let _ = u7::new(100) - u7::new(127); +} + +#[test] +fn subassign() { + let mut value = u9::new(500); + value -= u9::new(11); + assert_eq!(value, u9::new(489)); +} + +#[test] +#[should_panic] +fn subassign_overflow() { + let mut value = u9::new(30); + value -= u9::new(40); +} + +#[test] +fn bitand() { + assert_eq!(u17::new(0b11001100) & u17::new(0b01101001), u17::new(0b01001000)); + assert_eq!(u17::new(0b11001100) & u17::new(0), u17::new(0)); + assert_eq!(u17::new(0b11001100) & u17::new(0x1_FFFF), u17::new(0b11001100)); +} + +#[test] +fn bitandassign() { + let mut value = u4::new(0b0101); + value &= u4::new(0b0110); + assert_eq!(value, u4::new(0b0100)); +} + +#[test] +fn bitor() { + assert_eq!(u17::new(0b11001100) | u17::new(0b01101001), u17::new(0b11101101)); + assert_eq!(u17::new(0b11001100) | u17::new(0), u17::new(0b11001100)); + assert_eq!(u17::new(0b11001100) | u17::new(0x1_FFFF), u17::new(0x1_FFFF)); +} + +#[test] +fn bitorassign() { + let mut value = u4::new(0b0101); + value |= u4::new(0b0110); + assert_eq!(value, u4::new(0b0111)); +} + +#[test] +fn bitxor() { + assert_eq!(u17::new(0b11001100) ^ u17::new(0b01101001), u17::new(0b10100101)); + assert_eq!(u17::new(0b11001100) ^ u17::new(0), u17::new(0b11001100)); + assert_eq!(u17::new(0b11001100) ^ u17::new(0x1_FFFF), u17::new(0b1_11111111_00110011)); +} + +#[test] +fn bitxorassign() { + let mut value = u4::new(0b0101); + value ^= u4::new(0b0110); + assert_eq!(value, u4::new(0b0011)); +} + +#[test] +fn not() { + assert_eq!(!u17::new(0), u17::new(0b1_11111111_11111111)); + assert_eq!(!u5::new(0b10101), u5::new(0b01010)); +} + +#[test] +fn shl() { + assert_eq!(u17::new(0b1) << 5u8, u17::new(0b100000)); + // Ensure bits on the left are shifted out + assert_eq!(u9::new(0b11110000) << 3u64, u9::new(0b1_10000000)); +} + +#[test] +fn shlassign() { + let mut value = u9::new(0b11110000); + value <<= 3; + assert_eq!(value, u9::new(0b1_10000000)); +} + +#[test] +fn shr() { + assert_eq!(u17::new(0b100110) >> 5usize, u17::new(1)); + + // Ensure there's no sign extension + assert_eq!(u17::new(0b1_11111111_11111111) >> 8, u17::new(0b1_11111111)); +} + +#[test] +fn shrassign() { + let mut value = u9::new(0b1_11110000); + value >>= 6; + assert_eq!(value, u9::new(0b0_00000111)); +} + +#[test] +fn compare() { + assert_eq!(true, u4::new(0b1100) > u4::new(0b0011)); + assert_eq!(true, u4::new(0b1100) >= u4::new(0b0011)); + assert_eq!(false, u4::new(0b1100) < u4::new(0b0011)); + assert_eq!(false, u4::new(0b1100) <= u4::new(0b0011)); + assert_eq!(true, u4::new(0b1100) != u4::new(0b0011)); + assert_eq!(false, u4::new(0b1100) == u4::new(0b0011)); + + assert_eq!(false, u4::new(0b1100) > u4::new(0b1100)); + assert_eq!(true, u4::new(0b1100) >= u4::new(0b1100)); + assert_eq!(false, u4::new(0b1100) < u4::new(0b1100)); + assert_eq!(true, u4::new(0b1100) <= u4::new(0b1100)); + assert_eq!(false, u4::new(0b1100) != u4::new(0b1100)); + assert_eq!(true, u4::new(0b1100) == u4::new(0b1100)); + + assert_eq!(false, u4::new(0b0011) > u4::new(0b1100)); + assert_eq!(false, u4::new(0b0011) >= u4::new(0b1100)); + assert_eq!(true, u4::new(0b0011) < u4::new(0b1100)); + assert_eq!(true, u4::new(0b0011) <= u4::new(0b1100)); + assert_eq!(true, u4::new(0b0011) != u4::new(0b1100)); + assert_eq!(false, u4::new(0b0011) == u4::new(0b1100)); +} + +#[test] +fn min_max() { + assert_eq!(0, u4::MIN.value()); + assert_eq!(0b1111, u4::MAX.value()); + + assert_eq!(0, u15::MIN.value()); + assert_eq!(32767, u15::MAX.value()); + + assert_eq!(0, u31::MIN.value()); + assert_eq!(2147483647, u31::MAX.value()); + + assert_eq!(0, u63::MIN.value()); + assert_eq!(0x7FFF_FFFF_FFFF_FFFF, u63::MAX.value()); + + assert_eq!(0, u127::MIN.value()); + assert_eq!(0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF, u127::MAX.value()); +} + +#[test] +fn extract() { + assert_eq!(u5::new(0b10000), u5::extract(0b11110000, 0)); + assert_eq!(u5::new(0b11100), u5::extract(0b11110000, 2)); + assert_eq!(u5::new(0b11110), u5::extract(0b11110000, 3)); + + // Use extract with a custom type (5 bits of u32) + assert_eq!(UInt::::new(0b11110), UInt::::extract(0b11110000, 3)); +} + +#[test] +#[should_panic] +fn extract_not_enough_bits() { + let _ = u5::extract(0b11110000, 4); +} + +#[test] +fn from_same_bit_widths() { + assert_eq!(u5::from(UInt::::new(0b10101)), u5::new(0b10101)); + assert_eq!(u5::from(UInt::::new(0b10101)), u5::new(0b10101)); + assert_eq!(u5::from(UInt::::new(0b10101)), u5::new(0b10101)); + assert_eq!(u5::from(UInt::::new(0b10101)), u5::new(0b10101)); + assert_eq!(u5::from(UInt::::new(0b10101)), u5::new(0b10101)); + + assert_eq!(UInt::::from(UInt::::new(0b10101)), UInt::::new(0b10101)); + assert_eq!(u15::from(UInt::::new(0b10101)), u15::new(0b10101)); + assert_eq!(u15::from(UInt::::new(0b10101)), u15::new(0b10101)); + assert_eq!(u15::from(UInt::::new(0b10101)), u15::new(0b10101)); + assert_eq!(u15::from(UInt::::new(0b10101)), u15::new(0b10101)); + + assert_eq!(UInt::::from(u6::new(0b10101)), UInt::::new(0b10101)); + assert_eq!(UInt::::from(u14::new(0b10101)), UInt::::new(0b10101)); + assert_eq!(u30::from(UInt::::new(0b10101)), u30::new(0b10101)); + assert_eq!(u30::from(UInt::::new(0b10101)), u30::new(0b10101)); + assert_eq!(u30::from(UInt::::new(0b10101)), u30::new(0b10101)); + + assert_eq!(UInt::::from(UInt::::new(0b10101)), UInt::::new(0b10101)); + assert_eq!(UInt::::from(UInt::::new(0b10101)), UInt::::new(0b10101)); + assert_eq!(UInt::::from(UInt::::new(0b10101)), UInt::::new(0b10101)); + assert_eq!(u60::from(u60::new(0b10101)), u60::new(0b10101)); + assert_eq!(u60::from(UInt::::new(0b10101)), u60::new(0b10101)); + + assert_eq!(UInt::::from(UInt::::new(0b10101)), UInt::::new(0b10101)); + assert_eq!(UInt::::from(UInt::::new(0b10101)), UInt::::new(0b10101)); + assert_eq!(UInt::::from(UInt::::new(0b10101)), UInt::::new(0b10101)); + assert_eq!(UInt::::from(UInt::::new(0b10101)), UInt::::new(0b10101)); + assert_eq!(u120::from(UInt::::new(0b10101)), u120::new(0b10101)); +} + +#[test] +fn from_smaller_bit_widths() { + // The code to get more bits from fewer bits (through From) is the same as the code above + // for identical bitwidths. Therefore just do a few point checks to ensure things compile + + // There are compile-breakers for the opposite direction (e.g. tryint to do u5 = From(u17), + // but we can't test compile failures here + + // from is not yet supported if the bitcounts are different but the base data types are the same (need + // fancier Rust features to support that) + assert_eq!(u6::from(UInt::::new(0b10101)), u6::new(0b10101)); + assert_eq!(u6::from(UInt::::new(0b10101)), u6::new(0b10101)); + assert_eq!(u6::from(UInt::::new(0b10101)), u6::new(0b10101)); + assert_eq!(u6::from(UInt::::new(0b10101)), u6::new(0b10101)); + + assert_eq!(u15::from(UInt::::new(0b10101)), u15::new(0b10101)); + //assert_eq!(u15::from(UInt::::new(0b10101)), u15::new(0b10101)); + assert_eq!(u15::from(UInt::::new(0b10101)), u15::new(0b10101)); + assert_eq!(u15::from(UInt::::new(0b10101)), u15::new(0b10101)); + assert_eq!(u15::from(UInt::::new(0b10101)), u15::new(0b10101)); +} + +#[test] +fn widen() { + // As From() can't be used while keeping the base-data-type, there's widen + + assert_eq!(u5::new(0b11011).widen::<6>(), u6::new(0b11011)); + assert_eq!(u10::new(0b11011).widen::<11>(), u11::new(0b11011)); + assert_eq!(u20::new(0b11011).widen::<24>(), u24::new(0b11011)); + assert_eq!(u60::new(0b11011).widen::<61>(), u61::new(0b11011)); + assert_eq!(u80::new(0b11011).widen::<127>().value(), 0b11011); +}