fix: avoid u64 overflow in solve_itp for large nmax values - #588
fix: avoid u64 overflow in solve_itp for large nmax values#588greysquirr3l wants to merge 1 commit into
Conversation
|
Ope, investigating CI issue. |
da6b2e7 to
c64fec2
Compare
There was a problem hiding this comment.
Pull request overview
Fixes an overflow bug in the ITP root solver when epsilon is extremely small and the derived nmax exceeds 63, preventing debug-mode panics and release-mode misbehavior.
Changes:
- Replaces a
u64left-shift (1u64 << nmax) with a floating-pointexp2computation when scalingepsilon. - Adds a regression test that exercises a case where
nmaxbecomes large (e.g.epsilon = 1e-24).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let n1_2 = (((b - a) / epsilon).log2().ceil() - 1.0).max(0.0) as usize; | ||
| let nmax = n0 + n1_2; | ||
| let mut scaled_epsilon = epsilon * (1u64 << nmax) as f64; | ||
| let mut scaled_epsilon = epsilon * 2.0_f64.powi(nmax as i32); |
| let f = |x: f64| x - 0.5; | ||
| let x = solve_itp(f, 0.0, 1.0, 1e-24, 0, 0.2, f(0.0), f(1.0)); |
9ecb1d3 to
a7c7385
Compare
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.
a7c7385 to
12ea6bb
Compare
|
Oh, I think this may be the fix for issue #602 I submitted recently. |
|
I'm going to dig deeply into this set of problems soon. My immediate question is whether nmax > 63 is ever valid. A back-of-the-envelope analysis suggests that with bisection you're never going to get more than the number of mantissa bits (52 or 53 bits), so I'm thinking of just clamping nmax to that. That may well mean not being able to satisfy the precision request implied by epsilon. Clearly the existing code is wrong and needs a fix. I just need to find out where these too-small epsilons are coming from and what the best behavior for the solver should be in those cases. |
Problem
When
epsilonis very small (e.g.1e-24),nmaxinsolve_itp_falliblecan exceed 63. The current expression:overflows
u64whennmax >= 64:<< 64wraps to<< 0, producingscaled_epsilon = epsilon * 1instead ofepsilon * 2^nmaxHow
nmaxgets largeFor
b - a = 1.0andepsilon = 1e-24,log2(1e24) ≈ 79.7, givingnmax ≈ 79.Fix
Replace the integer shift with
f64::exp2():This is mathematically identical (
2^nmax) but uses floating-point arithmetic.f64handlesnmaxup to ~1023 before saturating to+inf— well beyond any epsilon that makes physical sense.Test
Added
test_solve_itp_large_nmax_does_not_overflowwithepsilon = 1e-24, which forcesnmax ≈ 79. This panics on the original code in debug mode and returns the wrong root in release mode.