pub fn eval_arithmetic(relation: &str, args: &[f64]) -> Option<bool> {
let (&x1, &x2, &x3) = (args.first()?, args.get(1)?, args.get(2)?);
let nonfinite_operand = !x1.is_finite() || !x2.is_finite() || !x3.is_finite();
let result = match relation {
"product" => x2 * x3,
"sum" => x2 + x3,
"quotient" => {
if x3 == 0.0 {
return if nonfinite_operand { None } else { Some(false) };
}
x2 / x3
}
_ => return None,
};
if nonfinite_operand || !result.is_finite() {
return None;
}
Some(isclose(x1, result))
}
fn isclose(a: f64, b: f64) -> bool {
(a - b).abs() <= 1e-9 * a.abs().max(b.abs())
}
#[cfg(test)]
mod tests {
use super::eval_arithmetic;
#[test]
fn integer_cases_exact() {
for r in crate::relations::BUILTIN_ARITHMETIC {
assert!(
eval_arithmetic(r, &[6.0, 2.0, 3.0]).is_some(),
"{r} must be evaluable"
);
}
for r in crate::relations::NUMERIC_COMPARISONS {
assert!(
eval_arithmetic(r, &[6.0, 2.0, 3.0]).is_none(),
"{r} must not be tolerant arithmetic"
);
}
assert_eq!(eval_arithmetic("product", &[6.0, 2.0, 3.0]), Some(true)); assert_eq!(eval_arithmetic("product", &[7.0, 2.0, 3.0]), Some(false));
assert_eq!(eval_arithmetic("sum", &[5.0, 2.0, 3.0]), Some(true)); assert_eq!(eval_arithmetic("sum", &[4.0, 2.0, 3.0]), Some(false));
assert_eq!(eval_arithmetic("quotient", &[3.0, 6.0, 2.0]), Some(true)); }
#[test]
fn float_tolerance_headline() {
assert_eq!(eval_arithmetic("sum", &[0.3, 0.1, 0.2]), Some(true));
assert_eq!(eval_arithmetic("sum", &[0.4, 0.1, 0.2]), Some(false));
assert_eq!(eval_arithmetic("product", &[0.01, 0.1, 0.1]), Some(true));
}
#[test]
fn dilcu_divide_by_zero_is_false_not_none() {
assert_eq!(eval_arithmetic("quotient", &[0.0, 5.0, 0.0]), Some(false));
}
#[test]
fn non_finite_operand_or_result_declines() {
let inf = f64::INFINITY;
assert_eq!(eval_arithmetic("sum", &[inf, inf, 1.0]), None);
assert_eq!(eval_arithmetic("product", &[1.0, inf, 2.0]), None);
assert_eq!(eval_arithmetic("quotient", &[1.0, inf, 2.0]), None);
assert_eq!(eval_arithmetic("sum", &[f64::NAN, 1.0, 2.0]), None);
assert_eq!(eval_arithmetic("product", &[1.0, 1e200, 1e200]), None);
assert_eq!(eval_arithmetic("quotient", &[0.0, 5.0, 0.0]), Some(false));
assert_eq!(eval_arithmetic("quotient", &[inf, 5.0, 0.0]), None);
}
#[test]
fn unknown_relation_and_short_args_are_none() {
assert_eq!(eval_arithmetic("exponential", &[8.0, 2.0, 3.0]), None);
assert_eq!(eval_arithmetic("sum", &[5.0, 2.0]), None);
}
}