//! # Rational Numbers (fractions) use core::cmp::{Ord, Ordering, PartialOrd}; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use crate::num::Sign::{Negative, Positive}; use crate::num::{FracOp, Int, Sign, Unsigned}; /// 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 { numerator: T, denominator: T, sign: Sign, } /// 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_export] 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) }; } 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 /// /// # Panics /// if `d` is 0, this constructor will panic 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 /// /// # Panics /// if `d` is 0, this constructor will panic pub fn new_unreduced>(n: N, d: N) -> Frac { assert!(!d.is_zero(), "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 numerator = n.to_unsigned(); let denominator = d.to_unsigned(); Frac { numerator, denominator, sign, } } /// Create a new, reduced rational from all the raw parts /// /// # Panics /// if `d` is 0, this constructor will panic fn raw(n: T, d: T, s: Sign) -> Frac { assert!(!d.is_zero(), "Fraction can not have a zero denominator"); Frac { numerator: n, denominator: d, sign: s, } .reduce() } /// Determine the output sign given the two input signs fn get_sign(a: Self, b: Self, op: FracOp) -> Sign { // -a + -b = -c if op == FracOp::Addition && a.sign == Negative && b.sign == Negative { return Negative; } // a - -b = c if op == FracOp::Subtraction && a.sign == Positive && b.sign == Negative { return Positive; } if a.sign == b.sign { Positive } else { Negative } } /// Convert the fraction to its simplest form pub fn reduce(mut self) -> Self { let gcd = T::gcd(self.numerator, self.denominator); self.numerator /= gcd; self.denominator /= gcd; self } } impl PartialOrd for Frac { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Frac { fn cmp(&self, other: &Self) -> Ordering { if self.sign != other.sign { return if self.sign == Positive { Ordering::Greater } else { Ordering::Less }; } if self.denominator == other.denominator { return self.numerator.cmp(&other.numerator); } let mut a = self.reduce(); let mut b = other.reduce(); if a.denominator == b.denominator { return a.numerator.cmp(&b.numerator); } let lcm = T::lcm(self.denominator, other.denominator); let x = lcm / self.denominator; let y = lcm / other.denominator; a.numerator *= x; a.denominator *= x; b.numerator *= y; b.denominator *= y; a.numerator.cmp(&b.numerator) } } impl Mul for Frac { type Output = Self; fn mul(self, rhs: Self) -> Self { let numerator = self.numerator * rhs.numerator; let denominator = self.denominator * rhs.denominator; let sign = Self::get_sign(self, rhs, FracOp::Other); Self::raw(numerator, denominator, sign) } } impl MulAssign for Frac { fn mul_assign(&mut self, rhs: Self) { *self = *self * rhs; } } impl Div for Frac { type Output = Self; fn div(self, rhs: Self) -> Self { let numerator = self.numerator * rhs.denominator; let denominator = self.denominator * rhs.numerator; let sign = Self::get_sign(self, rhs, FracOp::Other); Self::raw(numerator, denominator, sign) } } impl DivAssign for Frac { fn div_assign(&mut self, rhs: Self) { *self = *self / rhs; } } impl Add for Frac { 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.denominator != b.denominator { // Let's just use the simplest method, rather than // worrying about reducing to the least common denominator let numerator = (a.numerator * b.denominator) + (b.numerator * a.denominator); let denominator = a.denominator * b.denominator; let sign = Self::get_sign(a, b, FracOp::Addition); return Self::raw(numerator, denominator, sign); } let numerator = a.numerator + b.numerator; let denominator = self.denominator; let sign = Self::get_sign(a, b, FracOp::Addition); Self::raw(numerator, denominator, sign) } } impl AddAssign for Frac { fn add_assign(&mut self, rhs: Self) { *self = *self + rhs; } } impl Sub for Frac { 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.denominator != b.denominator { let (numerator, overflowed) = (a.numerator * b.denominator).left_overflowing_sub(b.numerator * a.denominator); let denominator = a.denominator * b.denominator; let sign = Self::get_sign(a, b, FracOp::Subtraction); let res = Self::raw(numerator, denominator, sign); return if overflowed { -res } else { res }; } let (numerator, overflowed) = a.numerator.left_overflowing_sub(b.numerator); let denominator = a.denominator; let sign = Self::get_sign(a, b, FracOp::Subtraction); let res = Self::raw(numerator, denominator, sign); if overflowed { -res } else { res } } } impl SubAssign for Frac { 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(test)] #[cfg(not(tarpaulin_include))] mod tests { use super::*; #[test] #[should_panic(expected = "Fraction can not have a zero denominator")] fn zero_denominator() { Frac::raw(1u8, 0u8, Sign::default()); } #[test] #[should_panic(expected = "Fraction can not have a zero denominator")] fn zero_denominator_new() { frac!(1 / 0); } #[test] fn test_get_sign() { assert_eq!( Sign::Positive, Frac::get_sign(frac!(1), frac!(-1), FracOp::Subtraction) ); assert_eq!( Sign::Negative, Frac::get_sign(frac!(-1), frac!(-1), FracOp::Addition) ); assert_eq!( Sign::Negative, Frac::get_sign(frac!(-1), frac!(1), FracOp::Addition) ); assert_eq!( Sign::Negative, Frac::get_sign(frac!(-1), frac!(1), FracOp::Subtraction) ); assert_eq!( Sign::Negative, Frac::get_sign(frac!(-1), frac!(1), FracOp::Other) ); } #[test] fn test_cmp() { assert_eq!(Ordering::Greater, frac!(3 / 4).cmp(&frac!(1 / 4))); assert_eq!(Ordering::Less, frac!(1 / 4).cmp(&frac!(3 / 4))); assert_eq!(Ordering::Equal, frac!(1 / 2).cmp(&frac!(4 / 8))); } #[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)); } }