rusty-numbers/src/rational.rs

387 lines
9.7 KiB
Rust
Raw Normal View History

2020-02-12 23:10:08 -05:00
//! # Rational Numbers (fractions)
2020-02-12 22:29:57 -05:00
use crate::num::Sign::*;
use crate::num::*;
use std::cmp::{Ord, Ordering, PartialOrd};
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
2020-02-12 23:10:08 -05:00
/// Type representing a fraction
2020-02-14 17:24:51 -05:00
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
2020-02-12 22:29:57 -05:00
pub struct Frac<T: Unsigned = usize> {
numer: T,
denom: T,
2020-02-12 23:10:08 -05:00
sign: Sign,
2020-02-12 22:29:57 -05:00
}
2020-02-14 17:24:51 -05:00
#[macro_export]
/// Create a [Frac](rational/struct.Frac.html) type with signed or unsigned number literals
///
/// Accepts:
///
/// ```no-run
/// // Fractions
/// frac!(1/3);
///
/// // Whole numbers
/// frac!(5u8);
///
/// // Whole numbers and fractions
/// frac!(1 1/2);
/// ```
2020-02-14 17:24:51 -05:00
macro_rules! frac {
($w:literal $n:literal / $d:literal) => {
frac!($w) + frac!($n / $d)
2020-02-14 17:24:51 -05:00
};
($n:literal / $d:literal) => {
2020-02-19 09:39:19 -05:00
$crate::rational::Frac::new($n, $d)
2020-02-14 17:24:51 -05:00
};
($w:literal) => {
2020-02-19 09:39:19 -05:00
$crate::rational::Frac::new($w, 1)
};
2020-02-14 17:24:51 -05:00
}
#[derive(Debug, Copy, Clone, PartialEq)]
enum FracOp {
Addition,
Subtraction,
Other,
}
2020-02-19 09:39:19 -05:00
impl<T: Unsigned> Frac<T> {
/// Create a new rational number from signed or unsigned arguments
///
/// Generally, you will probably prefer to use the [frac!](../macro.frac.html) macro
/// instead, as that is easier for mixed fractions and whole numbers
pub fn new<N: Int<Un = T>>(n: N, d: N) -> Frac<T> {
2020-02-19 09:39:19 -05:00
Self::new_unreduced(n, d).reduce()
}
2020-02-14 17:24:51 -05:00
2020-02-19 09:39:19 -05:00
/// Create a new rational number from signed or unsigned arguments
/// where the resulting fraction is not reduced
pub fn new_unreduced<N: Int<Un = T>>(n: N, d: N) -> Frac<T> {
2020-02-19 09:39:19 -05:00
if d.is_zero() {
panic!("Fraction can not have a zero denominator");
}
let mut sign = Positive;
2020-02-19 09:39:19 -05:00
if n.is_neg() {
sign = !sign;
}
2020-02-14 17:24:51 -05:00
2020-02-19 09:39:19 -05:00
if d.is_neg() {
sign = !sign;
}
// Convert the possibly signed arguments to unsigned values
2020-02-19 09:39:19 -05:00
let numer = n.to_unsigned();
let denom = d.to_unsigned();
Frac { numer, denom, sign }
2020-02-18 21:29:40 -05:00
}
/// Create a new rational from all the raw parts
2020-02-19 09:39:19 -05:00
fn raw(n: T, d: T, s: Sign) -> Frac<T> {
2020-02-13 17:13:25 -05:00
if d.is_zero() {
panic!("Fraction can not have a zero denominator");
}
2020-02-12 22:29:57 -05:00
Frac {
numer: n,
denom: d,
2020-02-14 17:24:51 -05:00
sign: s,
}
.reduce()
2020-02-13 17:13:25 -05:00
}
2020-02-14 17:24:51 -05:00
/// Determine the output sign given the two input signs
fn get_sign(a: Self, b: Self, op: FracOp) -> Sign {
if op == FracOp::Addition {
return if a.sign == Positive && b.sign == Positive {
Positive
} else {
Negative
};
}
2020-02-14 17:24:51 -05:00
if a.sign != b.sign {
if op == FracOp::Subtraction && b.sign == Negative {
Positive
} else {
Negative
}
2020-02-14 17:24:51 -05:00
} else {
Positive
2020-02-14 17:24:51 -05:00
}
2020-02-13 17:13:25 -05:00
}
2020-02-14 17:24:51 -05:00
/// Convert the fraction to its simplest form
2020-02-13 17:13:25 -05:00
fn reduce(mut self) -> Self {
let gcd = T::gcd(self.numer, self.denom);
self.numer /= gcd;
self.denom /= gcd;
self
2020-02-12 22:29:57 -05:00
}
}
2020-02-19 09:39:19 -05:00
impl<T> PartialOrd for Frac<T>
where
T: Unsigned + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
2020-02-19 09:39:19 -05:00
impl<T> Ord for Frac<T>
where
T: Unsigned + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
{
fn cmp(&self, other: &Self) -> Ordering {
2020-02-19 09:39:19 -05:00
if self.sign != other.sign {
return if self.sign == Positive {
2020-02-19 09:39:19 -05:00
Ordering::Greater
} else {
2020-02-19 09:39:19 -05:00
Ordering::Less
};
}
2020-02-18 21:29:40 -05:00
2020-02-19 09:39:19 -05:00
if self.denom == other.denom {
return self.numer.cmp(&other.numer);
}
2020-02-19 09:39:19 -05:00
let mut a = self.reduce();
let mut b = other.reduce();
2020-02-19 09:39:19 -05:00
if a.denom == b.denom {
return a.numer.cmp(&b.numer);
}
2020-02-19 09:39:19 -05:00
let lcm = T::lcm(self.denom, other.denom);
let x = lcm / self.denom;
let y = lcm / other.denom;
a.numer *= x;
a.denom *= x;
b.numer *= y;
b.denom *= y;
debug_assert_eq!(
a.denom, b.denom,
"Denominators should be equal here. \n{:#?}\n{:#?}\n{:?}\n{:?}",
a, b, x, y
);
a.numer.cmp(&b.numer)
}
}
2020-02-19 09:39:19 -05:00
impl<T> Mul for Frac<T>
where
T: Unsigned + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
{
2020-02-14 12:30:09 -05:00
type Output = Self;
fn mul(self, rhs: Self) -> Self {
let numer = self.numer * rhs.numer;
let denom = self.denom * rhs.denom;
let sign = Self::get_sign(self, rhs, FracOp::Other);
2020-02-14 17:24:51 -05:00
2020-02-19 09:39:19 -05:00
Self::raw(numer, denom, sign)
2020-02-14 17:24:51 -05:00
}
}
2020-02-19 09:39:19 -05:00
impl<T> MulAssign for Frac<T>
where
T: Unsigned + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
{
fn mul_assign(&mut self, rhs: Self) {
*self = self.clone() * rhs
}
}
2020-02-19 09:39:19 -05:00
impl<T> Div for Frac<T>
where
T: Unsigned + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
{
2020-02-14 17:24:51 -05:00
type Output = Self;
fn div(self, rhs: Self) -> Self {
let numer = self.numer * rhs.denom;
let denom = self.denom * rhs.numer;
let sign = Self::get_sign(self, rhs, FracOp::Other);
2020-02-14 17:24:51 -05:00
2020-02-19 09:39:19 -05:00
Self::raw(numer, denom, sign)
2020-02-14 17:24:51 -05:00
}
}
2020-02-14 12:30:09 -05:00
2020-02-19 09:39:19 -05:00
impl<T> DivAssign for Frac<T>
where
T: Unsigned + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
{
fn div_assign(&mut self, rhs: Self) {
*self = self.clone() / rhs
}
}
2020-02-19 09:39:19 -05:00
impl<T> Add for Frac<T>
where
T: Unsigned + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
{
2020-02-14 17:24:51 -05:00
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
let a = self;
let b = rhs;
// If the sign of one input differs,
// subtraction is equivalent
if a.sign == Negative && b.sign == Positive {
return b - -a;
} else if a.sign == Positive && b.sign == Negative {
return a - -b;
2020-02-14 12:30:09 -05:00
}
2020-02-14 17:24:51 -05:00
// Find a common denominator if needed
if a.denom != b.denom {
// Let's just use the simplest method, rather than
// worrying about reducing to the least common denominator
let numer = (a.numer * b.denom) + (b.numer * a.denom);
let denom = a.denom * b.denom;
let sign = Self::get_sign(a, b, FracOp::Addition);
2020-02-14 17:24:51 -05:00
2020-02-19 09:39:19 -05:00
return Self::raw(numer, denom, sign);
2020-02-14 17:24:51 -05:00
}
let numer = a.numer + b.numer;
let denom = self.denom;
let sign = Self::get_sign(a, b, FracOp::Addition);
2020-02-14 17:24:51 -05:00
2020-02-19 09:39:19 -05:00
Self::raw(numer, denom, sign)
2020-02-14 17:24:51 -05:00
}
}
2020-02-19 09:39:19 -05:00
impl<T> AddAssign for Frac<T>
where
T: Unsigned + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
{
fn add_assign(&mut self, rhs: Self) {
*self = self.clone() + rhs
}
}
2020-02-19 09:39:19 -05:00
impl<T> Sub for Frac<T>
where
T: Unsigned + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
{
2020-02-14 17:24:51 -05:00
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
let a = self;
let b = rhs;
2020-02-14 17:24:51 -05:00
if a.sign == Positive && b.sign == Negative {
return a + -b;
} else if a.sign == Negative && b.sign == Positive {
return -(b + -a);
}
if a.denom != b.denom {
let (numer, overflowed) = (a.numer * b.denom).left_overflowing_sub(b.numer * a.denom);
let denom = a.denom * b.denom;
let sign = Self::get_sign(a, b, FracOp::Subtraction);
let res = Self::raw(numer, denom, sign);
return if overflowed { -res } else { res };
}
let (numer, overflowed) = a.numer.left_overflowing_sub(b.numer);
let denom = a.denom;
let sign = Self::get_sign(a, b, FracOp::Subtraction);
let res = Self::raw(numer, denom, sign);
if overflowed { -res } else { res }
}
}
2020-02-19 09:39:19 -05:00
impl<T> SubAssign for Frac<T>
where
T: Unsigned + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
{
fn sub_assign(&mut self, rhs: Self) {
*self = self.clone() - rhs
2020-02-14 12:30:09 -05:00
}
2020-02-13 17:13:25 -05:00
}
2020-02-12 23:10:08 -05:00
impl<T: Unsigned> Neg for Frac<T> {
type Output = Self;
fn neg(self) -> Self::Output {
let mut out = self;
2020-02-12 23:10:08 -05:00
out.sign = !self.sign;
out
}
}
2020-02-12 22:29:57 -05:00
#[cfg(test)]
2020-02-14 12:11:57 -05:00
mod tests {
use super::*;
#[test]
fn mul_test() {
let frac1 = frac!(1 / 3u8);
let frac2 = frac!(2u8 / 3);
2020-02-14 12:11:57 -05:00
let expected = frac!(2u8 / 9);
2020-02-14 12:11:57 -05:00
assert_eq!(frac1 * frac2, expected);
}
2020-02-14 17:24:51 -05:00
#[test]
fn add_test() {
assert_eq!(frac!(5 / 6), frac!(1 / 3) + frac!(1 / 2), "1/3 + 1/2");
assert_eq!(frac!(1 / 3), frac!(2 / 3) + -frac!(1 / 3), "2/3 + -1/3");
2020-02-14 17:24:51 -05:00
}
#[test]
fn sub_test() {
assert_eq!(4u8.left_overflowing_sub(2).0, 2u8);
assert_eq!(0u8.left_overflowing_sub(2).0, 2u8);
assert_eq!(frac!(1 / 6), frac!(1 / 2) - frac!(1 / 3), "1/2 - 1/3");
}
2020-02-18 21:29:40 -05:00
#[test]
fn cmp_test() {
2020-02-19 09:39:19 -05:00
assert!(-frac!(5 / 3) < frac!(1 / 10_000));
assert!(frac!(1 / 10_000) > -frac!(10));
assert!(frac!(1 / 3) < frac!(1 / 2));
assert_eq!(frac!(1 / 2), frac!(3 / 6));
2020-02-18 21:29:40 -05:00
}
#[test]
fn negative_fractions() {
assert_eq!(-frac!(1 / 3), -frac!(2 / 3) + frac!(1 / 3), "-2/3 + 1/3");
assert_eq!(frac!(1), frac!(1 / 3) - -frac!(2 / 3), "1/3 - -2/3");
assert_eq!(-frac!(1), -frac!(2 / 3) - frac!(1 / 3), "-2/3 - +1/3");
assert_eq!(-frac!(1), -frac!(2 / 3) + -frac!(1 / 3), "-2/3 + -1/3");
}
2020-02-14 17:24:51 -05:00
#[test]
fn macro_test() {
let frac1 = frac!(1 / 3);
let frac2 = frac!(1u32 / 3);
2020-02-14 17:24:51 -05:00
assert_eq!(frac1, frac2);
let frac1 = -frac!(1 / 2);
let frac2 = -frac!(1u32 / 2);
2020-02-14 17:24:51 -05:00
assert_eq!(frac1, frac2);
assert_eq!(frac!(3 / 2), frac!(1 1/2));
assert_eq!(frac!(3 / 1), frac!(3));
2020-02-14 17:24:51 -05:00
}
2020-02-14 12:11:57 -05:00
}