use core::fmt::Debug;
pub trait Word:
Copy
+ Default
+ Eq
+ Ord
+ Debug
+ 'static
+ core::ops::BitAnd<Output = Self>
+ core::ops::BitOr<Output = Self>
+ core::ops::BitXor<Output = Self>
+ core::ops::Shr<u32, Output = Self>
+ core::ops::Shl<u32, Output = Self>
{
type Wide: WideWord;
const BITS_LOG2: u8;
const MIN: Self;
const MAX: Self;
fn from_i64_wrap(n: i64) -> Self;
fn to_i64(self) -> i64;
fn to_usize_checked(self) -> Option<usize> {
usize::try_from(self.to_i64()).ok()
}
fn widen(self) -> Self::Wide;
fn from_wide_wrap(w: Self::Wide) -> Self;
fn wrapping_add(self, other: Self) -> Self;
fn wrapping_sub(self, other: Self) -> Self;
fn wrapping_mul(self, other: Self) -> Self;
fn wrapping_div(self, other: Self) -> Self;
fn wrapping_rem(self, other: Self) -> Self;
fn wrapping_neg(self) -> Self;
}
pub trait WideWord:
Copy
+ Default
+ Eq
+ Ord
+ Debug
+ 'static
+ core::ops::Add<Output = Self>
+ core::ops::Sub<Output = Self>
+ core::ops::Mul<Output = Self>
+ core::ops::Div<Output = Self>
+ core::ops::Rem<Output = Self>
+ core::ops::Neg<Output = Self>
+ core::ops::Shr<u32, Output = Self>
+ core::ops::Shl<u32, Output = Self>
{
fn wide_add(self, other: Self) -> Self;
fn wide_sub(self, other: Self) -> Self;
fn wide_mul(self, other: Self) -> Self;
fn wide_neg(self) -> Self;
fn high_half(self) -> Self;
}
macro_rules! impl_word_pair {
($word:ty, $wide:ty, $bits_log2:expr, $word_bits:expr) => {
impl Word for $word {
type Wide = $wide;
const BITS_LOG2: u8 = $bits_log2;
const MIN: Self = <$word>::MIN;
const MAX: Self = <$word>::MAX;
fn from_i64_wrap(n: i64) -> Self {
n as $word
}
fn to_i64(self) -> i64 {
self as i64
}
fn widen(self) -> Self::Wide {
self as $wide
}
fn from_wide_wrap(w: Self::Wide) -> Self {
w as $word
}
fn wrapping_add(self, other: Self) -> Self {
<$word>::wrapping_add(self, other)
}
fn wrapping_sub(self, other: Self) -> Self {
<$word>::wrapping_sub(self, other)
}
fn wrapping_mul(self, other: Self) -> Self {
<$word>::wrapping_mul(self, other)
}
fn wrapping_div(self, other: Self) -> Self {
<$word>::wrapping_div(self, other)
}
fn wrapping_rem(self, other: Self) -> Self {
<$word>::wrapping_rem(self, other)
}
fn wrapping_neg(self) -> Self {
<$word>::wrapping_neg(self)
}
}
impl WideWord for $wide {
fn wide_add(self, other: Self) -> Self {
<$wide>::wrapping_add(self, other)
}
fn wide_sub(self, other: Self) -> Self {
<$wide>::wrapping_sub(self, other)
}
fn wide_mul(self, other: Self) -> Self {
<$wide>::wrapping_mul(self, other)
}
fn wide_neg(self) -> Self {
<$wide>::wrapping_neg(self)
}
fn high_half(self) -> Self {
self >> $word_bits
}
}
};
}
impl_word_pair!(i8, i16, 3, 8);
impl_word_pair!(i16, i32, 4, 16);
impl_word_pair!(i32, i64, 5, 32);
impl_word_pair!(i64, i128, 6, 64);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn i8_word_basics() {
assert_eq!(<i8 as Word>::BITS_LOG2, 3);
assert_eq!(<i8 as Word>::MIN, i8::MIN);
assert_eq!(<i8 as Word>::MAX, i8::MAX);
assert_eq!(<i8 as Word>::from_i64_wrap(0x1234), 0x34_u8 as i8);
assert_eq!((100_i8).wrapping_add(50), -106_i8);
}
#[test]
fn i16_word_basics() {
assert_eq!(<i16 as Word>::BITS_LOG2, 4);
assert_eq!(<i16 as Word>::from_i64_wrap(0x12345), 0x2345_i16);
assert_eq!((30000_i16).wrapping_add(10000), -25536_i16);
}
#[test]
fn i32_word_basics() {
assert_eq!(<i32 as Word>::BITS_LOG2, 5);
assert_eq!(<i32 as Word>::from_i64_wrap(0x1_0000_0001), 1_i32);
}
#[test]
fn i64_word_basics() {
assert_eq!(<i64 as Word>::BITS_LOG2, 6);
assert_eq!(<i64 as Word>::from_i64_wrap(0x1234), 0x1234_i64);
}
#[test]
fn widening_multiplication_high_low_split_i8() {
let a = 100_i8;
let b = 50_i8;
let wide = a.widen().wide_mul(b.widen());
assert_eq!(wide, 5000_i16);
assert_eq!(<i16 as WideWord>::high_half(wide), 19_i16);
assert_eq!(<i8 as Word>::from_wide_wrap(wide), -120_i8);
}
#[test]
fn widening_multiplication_high_low_split_i64() {
let a = i64::MAX;
let b = 2_i64;
let wide = a.widen().wide_mul(b.widen());
assert_eq!(wide, (i64::MAX as i128) * 2);
assert_eq!(<i128 as WideWord>::high_half(wide), 0_i128);
assert_eq!(<i64 as Word>::from_wide_wrap(wide), -2_i64);
}
#[test]
fn wrapping_arithmetic_at_extreme_values() {
assert_eq!(i8::MIN.wrapping_add(1), -127_i8);
assert_eq!(i8::MAX.wrapping_add(1), i8::MIN);
assert_eq!(i8::MIN.wrapping_neg(), i8::MIN);
}
#[test]
fn to_usize_checked_succeeds_for_non_negative() {
assert_eq!(<i8 as Word>::to_usize_checked(0_i8), Some(0_usize));
assert_eq!(<i8 as Word>::to_usize_checked(127_i8), Some(127_usize));
assert_eq!(
<i16 as Word>::to_usize_checked(30_000_i16),
Some(30_000_usize)
);
assert_eq!(
<i64 as Word>::to_usize_checked(1_000_000_i64),
Some(1_000_000_usize)
);
}
#[test]
fn to_usize_checked_rejects_negative() {
assert_eq!(<i8 as Word>::to_usize_checked(-1_i8), None);
assert_eq!(<i16 as Word>::to_usize_checked(i16::MIN), None);
assert_eq!(<i64 as Word>::to_usize_checked(-42_i64), None);
}
#[test]
fn bits_log2_matches_runtime_constant() {
assert_eq!(<i8 as Word>::BITS_LOG2, 3);
assert_eq!(<i16 as Word>::BITS_LOG2, 4);
assert_eq!(<i32 as Word>::BITS_LOG2, 5);
assert_eq!(<i64 as Word>::BITS_LOG2, 6);
}
}