diff --git a/crates/functions/src/math.rs b/crates/functions/src/math.rs index 7611dc410..998a1c069 100644 --- a/crates/functions/src/math.rs +++ b/crates/functions/src/math.rs @@ -306,8 +306,8 @@ fn modulo(x: f64, d: f64) -> R { if d == 0.0 { return Err(CellError::Div0); } - let q = (x / d).floor(); - let r = x - d * q; + // `%` is an exact remainder (truncated), so |r| < |d| even for tiny divisors. + let r = x % d; // Keep the sign of the divisor and avoid returning |d| from rounding. if r != 0.0 && (r < 0.0) != (d < 0.0) { return Ok(r + d); @@ -1442,6 +1442,19 @@ mod tests { close(ev1("ODD", 0.0), 1.0); } + #[test] + fn mod_tiny_divisor_stays_in_range() { + for (x, d) in [(3.0, 1e-300), (-1.0, 1e-300), (3.0, -1e-300), (-1.0, -1e-300)] { + match ev2("MOD", x, d) { + Value::Number(r) => { + assert!(r == 0.0 || (r < 0.0) == (d < 0.0), "MOD({x}, {d}) = {r}"); + assert!(r.abs() < d.abs(), "MOD({x}, {d}) = {r}"); + } + other => panic!("MOD({x}, {d}) = {other:?}"), + } + } + } + #[test] fn arithmetic() { close(ev2("MOD", 3.0, 2.0), 1.0);