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 <ava@sunnypup.io>
This commit is contained in:
Ava Apples Affine 2025-05-16 15:04:53 -07:00
parent 3174494001
commit a48fc52fab

View file

@ -42,7 +42,7 @@ trait Numeric: Copy + Clone + Debug + FromStr + Into<String> {
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<Number> 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 :)
if intermediate_numer.fract() == 0.0 && intermediate_denom.fract() == 0.0 {
Number::Fra(Fraction(intermediate_numer as isize, intermediate_denom as isize)
.simplify())
} else {
(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)
} 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()
}
}
// 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::<Number>()
.unwrap()
.make_inexact() {
return
}
assert!(false)
}
}