rusty-numbers/src/num.rs

328 lines
8.1 KiB
Rust

//! 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<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;
/// 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));
}
}