perf: compile date comparisons into numeric VM

This commit is contained in:
boris
2026-09-06 17:58:57 +08:00
parent e0b4a3f56c
commit bc228980af
2 changed files with 261 additions and 11 deletions
+87 -6
View File
@@ -314,15 +314,67 @@ fn eval_binary(operator: BinaryOp, lhs: Value, rhs: Value) -> Result<Value, Eval
BinaryOp::Multiply => Ok(Value::Number(number(lhs)? * number(rhs)?)),
BinaryOp::Divide => Ok(Value::Number(number(lhs)? / number(rhs)?)),
BinaryOp::Remainder => Ok(Value::Number(number(lhs)? % number(rhs)?)),
BinaryOp::Equal => Ok(Value::Boolean(lhs == rhs)),
BinaryOp::NotEqual => Ok(Value::Boolean(lhs != rhs)),
BinaryOp::Less => Ok(Value::Boolean(number(lhs)? < number(rhs)?)),
BinaryOp::LessEqual => Ok(Value::Boolean(number(lhs)? <= number(rhs)?)),
BinaryOp::Greater => Ok(Value::Boolean(number(lhs)? > number(rhs)?)),
BinaryOp::GreaterEqual => Ok(Value::Boolean(number(lhs)? >= number(rhs)?)),
BinaryOp::Equal => Ok(Value::Boolean(match (lhs, rhs) {
(Value::Number(lhs), Value::Number(rhs)) => float_equal(lhs, rhs),
(Value::Boolean(lhs), Value::Boolean(rhs)) => lhs == rhs,
_ => {
return Err(EvalError::new(
"comparison operands must have the same type",
));
}
})),
BinaryOp::NotEqual => Ok(Value::Boolean(match (lhs, rhs) {
(Value::Number(lhs), Value::Number(rhs)) => float_not_equal(lhs, rhs),
(Value::Boolean(lhs), Value::Boolean(rhs)) => lhs != rhs,
_ => {
return Err(EvalError::new(
"comparison operands must have the same type",
));
}
})),
BinaryOp::Less => {
let (lhs, rhs) = (number(lhs)?, number(rhs)?);
Ok(Value::Boolean(
(rhs - lhs) / float_comparison_scale(lhs, rhs) > f64::EPSILON,
))
}
BinaryOp::LessEqual => {
let (lhs, rhs) = (number(lhs)?, number(rhs)?);
Ok(Value::Boolean(
(rhs - lhs) / float_comparison_scale(lhs, rhs) > -f64::EPSILON,
))
}
BinaryOp::Greater => {
let (lhs, rhs) = (number(lhs)?, number(rhs)?);
Ok(Value::Boolean(
(lhs - rhs) / float_comparison_scale(lhs, rhs) > f64::EPSILON,
))
}
BinaryOp::GreaterEqual => {
let (lhs, rhs) = (number(lhs)?, number(rhs)?);
Ok(Value::Boolean(
(lhs - rhs) / float_comparison_scale(lhs, rhs) > -f64::EPSILON,
))
}
}
}
fn float_comparison_scale(lhs: f64, rhs: f64) -> f64 {
if lhs * rhs == 0.0 {
1.0
} else {
lhs.abs().max(rhs.abs())
}
}
fn float_equal(lhs: f64, rhs: f64) -> bool {
(lhs - rhs).abs() / float_comparison_scale(lhs, rhs) <= f64::EPSILON
}
fn float_not_equal(lhs: f64, rhs: f64) -> bool {
(lhs - rhs).abs() / float_comparison_scale(lhs, rhs) > f64::EPSILON
}
fn eval_builtin(builtin: Builtin, args: &[Value]) -> Result<Value, EvalError> {
let numeric = |index: usize| -> Result<f64, EvalError> {
args.get(index)
@@ -1313,6 +1365,35 @@ mod tests {
);
}
#[test]
fn floating_comparisons_match_rhai_epsilon_semantics() {
let adjacent = 11.699999999999998_f64;
assert_eq!(
evaluate("value == 11.7", &[("value", Value::Number(adjacent))]),
Value::Boolean(true)
);
assert_eq!(
evaluate("value != 11.7", &[("value", Value::Number(adjacent))]),
Value::Boolean(false)
);
assert_eq!(
evaluate("value <= 11.7", &[("value", Value::Number(adjacent))]),
Value::Boolean(true)
);
assert_eq!(
evaluate("value >= 11.7", &[("value", Value::Number(adjacent))]),
Value::Boolean(true)
);
assert_eq!(
evaluate("value < 11.7", &[("value", Value::Number(adjacent))]),
Value::Boolean(false)
);
assert_eq!(
evaluate("value > 11.7", &[("value", Value::Number(adjacent))]),
Value::Boolean(false)
);
}
#[test]
fn short_circuit_does_not_resolve_unused_variable() {
let program = compile("false && missing", |name| {