Repository navigation
Conversation
| let t = if squared_length == F::zero() && numerator == F::zero() { | ||
| // Avoid 0/0 from squared-length underflow, but preserve non-zero | ||
| // numerators so that infinite projections still clamp to an endpoint. |
There was a problem hiding this comment.
This guard catches underflow but not overflow. Past about 1e154 the squared length is +inf, usually with the numerator, so the ordinary path is inf / inf and t is NaN. scaled_projection already pulls an overflowing pair into range, so we only need to add:
| let t = if squared_length == F::zero() && numerator == F::zero() { | |
| // Avoid 0/0 from squared-length underflow, but preserve non-zero | |
| // numerators so that infinite projections still clamp to an endpoint. | |
| let t = if (squared_length == F::zero() && numerator == F::zero()) | |
| || !squared_length.is_finite() | |
| || !numerator.is_finite() | |
| { | |
| // Avoid 0/0 from underflow and inf/inf from overflow, but preserve a | |
| // non-zero numerator over a zero squared length so that infinite | |
| // projections still clamp to an endpoint. |
|
Oops, I accidentally committed my own suggested change. Sorry! That having been said, I think this is sound. I think we should hold off to see whether #1609 is reopened and then try this on top. |
|
Two additional tests from Opus that cover the // Squared length overflows to +inf, so the ordinary path is inf / inf.
#[test]
fn long_line_has_finite_closest_points() {
let line = Line::<f64>::new((0.0, 0.0), (1e200, 1e200));
let closest = line.closest_point(&Point::new(1e200, 0.0));
assert_finite(closest);
let Closest::SinglePoint(closest) = closest else {
panic!("expected a single point, got {closest:?}");
};
assert_relative_eq!(closest.x(), 5e199, max_relative = 1e-15);
assert_relative_eq!(closest.y(), 5e199, max_relative = 1e-15);
}
// 1e160 is finite; its square is not.
#[test]
fn line_with_overflowing_squared_length_projects_in_range() {
let line = Line::<f64>::new((0.0, 0.0), (1e160, 0.0));
let closest = line.closest_point(&Point::new(5e159, 1.0));
assert_finite(closest);
let Closest::SinglePoint(closest) = closest else {
panic!("expected a single point, got {closest:?}");
};
assert_relative_eq!(closest.x(), 5e159, max_relative = 1e-15);
assert_relative_eq!(closest.y(), 0.0);
} |
Do you want those two tests added to this branch @urschrei? |
|
Hi @urschrei, just wanted to follow up on this PR. I saw the discussion in #1615 about handling Hegel adoption separately, and was wondering if this fix could move forward independently. I'd be happy to add the two overflow regression tests you suggested, or any other focused coverage that would be helpful. Thanks again for taking the time to review this! |
Add the two regression cases suggested in PR georust#1612 for diagonal and axis-aligned segments whose squared lengths overflow. Keep the reviewed implementation unchanged. Co-authored-by: Codex <noreply@openai.com>
Refs georust#1604. Co-authored-by: Stephan Hügel <urschrei@gmail.com>
- Run the scaled fallback whenever the squared length is subnormal, not only when it is zero, using the same test as line_segment_distance. - Keep the unscaled quotient for NaN and infinite inputs. A NaN direction no longer panics, and a NaN query gives NaN coordinates, as on main. - Halve the coordinates where the difference of two finite coordinates overflows, and interpolate between the endpoints for the result. - Rescale the projection by three exact power-of-two multiplications instead of a loop of up to about 2,100 iterations. - Use wkt! fixtures, add regression tests for the cases above, and add closest_point criterion benchmarks to geo-benches.
33a270b to
21b38d3
Compare
|
Thanks @Udonburo. To save another review round, I've squashed the branch and added a commit on top that fixes the remaining edge cases. Changes
Performance Criterion medians, Apple Silicon,
Note that while the perf impact looks large, on "real-world" data it's within noise. In the last two rows Follow-up work we'll want to do
|
|
I've also squashed the commits, mine are currently in a separate commit. |
|
On the first follow-up item (a shared scaled-projection helper): I measured how far the two fallbacks disagree before writing one, and I don't think it's worth doing. I compared
A shared helper would only remove duplicate code. Only the projection parameter could be shared, it would change the distance fallback's last bits, and |
Refs #1604.
This fixes
Line::closest_pointreturning NaN coordinates when a non-zero segment's squared length underflows or the projection's dot products overflow. The existing calculation stays in place unless both dot products round to zero (0/0) or either dot product is non-finite. A non-zero numerator over a zero squared length still produces an infinite projection that clamps to an endpoint.The fallback scales the direction and query difference separately by powers of two and uses compensated dot products to avoid the cancellation from unit normalization. The tests cover exact and near cancellation, subnormal interior points, endpoint clamping in both directions, and the two suggested overflow regression cases. Both new tests fail with NaN coordinates when the overflow guard is removed and pass when it is restored. Polygon/RTree distance, coordinate subtraction errors and existing intersection-classification issues are outside this change.
Validation after merging
mainata4a6087, on Windows x86_64 MSVC with Rust 1.92.0:closest_pointpass in debug and release, including the 27 tests in theclosest_pointmodule.-Dwarnings), and the all-target build check without default features pass.geolibrary suite reports 1,225 passed, 2 failed and 2 ignored. Both failures (test_polygon_densifyandhaversine_segment_length) reproduce with identical assertion values on unchangedmain, using the same lockfile and a separate build directory.In the original measurements, ordinary-path timings were similar. The fallback cost about 2–3x for nearby tiny queries and reached about 1.5 µs in the extreme f64 cases measured. These benchmarks were not rerun for this update.
Codex helped with reproduction, implementation, testing, numerical review, merge-conflict resolution and documentation updates.
CHANGES.md.