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()
}
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}");
}
}
#[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);
}
}