Skip to content

Commit 9ecb1d3

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 9ecb1d3

1 file changed

Lines changed: 10 additions & 1 deletion

File tree

kurbo/src/common.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,8 @@ 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 powi_nmax = nmax.min(i32::MAX as usize) as i32;
714+
let mut scaled_epsilon = epsilon * 2.0_f64.powi(powi_nmax);
714715
while b - a > 2.0 * epsilon {
715716
let x1_2 = 0.5 * (a + b);
716717
let r = scaled_epsilon - 0.5 * (b - a);
@@ -1097,6 +1098,14 @@ mod tests {
10971098
assert!(f(x).abs() < 6e-12);
10981099
}
10991100

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

0 commit comments

Comments
 (0)