//! # Rational Numbers (fractions) 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}; /// Type representing a fraction /// /// There are three basic constructors: /// /// ``` /// use rusty_numbers::frac; /// use rusty_numbers::rational::Frac; /// /// // Macro /// let reduced_macro = frac!(3 / 4); /// /// // Frac::new (called by the macro) /// let reduced = Frac::new(3, 4); /// # assert_eq!(reduced_macro, reduced); /// /// // Frac::new_unreduced /// let unreduced = Frac::new_unreduced(4, 16); /// ``` #[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 /// /// Example: /// ``` /// use rusty_numbers::frac; /// /// // Proper fractions /// frac!(1 / 3); /// /// // Improper fractions /// frac!(4 / 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 { Addition, 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 = Positive; if n.is_neg() { sign = !sign; } if d.is_neg() { sign = !sign; } // Convert the possibly signed arguments to unsigned values let numer = n.to_unsigned(); let denom = d.to_unsigned(); Frac { numer, denom, sign } } /// Create a new rational from all 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, op: FracOp) -> Sign { let mut output = Sign::default(); if op == FracOp::Addition && !(a.sign == Positive && b.sign == Positive) { output = Negative; } if a.sign != b.sign { output = if op == FracOp::Subtraction && b.sign == Negative { Positive } else { Negative } } output } /// Convert the fraction to its simplest form pub 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 == 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 { 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; 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 * 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 / 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 a.sign == Negative && b.sign == Positive { return b - -a; } else if a.sign == Positive && b.sign == Negative { return a - -b; } // 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); return Self::raw(numer, denom, sign); } let numer = a.numer + b.numer; let denom = self.denom; let sign = Self::get_sign(a, b, FracOp::Addition); 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 + 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; 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 } } } impl SubAssign for Frac where T: Unsigned + Add + Sub + Mul + Div, { fn sub_assign(&mut self, rhs: Self) { *self = *self - rhs } } impl Neg for Frac { type Output = Self; fn neg(self) -> Self::Output { let mut out = self; out.sign = !self.sign; out } } #[cfg_attr(tarpaulin, skip)] #[cfg(test)] mod tests { use super::*; #[test] #[should_panic(expected = "Fraction can not have a zero denominator")] fn zero_denom() { Frac::raw(1u8, 0u8, Sign::default()); } #[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)); assert_eq!(-frac!(1/2), frac!(-1/2)); assert_eq!(-frac!(1/2), frac!(1/-2)); assert_eq!(frac!(1/2), frac!(-1/-2)); } }