Skip to content

Commit da6b2e7

Browse files
committed
fix: avoid u64 overflow in solve_itp for large nmax values
When epsilon is very small (e.g. 1e-24), nmax can exceed 63. The expression `(1u64 << nmax) as f64` then overflows: debug builds panic, release builds silently produce the wrong value because x86 masks the shift amount to 6 bits (so << 64 wraps to << 0 = 1). Replace with `(nmax as f64).exp2()` which is mathematically identical (2^nmax) but uses f64 arithmetic, handling nmax up to ~1023 before saturating to +inf — well beyond any practical epsilon. Add a regression test with epsilon=1e-24 (nmax≈79) that panics on the original code in debug mode.
1 parent 838b69e commit da6b2e7

1 file changed

Lines changed: 9 additions & 1 deletion

File tree

kurbo/src/common.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ pub(crate) fn solve_itp_fallible<E>(
710710
) -> Result<(f64, f64), E> {
711711
let n1_2 = (((b - a) / epsilon).log2().ceil() - 1.0).max(0.0) as usize;
712712
let nmax = n0 + n1_2;
713-
let mut scaled_epsilon = epsilon * (1u64 << nmax) as f64;
713+
let mut scaled_epsilon = epsilon * (nmax as f64).exp2();
714714
while b - a > 2.0 * epsilon {
715715
let x1_2 = 0.5 * (a + b);
716716
let r = scaled_epsilon - 0.5 * (b - a);
@@ -1097,6 +1097,14 @@ mod tests {
10971097
assert!(f(x).abs() < 6e-12);
10981098
}
10991099

1100+
#[test]
1101+
fn test_solve_itp_large_nmax_does_not_overflow() {
1102+
let f = |x: f64| x - 0.5;
1103+
let x = solve_itp(f, 0.0, 1.0, 1e-24, 0, 0.2, f(0.0), f(1.0));
1104+
assert!(x.is_finite());
1105+
assert!((x - 0.5).abs() <= 1e-12);
1106+
}
1107+
11001108
#[test]
11011109
fn test_inv_arclen() {
11021110
use crate::{ParamCurve, ParamCurveArclen};

0 commit comments

Comments
 (0)