commit c5e952d7d7c5574957b21f3750b3265e2b12e79e Author: EvilMuffinHa Date: Tue Aug 2 08:55:59 2022 -0400 conversions diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3e9ca22 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "smallint" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +num-bigint = "0.4.3" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..fd58eb9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,188 @@ +//! A crate for small integer optimization. Provides the [`SmallInt`] type. When possible this will +//! inline an integer and store it on the stack if that integer is small. However, for larger values, +//! this will be instead stored on the heap as a pointer to a `u32` slice, a length, and a sign. + +use num_bigint::BigInt; +use num_bigint::Sign; +use core::mem::ManuallyDrop; + +/// An error that occurred when processing a SmallInt. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SmallIntError { + /// Conversion error when converting from SmallInt to other integer types. + ConversionError, +} + +impl std::error::Error for SmallIntError {} + +impl std::fmt::Display for SmallIntError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self::ConversionError = self; + write!(f, "Error converting integer to SmallInt.") + } +} + + +/// An integer-like type that will store small integers up to `i64` inline. Larger integers are +/// represented as `BigInt` types, which are stored on the heap. +#[derive(Clone, PartialEq, Eq)] +pub enum SmallInt { + /// An integer stored inline. + Inline(i128), + /// A larger integer stored on the heap. This value is represented in base 232 and + /// ordered least significant digit first. Also stored with the size and sign of the integer. + /// + /// It is assumed that values stored on the heap are larger than the maximum value of inline + /// integers. + Heap((*mut u32, isize)) +} + + + +macro_rules! int_impl { + ($itype:ty) => { + impl From<$itype> for SmallInt { + fn from(a: $itype) -> Self { + SmallInt::Inline(i128::from(a)) + } + } + + impl TryFrom for $itype { + + type Error = SmallIntError; + + fn try_from(s: SmallInt) -> Result { + match s { + SmallInt::Inline(i) => <$itype>::try_from(i).map_err(|_| SmallIntError::ConversionError), + SmallInt::Heap((_, _)) => Err(SmallIntError::ConversionError), + } + } + } + } +} + +int_impl!(u8); +int_impl!(u16); +int_impl!(u32); +int_impl!(u64); +int_impl!(i8); +int_impl!(i16); +int_impl!(i32); +int_impl!(i64); +int_impl!(i128); + + +impl From for SmallInt { + fn from(a: u128) -> Self { + match i128::try_from(a) { + Ok(i) => Self::Inline(i), + Err(_) => { + let mut v = a; + let mut vec = Vec::with_capacity(4); + while v != 0 { + vec.push(v as u32); + + v = (v >> 1) >> (32 - 1); + } + let mut slice = ManuallyDrop::new(vec.into_boxed_slice()); + SmallInt::Heap((slice.as_mut_ptr(), isize::try_from(slice.len()).unwrap())) + } + } + } +} + +impl TryFrom for u128 { + + type Error = SmallIntError; + + fn try_from(s: SmallInt) -> Result { + match s { + SmallInt::Inline(i) => u128::try_from(i).map_err(|_| SmallIntError::ConversionError), + SmallInt::Heap((r, s)) => { + let mut ret: u128 = 0; + let mut bits = 0; + let size = usize::try_from(s.abs()).unwrap(); + let slice = unsafe { std::slice::from_raw_parts(r, size) }; + for i in slice { + if bits >= 128 { + return Err(SmallIntError::ConversionError); + } + ret |= u128::from(*i) << bits; + bits += 32; + } + + Ok(ret) + } + } + } +} + + +impl From for SmallInt { + fn from(b: BigInt) -> Self { + match (&b).try_into() { + Ok(i) => SmallInt::Inline(i), + Err(_) => { + let (sign, vec) = b.to_u32_digits(); + let mut slice = ManuallyDrop::new(vec.into_boxed_slice()); + let size = match sign { + Sign::Minus => -isize::try_from(slice.len()).unwrap(), + Sign::NoSign => panic!("Shouldn't happen; BigInts which store zero should convert to inline."), + Sign::Plus => isize::try_from(slice.len()).unwrap() + }; + SmallInt::Heap((slice.as_mut_ptr(), size)) } + } + } +} + +impl From for BigInt { + + + fn from(s: SmallInt) -> Self { + match s { + SmallInt::Inline(i) => Self::from(i), + SmallInt::Heap((r, s)) => { + let size = usize::try_from(s.abs()).unwrap(); + let sign = s.signum(); + let slice = unsafe { std::slice::from_raw_parts(r, size) }; + let bs = match sign { + 0 => Sign::NoSign, + -1 => Sign::Minus, + 1 => Sign::Plus, + _ => panic!("This shouldn't happen; There are only 3 signums."), + }; + + BigInt::new(bs, slice.to_vec()) + } + } + } +} + +#[cfg(test)] +mod conversion_tests { + + use crate::SmallInt; + + #[test] + fn test_u8() { + let i = u8::MAX; + let s = SmallInt::from(i); + assert_eq!(u8::try_from(s).unwrap(), i); + + let i = u8::MIN; + let s = SmallInt::from(i); + assert_eq!(u8::try_from(s).unwrap(), i); + } + + #[test] + fn test_i8() { + let i = i8::MIN; + let s = SmallInt::from(i); + assert_eq!(i8::try_from(s).unwrap(), i); + + let i = i8::MAX; + let s = SmallInt::from(i); + assert_eq!(i8::try_from(s).unwrap(), i); + } +} +