Skip to content

Commit db84b2a

Browse files
committed
pull in log2
1 parent 794a4cf commit db84b2a

3 files changed

Lines changed: 69 additions & 6 deletions

File tree

src/real/arithmetic.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ impl Class {
544544
Self::ln_computable(base).multiply(Self::ln_computable(&rationals::TEN).inverse())
545545
}
546546
Log2(base) => {
547-
Self::ln_computable(base).multiply(Self::ln_computable(&*rationals::TWO).inverse())
547+
Self::ln_computable(base).multiply(Self::ln_computable(&rationals::TWO).inverse())
548548
}
549549
SinPi(rational) => {
550550
let argument =
@@ -1533,7 +1533,8 @@ impl Real {
15331533
Irrational => "scaled-computable",
15341534
Pi | PiPow(_) | PiInv | PiExp(_) | PiInvExp(_) | PiSqrt(_) | ConstProduct(_)
15351535
| ConstOffset(_) | ConstProductSqrt(_) | Sqrt(_) | Exp(_) | Ln(_) | LnAffine(_)
1536-
| LnProduct(_) | Log10(_) | Log2(_) | SinPi(_) | TanPi(_) => "symbolic-nonzero-scale",
1536+
| LnProduct(_) | Log10(_) | Log2(_) | SinPi(_) | TanPi(_) =>
1537+
"symbolic-nonzero-scale",
15371538
}
15381539
);
15391540

@@ -3345,7 +3346,7 @@ impl Real {
33453346

33463347
crate::trace_dispatch!("real", "log2", "rational-log2-special-form");
33473348
let computable =
3348-
Class::ln_computable(&r).multiply(Class::ln_computable(&*rationals::TWO).inverse());
3349+
Class::ln_computable(&r).multiply(Class::ln_computable(&rationals::TWO).inverse());
33493350
Ok(Self {
33503351
rational: Rational::one(),
33513352
class: Log2(r),

src/real/tests.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,20 @@ mod tests {
11621162
}
11631163
}
11641164

1165+
#[test]
1166+
fn log2_of_fractional_non_power_rational_matches_f64() {
1167+
for (numerator, denominator) in [(3_i64, 8_u64), (5, 12), (17, 1024)] {
1168+
let value = Real::new(Rational::fraction(numerator, denominator).unwrap())
1169+
.log2()
1170+
.unwrap();
1171+
assert_close(
1172+
value,
1173+
((numerator as f64) / (denominator as f64)).log2(),
1174+
1e-12,
1175+
);
1176+
}
1177+
}
1178+
11651179
#[test]
11661180
fn log2_of_negative_errors() {
11671181
let negative = Real::new(Rational::new(-3));
@@ -1206,6 +1220,30 @@ mod tests {
12061220
assert_eq!(quotient, expected);
12071221
}
12081222

1223+
#[test]
1224+
fn log2_ln_quotient_preserves_exact_scaled_logs() {
1225+
let numerator = Real::new(Rational::new(9)).ln().unwrap();
1226+
let denominator = Real::new(Rational::new(4)).ln().unwrap();
1227+
let quotient = (numerator / denominator).unwrap();
1228+
let expected = Real::new(Rational::new(3)).log2().unwrap();
1229+
assert_eq!(quotient, expected);
1230+
1231+
let numerator = Real::new(Rational::new(32)).ln().unwrap();
1232+
let denominator = Real::new(Rational::fraction(1, 2).unwrap()).ln().unwrap();
1233+
assert_eq!((numerator / denominator).unwrap(), Rational::new(-5));
1234+
}
1235+
1236+
#[test]
1237+
fn log2_ln_quotient_ignores_warmed_numerator_cache() {
1238+
let numerator = Real::new(Rational::new(5)).ln().unwrap();
1239+
let warmed = numerator.to_f64_approx().unwrap();
1240+
assert!((warmed - 5.0_f64.ln()).abs() < 1e-12);
1241+
1242+
let denominator = Real::new(Rational::new(2)).ln().unwrap();
1243+
let quotient = (numerator / denominator).unwrap();
1244+
assert_close(quotient, 5.0_f64.log2(), 1e-12);
1245+
}
1246+
12091247
fn assert_close(value: Real, expected: f64, tolerance: f64) {
12101248
let actual: f64 = value.into();
12111249
let scale = expected.abs().max(1.0);

src/simple.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ impl Simple {
280280
};
281281
let mut value = first.value(names)?;
282282
for operand in operands {
283-
value = value + operand.value(names)?;
283+
value += operand.value(names)?;
284284
}
285285
Ok(value)
286286
}
@@ -323,7 +323,7 @@ impl Simple {
323323
let mut value: Real = self.operands.first().unwrap().value(names)?;
324324
let operands = self.operands.iter().skip(1);
325325
for operand in operands {
326-
value = value - (operand.value(names)?);
326+
value -= operand.value(names)?;
327327
}
328328
Ok(value)
329329
}
@@ -353,7 +353,7 @@ impl Simple {
353353
};
354354
let mut value = first.value(names)?;
355355
for operand in operands {
356-
value = value * operand.value(names)?;
356+
value *= operand.value(names)?;
357357
}
358358
Ok(value)
359359
}
@@ -703,6 +703,8 @@ mod tests {
703703
"(l 5)",
704704
"(log 5)",
705705
"(log10 5)",
706+
"(log2 5)",
707+
"(lg 5)",
706708
"(exp 5)",
707709
"(e 5)",
708710
"(sqrt 5)",
@@ -823,6 +825,28 @@ mod tests {
823825
}
824826
}
825827

828+
#[test]
829+
fn log2_aliases_parse_as_base2() {
830+
let empty = HashMap::new();
831+
for case in ["(log2 1024)", "(lg 1024)"] {
832+
let xpr: Simple = case.parse().unwrap();
833+
let result = xpr.evaluate(&empty).unwrap();
834+
assert!(result.is_integer(), "{case}");
835+
assert_eq!(format!("{result}"), "10", "{case}");
836+
}
837+
}
838+
839+
#[test]
840+
fn log2_parser_rejects_bad_domain_and_arity() {
841+
let empty = HashMap::new();
842+
843+
let negative: Simple = "(lg -1)".parse().unwrap();
844+
assert_eq!(negative.evaluate(&empty), Err(Problem::NotANumber));
845+
846+
let wrong_arity: Simple = "(log2 2 4)".parse().unwrap();
847+
assert_eq!(wrong_arity.evaluate(&empty), Err(Problem::ParseError));
848+
}
849+
826850
#[test]
827851
fn div_pi_e_4() {
828852
let empty = HashMap::new();

0 commit comments

Comments
 (0)