From a48fc52fab2f386c66c100286277ece61f1604a9 Mon Sep 17 00:00:00 2001 From: Ava Affine Date: Fri, 16 May 2025 15:04:53 -0700 Subject: [PATCH] Number: fix frac.pow(frac) issue and better make_inexact() for sci This commit fixes a bad procedure used to raise a fraction to the power of another fraction, and adds additional test cases for this. It also fixes a todo in the number package for implementing make_inexact on ScientificNotation numbers such that they can handle negative exponents. Signed-off-by: Ava Affine --- mycelium/src/number.rs | 106 ++++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 48 deletions(-) diff --git a/mycelium/src/number.rs b/mycelium/src/number.rs index f581531..21d2b39 100644 --- a/mycelium/src/number.rs +++ b/mycelium/src/number.rs @@ -39,10 +39,10 @@ pub const E_SCIENTIFIC_OPERAND: &str = "couldnt parse 32 bit float operand"; pub const E_SCIENTIFIC_POWER: &str = "couldnt parse integer power"; trait Numeric: Copy + Clone + Debug + FromStr + Into { - fn is_exact(&self) -> bool; - fn make_inexact(&self) -> Float; - fn make_exact(&self) -> Fraction; - } + fn is_exact(&self) -> bool; + fn make_inexact(&self) -> Float; + fn make_exact(&self) -> Fraction; +} #[derive(Copy, Clone, Debug, PartialEq)] @@ -321,53 +321,38 @@ impl Pow for Number { type Output = Number; fn pow(self, rhs: Number) -> Self::Output { if self.is_exact() && rhs.is_exact() { - let Fraction(lnum, lden) = self.make_exact(); - let Fraction(mut rnum, mut rden) = rhs.make_exact(); + let Fraction(mut lnum, mut lden) = self + .make_exact() + .simplify(); + let Fraction(rnum, rden) = rhs + .make_exact() + .simplify(); - // normalize the negative to the top of the fraction - if rden < 0 { - rnum = 0 - rnum; - rden = 0 - rden; + // flip first frac if second one is negative + if rnum < 0 { + let tmp = lnum; + lnum = lden; + lden = tmp; } + // apply fractional exponent (denominator) + let mut intermediate_numer = f64::powf(lnum as f64, 1.0 / rden as f64); + let mut intermediate_denom = f64::powf(lden as f64, 1.0 / rden as f64); + // apply whole exponent (numerator) - let mut intermediate_numer = - pow::pow(lnum, rnum.abs() as usize) as f64; - let mut intermediate_denom = - pow::pow(lden, rnum.abs() as usize) as f64; + intermediate_numer = + f64::powf(intermediate_numer, rnum.abs() as f64); + intermediate_denom = + f64::powf(intermediate_denom, rnum.abs() as f64); - // handle negative exponent - if rnum < 0 { - intermediate_numer = 1.0 / intermediate_numer; - intermediate_denom = 1.0 / intermediate_denom; - } - - // dont bother taking an nth root where n=1 - if rden == 1 { - if intermediate_numer.fract() == 0.0 && - intermediate_denom.fract() == 0.0 { - // return whatever the float decides :) - (intermediate_numer / intermediate_denom).into() - - // we still have whole numbers everywhere - } else { - Number::Fra(Fraction(intermediate_numer as isize, intermediate_denom as isize)) - } - - // gotta take an nth root (right hand denom > 1) + if intermediate_numer.fract() == 0.0 && intermediate_denom.fract() == 0.0 { + Number::Fra(Fraction(intermediate_numer as isize, intermediate_denom as isize) + .simplify()) } else { - let num_res = - f64::powf(intermediate_numer as f64, 1.0 / rden as f64); - let den_res = - f64::powf(intermediate_denom as f64, 1.0 / rden as f64); - if num_res.fract() == 0.0 && den_res.fract() == 0.0 { - Number::Fra(Fraction(num_res as isize, den_res as isize)) - - } else { - (num_res / den_res).into() - } + (intermediate_numer / intermediate_denom).into() } + // one or both are already inexact } else { let Float(l) = self.make_inexact(); let Float(r) = rhs.make_inexact(); @@ -444,8 +429,21 @@ impl Numeric for SymbolicNumber { impl Fraction { fn simplify(&self) -> Fraction { - let g = gcd(self.0, self.1); - Fraction(self.0 / g, self.1 / g) + let mut n = self.0; + let mut d = self.1; + /* normalize negative to numerator + * if numerator is also negative this becomes a positive frac + */ + if self.1 < 0 { + d = 0 - self.1; + n = 0 - self.0; + } + + /* divide both by their greatest common divisor + * leading to simplest possible form + */ + let g = gcd(n, d); + Fraction(n / g, d / g) } } @@ -572,8 +570,7 @@ impl Numeric for ScientificNotation { } fn make_inexact(&self) -> Float { - // TODO: This pow function needs to be replaced with one that can handle negative exponents - Float(self.0 as f64 * pow::pow(10, self.1 as usize) as f64) + Float(self.0 as f64 * f64::powi(10.0, self.1 as i32) as f64) } fn make_exact(&self) -> Fraction { @@ -750,7 +747,8 @@ mod tests { vec!["2/1", "2/1", "4/1"], vec!["2/1", "2/-1", "1/4"], vec!["2/1", "2/2", "2/1"], - vec!["2/1", "2.0", "4/1"] + vec!["2/1", "2.0", "4/1"], + vec!["27/8", "2/-3", "4/9"] ]; cases.iter().for_each(|case| { @@ -779,4 +777,16 @@ mod tests { assert!(x < z); }); } + + #[test] + fn float_negative_exponent_case() { + if let Float(0.1) = "1e-1" + .parse::() + .unwrap() + .make_inexact() { + return + } + + assert!(false) + } }