Skip to content

Commit 43d2fef

Browse files
removed threshold from Vector2DOps::try_normalize
1 parent 5706059 commit 43d2fef

File tree

1 file changed

+60
-40
lines changed

1 file changed

+60
-40
lines changed

geo/src/algorithm/vector_ops.rs

+60-40
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,17 @@ where
104104
/// Try to find a vector of unit length in the same direction as this
105105
/// vector.
106106
///
107-
/// Returns `None` if the magnitude of this vector is less than
108-
/// `minimum_magnitude` or the magnitude is not finite.
109-
/// - For f32 the minimum_magnitude can be set to about `1e-30_f32`
110-
/// - For F64 the minimum_magnitude can be set to about `2e-301_f64`
111-
///
112-
/// These values should avoid overflowing to Infinity for coordinate values
113-
/// in the range typically relevant for spatial data (+-40e6) which is the
114-
/// approximate length of the earth's great circle in metres.
115-
fn try_normalize(self, minimum_magnitude: Self::Scalar) -> Option<Self>;
107+
/// Returns `None` if the result is not finite. This can happen when
108+
///
109+
/// - the vector is really small (or zero length) and the `.magnitude()`
110+
/// calculation has rounded-down to `0.0`
111+
/// - the vector is really large and the `.magnitude()` has rounded-up
112+
/// or 'overflowed' to `f64::INFINITY`
113+
/// - Either x or y are `f64::NAN` or `f64::INFINITY`
114+
fn try_normalize(self) -> Option<Self>;
115+
116+
/// Returns true if both the x and y components are finite
117+
fn is_finite(self) -> bool;
116118
}
117119

118120
impl<T> Vector2DOps for Coord<T>
@@ -151,23 +153,30 @@ where
151153
}
152154
}
153155

154-
fn try_normalize(self, minimum_magnitude: Self::Scalar) -> Option<Self> {
156+
fn try_normalize(self) -> Option<Self> {
155157
let magnitude = self.magnitude();
156-
if magnitude.is_finite() && magnitude.abs() > minimum_magnitude {
157-
Some(self / magnitude)
158+
let result = self / magnitude;
159+
// Both the result AND the magnitude must be finite they are finite
160+
// Otherwise very large vectors overflow magnitude to Infinity,
161+
// and the after the division the result would be coord!{x:0.0,y:0.0}
162+
// Note we don't need to check if magnitude is zero, because after the division
163+
// that would have made result non-finite or NaN anyway.
164+
if result.is_finite() && magnitude.is_finite() {
165+
Some(result)
158166
} else {
159167
None
160168
}
161169
}
170+
171+
fn is_finite(self) -> bool {
172+
self.x.is_finite() && self.y.is_finite()
173+
}
162174
}
163175

164176
#[cfg(test)]
165177
mod test {
166-
// crate dependencies
167-
use crate::coord;
168-
169-
// private imports
170178
use super::Vector2DOps;
179+
use crate::coord;
171180

172181
#[test]
173182
fn test_cross_product() {
@@ -288,60 +297,71 @@ mod test {
288297
}
289298

290299
#[test]
291-
fn test_normalize() {
292-
let f64_minimum_threshold = 2e-301f64;
300+
fn test_try_normalize() {
301+
293302
// Already Normalized
294303
let a = coord! {
295304
x: 1.0,
296305
y: 0.0
297306
};
298-
assert_relative_eq!(a.try_normalize(f64_minimum_threshold).unwrap(), a);
307+
assert_relative_eq!(a.try_normalize().unwrap(), a);
299308

300309
// Already Normalized
301310
let a = coord! {
302311
x: 1.0 / f64::sqrt(2.0),
303312
y: -1.0 / f64::sqrt(2.0)
304313
};
305-
assert_relative_eq!(a.try_normalize(f64_minimum_threshold).unwrap(), a);
314+
assert_relative_eq!(a.try_normalize().unwrap(), a);
306315

307316
// Non trivial example
308317
let a = coord! { x: -10.0, y: 8.0 };
309318
assert_relative_eq!(
310-
a.try_normalize(f64_minimum_threshold).unwrap(),
319+
a.try_normalize().unwrap(),
311320
coord! { x: -10.0, y: 8.0 } / f64::sqrt(10.0 * 10.0 + 8.0 * 8.0)
312321
);
322+
}
323+
324+
#[test]
325+
fn test_try_normalize_edge_cases() {
326+
use float_next_after::NextAfter;
327+
328+
// The following tests demonstrate some of the floating point
329+
// edge cases that can cause try_normalize to return None.
330+
331+
// Zero vector - Normalize returns None
332+
let a = coord! { x: 0.0, y: 0.0 };
333+
assert_eq!(a.try_normalize(), None);
313334

314-
// Very Small Input - Fails because below threshold
335+
// Very Small Input - Normalize returns None because of
336+
// rounding-down to zero in the .magnitude() calculation
315337
let a = coord! {
316338
x: 0.0,
317-
y: f64_minimum_threshold / 2.0
339+
y: 1e-301_f64
318340
};
319-
assert_eq!(a.try_normalize(f64_minimum_threshold), None);
341+
assert_eq!(a.try_normalize(), None);
320342

321-
// Zero vector - Fails because below threshold
322-
let a = coord! { x: 0.0, y: 0.0 };
323-
assert_eq!(a.try_normalize(f64_minimum_threshold), None);
324-
325-
// Large vector Pass;
326-
// Note: this fails because the magnitude calculation overflows to infinity
327-
// this could be avoided my modifying the implementation to use scaling be avoided using scaling
343+
// A large vector where try_normalize returns Some
344+
// Because the magnitude is f64::MAX (Just before overflow to f64::INFINITY)
328345
let a = coord! {
329346
x: f64::sqrt(f64::MAX/2.0),
330347
y: f64::sqrt(f64::MAX/2.0)
331348
};
332-
assert!(a.try_normalize(f64_minimum_threshold).is_some());
349+
assert!(a.try_normalize().is_some());
333350

334-
// Large Vector Fail;
335-
// Note: This test uses the unstable float_next_up_down feature
336-
// let a = coord! { x: f64::sqrt(f64::MAX/2.0), y: f64::next_up(f64::sqrt(f64::MAX/2.0)) };
337-
// assert!(a.try_normalize(1e-10f64).is_none());
351+
// A large vector where try_normalize returns None
352+
// because the magnitude is just above f64::MAX
353+
let a = coord! {
354+
x: f64::sqrt(f64::MAX / 2.0),
355+
y: f64::sqrt(f64::MAX / 2.0).next_after(f64::INFINITY)
356+
};
357+
assert_eq!(a.try_normalize(), None);
338358

339-
// NaN
359+
// Where one of the components is NaN try_normalize returns None
340360
let a = coord! { x: f64::NAN, y: 0.0 };
341-
assert_eq!(a.try_normalize(f64_minimum_threshold), None);
361+
assert_eq!(a.try_normalize(), None);
342362

343-
// Infinite
363+
// Where one of the components is Infinite try_normalize returns None
344364
let a = coord! { x: f64::INFINITY, y: 0.0 };
345-
assert_eq!(a.try_normalize(f64_minimum_threshold), None);
365+
assert_eq!(a.try_normalize(), None);
346366
}
347367
}

0 commit comments

Comments
 (0)