360 lines
9.3 KiB
Rust
360 lines
9.3 KiB
Rust
//! Numeric Helper Traits
|
|
//!
|
|
//! Home to the numeric trait chain of doom, aka `Unsigned`
|
|
#![allow(unused_comparisons)]
|
|
use core::cmp::{max, min, 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_attr(tarpaulin, skip)]
|
|
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<Ordering> {
|
|
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<Output = Self> + Sub<Output = Self> + Mul<Output = Self> + Div<Output = Self>
|
|
{
|
|
/// Find the greatest common denominator of two numbers
|
|
fn gcd(a: Self, b: Self) -> Self;
|
|
|
|
/// Euclid gcd algorithm
|
|
fn e_gcd(a: Self, b: Self) -> Self;
|
|
|
|
/// Stein gcd algorithm
|
|
fn stein_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 e_gcd(x: $Type, y: $Type) -> $Type {
|
|
let mut x = x;
|
|
let mut y = y;
|
|
while y != 0 {
|
|
let t = y;
|
|
y = x % y;
|
|
x = t;
|
|
}
|
|
x
|
|
}
|
|
|
|
fn stein_gcd(a: Self, b: Self) -> Self {
|
|
match ((a, b), (a & 1, b & 1)) {
|
|
((x, y), _) if x == y => y,
|
|
((0, x), _) | ((x, 0), _) => x,
|
|
((x, y), (0, 1)) | ((y, x), (1, 0)) => Self::stein_gcd(x >> 1, y),
|
|
((x, y), (0, 0)) => Self::stein_gcd(x >> 1, y >> 1) << 1,
|
|
((x, y), (1, 1)) => {
|
|
let (x, y) = (min(x, y), max(x, y));
|
|
Self::stein_gcd((y - x) >> 1, x)
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
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_attr(tarpaulin, skip)]
|
|
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);
|
|
assert_eq!(u16::e_gcd(36, 48), 12);
|
|
assert_eq!(u16::stein_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));
|
|
}
|
|
}
|