//! # Rational Numbers (fractions) use crate::num::*; use std::cmp::{Ord, Ordering, PartialOrd}; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; /// Type representing a fraction #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct Frac { numer: T, denom: T, sign: Sign, } #[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); /// ``` macro_rules! frac { ($w:literal $n:literal / $d:literal) => { frac!($w) + frac!($n / $d) }; ($n:literal / $d:literal) => { $crate::rational::Frac::new($n, $d) }; ($w:literal) => { $crate::rational::Frac::new($w, 1) }; } #[derive(Debug, Copy, Clone, PartialEq)] enum FracOp { Subtraction, Other, } impl Frac { /// 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: N, d: N) -> Frac { Self::new_unreduced(n, d).reduce() } /// Create a new rational number from signed or unsigned arguments /// where the resulting fraction is not reduced pub fn new_unreduced>(n: N, d: N) -> Frac { if d.is_zero() { panic!("Fraction can not have a zero denominator"); } let mut sign = Sign::Positive; if n.is_neg() { sign = !sign; } if d.is_neg() { sign = !sign; } let numer = n.to_unsigned(); let denom = d.to_unsigned(); Frac { numer, denom, sign, } } /// Create a new rational from the raw parts fn raw(n: T, d: T, s: Sign) -> Frac { if d.is_zero() { panic!("Fraction can not have a zero denominator"); } Frac { numer: n, denom: d, sign: s, }.reduce() } /// Determine the output sign given the two input signs fn get_sign(a: Self, b: Self, c: FracOp) -> Sign { if a.sign != b.sign { if c == FracOp::Subtraction && b.sign == Sign::Negative { Sign::Positive } else { Sign::Negative } } else { Sign::Positive } } /// Convert the fraction to its simplest form fn reduce(mut self) -> Self { let gcd = T::gcd(self.numer, self.denom); self.numer /= gcd; self.denom /= gcd; self } } impl PartialOrd for Frac where T: Unsigned + Add + Sub + Mul + Div, { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Frac where T: Unsigned + Add + Sub + Mul + Div, { fn cmp(&self, other: &Self) -> Ordering { if self.sign != other.sign { return if self.sign == Sign::Positive { Ordering::Greater } else { Ordering::Less }; } if self.denom == other.denom { return self.numer.cmp(&other.numer); } let mut a = self.reduce(); let mut b = other.reduce(); if a.denom == b.denom { assert!(false, "{:#?}\n{:#?}", a, b); return a.numer.cmp(&b.numer); } 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) } } impl Mul for Frac where T: Unsigned + Add + Sub + Mul + Div, { 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); Self::raw(numer, denom, sign) } } impl MulAssign for Frac where T: Unsigned + Add + Sub + Mul + Div, { fn mul_assign(&mut self, rhs: Self) { *self = self.clone() * rhs } } impl Div for Frac where T: Unsigned + Add + Sub + Mul + Div, { 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); Self::raw(numer, denom, sign) } } impl DivAssign for Frac where T: Unsigned + Add + Sub + Mul + Div, { fn div_assign(&mut self, rhs: Self) { *self = self.clone() / rhs } } impl Add for Frac where T: Unsigned + Add + Sub + Mul + Div, { 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 self.sign != rhs.sign { if a > b { return a - b; } else if a < b { return b - a; } } // 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::Other); return Self::raw(numer, denom, sign); } let numer = a.numer + b.numer; let denom = self.denom; let sign = Self::get_sign(a, b, FracOp::Other); Self::raw(numer, denom, sign) } } impl AddAssign for Frac where T: Unsigned + Add + Sub + Mul + Div, { fn add_assign(&mut self, rhs: Self) { *self = self.clone() + rhs } } impl Sub for Frac where T: Unsigned + Add + Sub + Mul + Div, { type Output = Self; fn sub(self, rhs: Self) -> Self::Output { let a = self; let b = rhs; // @TODO handle sign "overflow" conditions if a.denom != b.denom { let numer = (a.numer * b.denom) - (b.numer * a.denom); let denom = a.denom * b.denom; let sign = Self::get_sign(a, b, FracOp::Subtraction); return Self::raw(numer, denom, sign); } let numer = a.numer - b.numer; let denom = a.denom; let sign = Self::get_sign(a, b, FracOp::Subtraction); Self::raw(numer, denom, sign) } } impl SubAssign for Frac where T: Unsigned + Add + Sub + Mul + Div, { fn sub_assign(&mut self, rhs: Self) { *self = self.clone() - rhs } } impl Neg for Frac { type Output = Self; fn neg(self) -> Self::Output { let mut out = self.clone(); out.sign = !self.sign; out } } #[cfg(test)] mod tests { use super::*; #[test] fn mul_test() { let frac1 = frac!(1 / 3u8); let frac2 = frac!(2u8 / 3); let expected = frac!(2u8 / 9); assert_eq!(frac1 * frac2, expected); } #[test] fn add_test() { assert_eq!(frac!(5 / 6), frac!(1 / 3) + frac!(1 / 2)); assert_eq!(frac!(1 / 3), frac!(2 / 3) + -frac!(1 / 3), "2/3 + -1/3"); // assert_eq!(-frac!(1 / 3), -frac!(2 / 3) + frac!(1 / 3), "-2/3 + 1/3"); } #[test] fn sub_test() { assert_eq!(frac!(1 / 6), frac!(1 / 2) - frac!(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"); } #[test] fn cmp_test() { 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)); } #[test] fn macro_test() { let frac1 = frac!(1 / 3); let frac2 = frac!(1u32 / 3); assert_eq!(frac1, frac2); let frac1 = -frac!(1 / 2); let frac2 = -frac!(1u32 / 2); assert_eq!(frac1, frac2); assert_eq!(frac!(3 / 2), frac!(1 1/2)); assert_eq!(frac!(3 / 1), frac!(3)); } }