use crate::kernel::expr::PredicateKind;
use crate::kernel::{ExprData, ExprId, ExprPool};
use rug::float::Round;
use rug::{Float, Integer, Rational};
pub fn integer_to_f64(n: &Integer) -> f64 {
if n.significant_bits() <= f64::MANTISSA_DIGITS {
return n.to_f64();
}
let (mut f, dir) = Float::with_val_round(f64::MANTISSA_DIGITS, n, Round::Nearest);
f.subnormalize_ieee_round(dir, Round::Nearest);
f.to_f64()
}
pub fn rational_to_f64(r: &Rational) -> f64 {
let (numer, denom) = (r.numer(), r.denom());
if numer.significant_bits() <= f64::MANTISSA_DIGITS
&& denom.significant_bits() <= f64::MANTISSA_DIGITS
{
return numer.to_f64() / denom.to_f64();
}
let (mut f, dir) = Float::with_val_round(f64::MANTISSA_DIGITS, r, Round::Nearest);
f.subnormalize_ieee_round(dir, Round::Nearest);
f.to_f64()
}
const EXACT_INTEGER_F64_LIMIT: f64 = 9_007_199_254_740_992.0;
fn exact_integer_node(data: &ExprData) -> Option<&Integer> {
match data {
ExprData::Integer(n) => Some(&n.0),
ExprData::Rational(r) if *r.0.denom() == 1 => Some(r.0.numer()),
_ => None,
}
}
pub fn integer_is_exact_f64(n: &Integer) -> bool {
n.significant_bits() <= f64::MANTISSA_DIGITS
|| Integer::from_f64(integer_to_f64(n)).as_ref() == Some(n)
}
#[inline]
pub fn pow_f64(base: f64, exp: f64, exp_node: impl FnOnce() -> Option<ExprData>) -> f64 {
if exp.abs() < EXACT_INTEGER_F64_LIMIT {
return base.powf(exp);
}
pow_f64_wide_exponent(base, exp, exp_node())
}
#[cold]
fn pow_f64_wide_exponent(base: f64, exp: f64, exp_node: Option<ExprData>) -> f64 {
match exp_node.as_ref().and_then(exact_integer_node) {
Some(n) => pow_f64_integer_exponent(base, n),
None => base.powf(exp),
}
}
pub fn pow_f64_integer_exponent(base: f64, n: &Integer) -> f64 {
let exp = integer_to_f64(n);
if !base.is_finite() || !base.is_sign_negative() {
return base.powf(exp);
}
let magnitude = (-base).powf(exp);
if n.is_odd() {
-magnitude
} else {
magnitude
}
}
pub fn try_expr_f64(expr: ExprId, pool: &ExprPool) -> Option<f64> {
match pool.get(expr) {
ExprData::Integer(n) => Some(integer_to_f64(&n.0)),
ExprData::Rational(r) => Some(rational_to_f64(&r.0)),
ExprData::Float(f) => Some(f.inner.to_f64()),
_ => None,
}
}
pub fn try_predicate_bool(kind: &PredicateKind, args: &[ExprId], pool: &ExprPool) -> Option<bool> {
match kind {
PredicateKind::True => Some(true),
PredicateKind::False => Some(false),
PredicateKind::Not => {
let inner = try_predicate_bool_from_expr(args[0], pool)?;
Some(!inner)
}
PredicateKind::And => {
for &a in args {
if !try_predicate_bool_from_expr(a, pool)? {
return Some(false);
}
}
Some(true)
}
PredicateKind::Or => {
for &a in args {
if try_predicate_bool_from_expr(a, pool)? {
return Some(true);
}
}
Some(false)
}
PredicateKind::Lt => Some(try_expr_f64(args[0], pool)? < try_expr_f64(args[1], pool)?),
PredicateKind::Le => Some(try_expr_f64(args[0], pool)? <= try_expr_f64(args[1], pool)?),
PredicateKind::Gt => Some(try_expr_f64(args[0], pool)? > try_expr_f64(args[1], pool)?),
PredicateKind::Ge => Some(try_expr_f64(args[0], pool)? >= try_expr_f64(args[1], pool)?),
PredicateKind::Eq => Some(try_expr_f64(args[0], pool)? == try_expr_f64(args[1], pool)?),
PredicateKind::Ne => Some(try_expr_f64(args[0], pool)? != try_expr_f64(args[1], pool)?),
}
}
pub fn try_predicate_bool_from_expr(expr: ExprId, pool: &ExprPool) -> Option<bool> {
match pool.get(expr) {
ExprData::Predicate { kind, args } => try_predicate_bool(&kind, &args, pool),
_ => None,
}
}
#[cfg(test)]
mod conversion_tests {
use super::*;
use rug::ops::Pow;
#[test]
fn integer_to_f64_rounds_to_nearest_not_towards_zero() {
let n = Integer::from(10).pow(30);
assert_eq!(n.to_f64(), 9.999999999999999e29, "rug still truncates");
assert_eq!(integer_to_f64(&n), 1e30);
let n = Integer::from(2u64).pow(70) - 1u32;
assert_eq!(integer_to_f64(&n), 1.1805916207174113e21);
let n: Integer = "12345678901234567890123456789".parse().unwrap();
assert_eq!(integer_to_f64(&n), 1.2345678901234568e28);
assert_eq!(integer_to_f64(&-n.clone()), -1.2345678901234568e28);
for k in [0i64, 1, -1, 42, -7, 1 << 52, -(1 << 52)] {
assert_eq!(integer_to_f64(&Integer::from(k)), k as f64, "k = {k}");
}
let n = Integer::from(1u64 << 53) + 1u32;
assert_eq!(integer_to_f64(&n), 9007199254740992.0);
}
#[test]
fn integer_to_f64_saturates_past_dbl_max() {
let huge = Integer::from(10).pow(400);
assert_eq!(integer_to_f64(&huge), f64::INFINITY);
assert_eq!(integer_to_f64(&-huge.clone()), f64::NEG_INFINITY);
}
#[test]
fn rational_to_f64_rounds_to_nearest_not_towards_zero() {
let r = Rational::from((2, 5));
assert_eq!(r.to_f64(), 0.39999999999999997, "rug still truncates");
assert_eq!(rational_to_f64(&r), 0.4);
for (n, d, want) in [
(-7i64, 3i64, -2.3333333333333335f64),
(7, 3, 2.3333333333333335),
(-5, 3, -1.6666666666666667),
(355, 113, 3.1415929203539825),
(1, 3, 0.3333333333333333),
(22, 7, 3.142857142857143),
] {
assert_eq!(rational_to_f64(&Rational::from((n, d))), want, "{n}/{d}");
}
}
fn odd_wide_exponent() -> Integer {
Integer::from(10).pow(30) + 1u32
}
#[test]
fn pow_f64_keeps_the_parity_of_an_exponent_f64_cannot_hold() {
let n = odd_wide_exponent();
assert_eq!(integer_to_f64(&n), 1e30);
assert_eq!((-1.0f64).powf(1e30), 1.0, "the defect");
assert_eq!(pow_f64_integer_exponent(-1.0, &n), -1.0);
assert_eq!(pow_f64_integer_exponent(-1.0, &(n.clone() - 1u32)), 1.0);
assert_eq!(pow_f64_integer_exponent(1.0, &n), 1.0);
let node = ExprData::Integer(crate::kernel::BigInt(n.clone()));
assert_eq!(pow_f64(-1.0, 1e30, || Some(node.clone())), -1.0);
}
#[test]
fn pow_f64_leaves_every_representable_exponent_bit_for_bit_alone() {
for &base in &[-3.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 2.0, 7.25] {
for &e in &[0.0, 1.0, 2.0, 3.0, -1.0, -2.0, 0.5, -0.5, 53.0, 1e15, -1e15] {
let n = Integer::from(e as i64);
let node = ExprData::Integer(crate::kernel::BigInt(n));
let got = pow_f64(base, e, || Some(node));
let want = base.powf(e);
assert_eq!(
got.to_bits(),
want.to_bits(),
"{base} ^ {e}: {got} vs {want}"
);
}
}
}
#[test]
fn pow_f64_leaves_an_exactly_representable_wide_exponent_alone() {
let n = Integer::from(1u64 << 60);
assert_eq!(pow_f64_integer_exponent(-1.0, &n), 1.0);
assert_eq!((-1.0f64).powf(integer_to_f64(&n)), 1.0);
let odd = n + 1u32;
assert_eq!(integer_to_f64(&odd), 1.152921504606847e18);
assert_eq!(pow_f64_integer_exponent(-1.0, &odd), -1.0);
}
#[test]
fn pow_f64_signs_the_overflow_and_the_underflow() {
let n = odd_wide_exponent();
assert_eq!(pow_f64_integer_exponent(-2.0, &n), f64::NEG_INFINITY);
assert_eq!(pow_f64_integer_exponent(2.0, &n), f64::INFINITY);
assert!(pow_f64_integer_exponent(-0.5, &n).is_sign_negative());
assert_eq!(pow_f64_integer_exponent(-0.5, &n), 0.0);
assert!(pow_f64_integer_exponent(-0.0, &n).is_sign_negative());
}
#[test]
fn pow_f64_falls_back_for_an_exponent_that_was_never_exact() {
let node = ExprData::Float(crate::kernel::BigFloat {
inner: rug::Float::with_val(53, 1e30),
prec: 53,
});
assert_eq!(pow_f64(-1.0, 1e30, || Some(node)), (-1.0f64).powf(1e30));
assert_eq!(pow_f64(-1.0, 1e30, || None), (-1.0f64).powf(1e30));
assert!(pow_f64(-2.0, f64::NAN, || None).is_nan());
}
#[test]
fn rational_to_f64_survives_numerator_and_denominator_both_overflowing() {
let numer = Integer::from(3) * Integer::from(10).pow(400) + 1u32;
let denom = Integer::from(2) * Integer::from(10).pow(400);
assert!(numer.to_f64().is_infinite() && denom.to_f64().is_infinite());
assert!(
(numer.to_f64() / denom.to_f64()).is_nan(),
"the old formula"
);
let r = Rational::from((numer, denom));
assert_eq!(rational_to_f64(&r), 1.5);
}
#[test]
fn rational_to_f64_handles_subnormals_and_overflow() {
let big = Integer::from(10).pow(400);
assert_eq!(rational_to_f64(&Rational::from((1, big.clone()))), 0.0);
assert_eq!(rational_to_f64(&Rational::from((big, 1))), f64::INFINITY);
let denom = Integer::from(2u32).pow(1074);
assert_eq!(rational_to_f64(&Rational::from((1, denom))), 5e-324);
}
}