diff --git a/types/src/number/math.rs b/types/src/number/math.rs index bf9aef2..a5ef5b7 100644 --- a/types/src/number/math.rs +++ b/types/src/number/math.rs @@ -2,7 +2,7 @@ * Eryn Wells */ -use number::Int; +use number::{Int, Flt}; pub trait GCD { /// Find the greatest common divisor of `self` and another number. @@ -14,6 +14,11 @@ pub trait LCM { fn lcm(self, other: Self) -> Self; } +pub trait Rational { + /// Convert `self` into a rational number -- the quotient of two whole numbers. + fn to_rational(self) -> (Int, Int); +} + impl GCD for Int { fn gcd(self, other: Int) -> Int { let (mut a, mut b) = if self > other { @@ -43,6 +48,30 @@ impl LCM for Int { } } +impl Rational for Int { + fn to_rational(self) -> (Int, Int) { (self, 1) } +} + +impl Rational for Flt { + fn to_rational(self) -> (Int, Int) { + // Convert the float to a fraction by iteratively multiplying by 10 until the fractional part of the float is 0.0. + let whole_part = self.trunc(); + let mut p = self.fract(); + let mut q = 1.0; + while p.fract() != 0.0 { + p *= 10.0; + q *= 10.0; + } + p += whole_part * q; + + // Integers from here down. Reduce the fraction before returning. + let p = p as Int; + let q = q as Int; + let gcd = p.gcd(q); + (p / gcd, q / gcd) + } +} + #[cfg(test)] mod tests { use super::{LCM, GCD}; diff --git a/types/src/number/real.rs b/types/src/number/real.rs index f8b7f3c..19a98ed 100644 --- a/types/src/number/real.rs +++ b/types/src/number/real.rs @@ -24,13 +24,19 @@ impl Real { /// /// assert_eq!(Real::Integer(12).reduce(), Real::Integer(12)); /// assert_eq!(Real::Rational(2, 4).reduce(), Real::Rational(1, 2)); + /// assert_eq!(Real::Rational(3, 7).reduce(), Real::Rational(3, 7)); /// assert_eq!(Real::Irrational(2.4).reduce(), Real::Irrational(2.4)); /// ``` pub fn reduce(self) -> Real { match self { Real::Rational(p, q) => { let gcd = p.gcd(q); - Real::Rational(p / gcd, q / gcd) + if gcd == 1 { + self + } + else { + Real::Rational(p / gcd, q / gcd) + } }, _ => self } @@ -75,6 +81,23 @@ impl Real { } /// Demote a Real to the next lowest type, if possible. + /// + /// # Examples + /// + /// Integers can't reduce. + /// + /// ``` + /// use sibiltypes::number::Real; + /// assert!(Real::Integer(3).demote_once().is_none()); + /// ``` + /// + /// Rationals can be demoted in certain cases. + /// + /// ``` + /// use sibiltypes::number::Real; + /// assert_eq!(Real::Rational(4, 1).demote_once(), Some(Real::Integer(4))); + /// assert!(Real::Rational(4, 7).demote_once().is_none()); + /// ``` pub fn demote_once(self) -> Option { match self { Real::Integer(_) => None, @@ -84,17 +107,10 @@ impl Real { else { None }, - // TODO: Convert an irrational into a fraction. Real::Irrational(v) => { - let whole_part = v.trunc(); - let mut p = v.fract(); - let mut q = 1.0; - while p.fract() != 0.0 { - p *= 10.0; - q *= 10.0; - } - p += whole_part * q; - Some(Real::Rational(p as Int, q as Int).reduce()) + let (p, q) = v.to_rational(); + // No need to reduce here since p/q is already reduced. + Some(Real::Rational(p, q)) } } }