@@ -104,15 +104,17 @@ where
104
104
/// Try to find a vector of unit length in the same direction as this
105
105
/// vector.
106
106
///
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 ;
116
118
}
117
119
118
120
impl < T > Vector2DOps for Coord < T >
@@ -151,23 +153,30 @@ where
151
153
}
152
154
}
153
155
154
- fn try_normalize ( self , minimum_magnitude : Self :: Scalar ) -> Option < Self > {
156
+ fn try_normalize ( self ) -> Option < Self > {
155
157
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)
158
166
} else {
159
167
None
160
168
}
161
169
}
170
+
171
+ fn is_finite ( self ) -> bool {
172
+ self . x . is_finite ( ) && self . y . is_finite ( )
173
+ }
162
174
}
163
175
164
176
#[ cfg( test) ]
165
177
mod test {
166
- // crate dependencies
167
- use crate :: coord;
168
-
169
- // private imports
170
178
use super :: Vector2DOps ;
179
+ use crate :: coord;
171
180
172
181
#[ test]
173
182
fn test_cross_product ( ) {
@@ -288,60 +297,71 @@ mod test {
288
297
}
289
298
290
299
#[ test]
291
- fn test_normalize ( ) {
292
- let f64_minimum_threshold = 2e-301f64 ;
300
+ fn test_try_normalize ( ) {
301
+
293
302
// Already Normalized
294
303
let a = coord ! {
295
304
x: 1.0 ,
296
305
y: 0.0
297
306
} ;
298
- assert_relative_eq ! ( a. try_normalize( f64_minimum_threshold ) . unwrap( ) , a) ;
307
+ assert_relative_eq ! ( a. try_normalize( ) . unwrap( ) , a) ;
299
308
300
309
// Already Normalized
301
310
let a = coord ! {
302
311
x: 1.0 / f64 :: sqrt( 2.0 ) ,
303
312
y: -1.0 / f64 :: sqrt( 2.0 )
304
313
} ;
305
- assert_relative_eq ! ( a. try_normalize( f64_minimum_threshold ) . unwrap( ) , a) ;
314
+ assert_relative_eq ! ( a. try_normalize( ) . unwrap( ) , a) ;
306
315
307
316
// Non trivial example
308
317
let a = coord ! { x: -10.0 , y: 8.0 } ;
309
318
assert_relative_eq ! (
310
- a. try_normalize( f64_minimum_threshold ) . unwrap( ) ,
319
+ a. try_normalize( ) . unwrap( ) ,
311
320
coord! { x: -10.0 , y: 8.0 } / f64 :: sqrt( 10.0 * 10.0 + 8.0 * 8.0 )
312
321
) ;
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 ) ;
313
334
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
315
337
let a = coord ! {
316
338
x: 0.0 ,
317
- y: f64_minimum_threshold / 2.0
339
+ y: 1e-301_f64
318
340
} ;
319
- assert_eq ! ( a. try_normalize( f64_minimum_threshold ) , None ) ;
341
+ assert_eq ! ( a. try_normalize( ) , None ) ;
320
342
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)
328
345
let a = coord ! {
329
346
x: f64 :: sqrt( f64 :: MAX /2.0 ) ,
330
347
y: f64 :: sqrt( f64 :: MAX /2.0 )
331
348
} ;
332
- assert ! ( a. try_normalize( f64_minimum_threshold ) . is_some( ) ) ;
349
+ assert ! ( a. try_normalize( ) . is_some( ) ) ;
333
350
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 ) ;
338
358
339
- // NaN
359
+ // Where one of the components is NaN try_normalize returns None
340
360
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 ) ;
342
362
343
- // Infinite
363
+ // Where one of the components is Infinite try_normalize returns None
344
364
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 ) ;
346
366
}
347
367
}
0 commit comments