//! Numeric Helper Traits //! //! Home to the numeric trait chain of doom, aka `Unsigned` #![allow(unused_comparisons)] use core::cmp::Ordering; use core::convert::TryFrom; use core::fmt::Debug; use core::ops::{ Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign, }; /// Represents the sign of a rational number #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(u8)] pub enum Sign { /// Greater than zero, or zero Positive, /// Less than zero Negative, } /// The type of mathematical operation #[derive(Debug, Copy, Clone, PartialEq)] pub enum FracOp { Addition, Subtraction, Other, } impl Default for Sign { #[cfg(not(tarpaulin_include))] fn default() -> Self { Sign::Positive } } impl Neg for Sign { type Output = Self; fn neg(self) -> Self::Output { !self } } impl Not for Sign { type Output = Self; fn not(self) -> Self::Output { match self { Self::Positive => Self::Negative, Self::Negative => Self::Positive, } } } impl PartialOrd for Sign { fn partial_cmp(&self, other: &Self) -> Option { match self { Self::Positive => match other { Self::Positive => Some(Ordering::Equal), Self::Negative => Some(Ordering::Greater), }, Self::Negative => match other { Self::Positive => Some(Ordering::Less), Self::Negative => Some(Ordering::Equal), }, } } } /// Native number type pub trait Num: Add + AddAssign + Debug + Div + DivAssign + Mul + MulAssign + Rem + RemAssign + PartialOrd + PartialEq + Copy + Sub + SubAssign { /// Implement absolute value function for unsigned numbers fn abs(self) -> Self { self } } /// Integer primitive pub trait Int: Num + BitAnd + BitAndAssign + BitOr + BitOrAssign + BitXor + BitXorAssign + Eq + Ord + Not + Shl + Shr + ShlAssign + ShrAssign { /// Associated type for unsigned conversion type Un; /// The maximum value of the type fn max_value() -> Self; /// Is this a zero value? fn is_zero(self) -> bool; /// Is this number less than zero? fn is_neg(self) -> bool; /// Returns a tuple of the subtraction along with a boolean indicating whether an arithmetic /// overflow would occur. If an overflow would have occurred then the wrapped value is returned. /// /// The wrapped value is the amount less than the minimum value as a positive number fn left_overflowing_sub(self, rhs: Self) -> (Self, bool); /// Convert to an unsigned number /// /// A meaningless operation when implemented on an /// unsigned type, but the interface consistency solves /// other issues fn to_unsigned(self) -> Self::Un; } /// A Trait representing unsigned integer primitives pub trait Unsigned: Int + Add + Sub + Mul + Div { /// Find the greatest common denominator of two numbers fn gcd(a: Self, b: Self) -> Self; /// Find the least common multiple of two numbers fn lcm(a: Self, b: Self) -> Self; } /// A Trait representing signed integer primitives pub trait Signed: Int {} macro_rules! impl_empty { ($Ident: ty, ($( $Type: ty ),*) ) => { $( impl $Ident for $Type {} )* } } macro_rules! impl_int { ($(($type: ty, $un_type: ty)),* ) => { $( impl Int for $type { type Un = $un_type; fn is_zero(self) -> bool { self == 0 } fn max_value() -> $type { <$type>::max_value() } fn is_neg(self) -> bool { self < 0 } fn to_unsigned(self) -> $un_type { let abs = <$type>::abs(self); // Converting from signed to unsigned should always be safe // when using the absolute value, especially since I'm converting // between the same bit size <$un_type>::try_from(abs).unwrap() } fn left_overflowing_sub(self, rhs: Self) -> (Self, bool) { let (res, overflow) = <$type>::overflowing_sub(self, rhs); let res = if overflow { <$type>::max_value() - res + 1 } else { res }; (res, overflow) } } )* } } macro_rules! impl_unsigned { ($($Type: ty),* ) => { $( impl Unsigned for $Type { /// Implementation based on /// [Binary GCD algorithm](https://en.wikipedia.org/wiki/Binary_GCD_algorithm) fn gcd(a: $Type, b: $Type) -> $Type { if a == b { return a; } else if a == 0 { return b; } else if b == 0 { return a; } let a_even = a % 2 == 0; let b_even = b % 2 == 0; if a_even { if b_even { // Both a & b are even return Self::gcd(a >> 1, b >> 1) << 1; } else if !b_even { // b is odd return Self::gcd(a >> 1, b); } } // a is odd, b is even if (!a_even) && b_even { return Self::gcd(a, b >> 1); } if a > b { return Self::gcd((a - b) >> 1, b); } Self::gcd((b - a) >> 1, a) } fn lcm(a: $Type, b: $Type) -> $Type { if (a == 0 && b == 0) { return 0; } a * b / Self::gcd(a, b) } } )* }; } impl_empty!( Num, (i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize) ); impl_int!( (i8, u8), (u8, u8), (i16, u16), (u16, u16), (i32, u32), (u32, u32), (i64, u64), (u64, u64), (i128, u128), (u128, u128), (isize, usize), (usize, usize) ); impl_unsigned!(u8, u16, u32, u64, u128, usize); impl_empty!(Signed, (i8, i16, i32, i64, i128, isize)); #[cfg(test)] #[cfg(not(tarpaulin_include))] mod tests { use super::*; #[test] fn test_sign() { let s = Sign::default(); assert_eq!(s, Sign::Positive); let ms = -s; assert_eq!(ms, Sign::Negative); let ns = !s; assert_eq!(ns, Sign::Negative); let a = Sign::Negative; let b = Sign::Positive; let c = Sign::Negative; let d = Sign::Positive; assert_eq!(a.partial_cmp(&b), Some(Ordering::Less)); assert_eq!(b.partial_cmp(&a), Some(Ordering::Greater)); assert_eq!(a.partial_cmp(&c), Some(Ordering::Equal)); assert_eq!(b.partial_cmp(&d), Some(Ordering::Equal)); } #[test] fn test_los() { assert_eq!(4u8.left_overflowing_sub(2).0, 2u8); assert_eq!(0u8.left_overflowing_sub(2).0, 2u8); } #[test] fn test_gcd() { assert_eq!(u8::gcd(5, 0), 5); assert_eq!(u8::gcd(0, 5), 5); assert_eq!(u8::gcd(2, 3), 1); assert_eq!(u8::gcd(2, 2), 2); assert_eq!(u8::gcd(2, 8), 2); assert_eq!(u16::gcd(36, 48), 12); } #[test] fn test_lcm() { assert_eq!(u32::lcm(2, 8), 8); assert_eq!(u16::lcm(2, 3), 6); assert_eq!(usize::lcm(15, 30), 30); assert_eq!(u128::lcm(1, 5), 5); assert_eq!(0u8, u8::lcm(0, 0)); } }