@@ -9,16 +9,17 @@ use core::ops::Mul;
99/// An approximate unit direction stored as fixed-scale integer components.
1010///
1111/// Obtain one with [`IntVector::fast_normalize`] or [`Self::normalize_with_float`].
12- /// The represented components are
13- /// `x() / DENOMINATOR` and `y() / DENOMINATOR`, each in `[-1, 1]`. The length is
14- /// approximately one and never exceeds it.
12+ /// The represented components are `x() / DENOMINATOR` and `y() / DENOMINATOR`.
13+ /// Floating-point construction is approximate: rounding may make the length
14+ /// slightly greater than one. Integer normalization via `fast_normalize` and
15+ /// checked conversion via [`Self::try_from_float`] keep the length at most one.
1516///
1617/// Integer normalization via `fast_normalize` favors speed over precision:
1718/// it keeps about 6, 14, or 30 bits
1819/// of direction precision for `i16`, `i32`, or `i64`, respectively.
1920/// The storage scale does not imply that all stored bits are accurate.
20- /// [`Rotation::apply`](crate::int::angle::Rotation::apply) preserves the upper
21- /// length bound , but repeated rotations accumulate contraction and angular
21+ /// [`Rotation::apply`](crate::int::angle::Rotation::apply) does not increase
22+ /// length, but repeated rotations accumulate contraction and angular
2223/// error; the normalization precision above is not a bound on that accumulation.
2324#[ derive( Debug , Clone , Copy , PartialEq , Eq , Hash ) ]
2425pub struct UnitIntVector < T : IntNumber = i32 > {
@@ -27,7 +28,7 @@ pub struct UnitIntVector<T: IntNumber = i32> {
2728}
2829
2930impl < T : IntNumber > UnitIntVector < T > {
30- // Only for arithmetic that proves the resulting norm is at most one .
31+ // The caller supplies an approximately unit or shorter nonzero vector .
3132 #[ inline( always) ]
3233 pub ( crate ) fn from_components ( x : T , y : T ) -> Self {
3334 Self { x, y }
@@ -47,23 +48,37 @@ impl<T: IntNumber> UnitIntVector<T> {
4748 if x. abs ( ) > 1.0 || y. abs ( ) > 1.0 {
4849 return None ;
4950 }
50- let scale = Self :: DENOMINATOR . to_f64 ( ) ;
51- let x = T :: from_rounded_float ( libm:: trunc ( x * scale) ) ;
52- let y = T :: from_rounded_float ( libm:: trunc ( y * scale) ) ;
53- let ( wx, wy) = ( x. to_wide ( ) , y. to_wide ( ) ) ;
51+ let unit = Self :: from_float_unchecked ( x, y) ;
52+ let ( wx, wy) = ( unit. x . to_wide ( ) , unit. y . to_wide ( ) ) ;
5453 let squared = wx * wx + wy * wy;
5554 if squared == T :: Wide :: ZERO || squared > Self :: DENOMINATOR * Self :: DENOMINATOR {
5655 return None ;
5756 }
58- Some ( Self { x, y } )
57+ Some ( unit)
58+ }
59+
60+ /// Converts already prepared floating-point components without validation
61+ /// or normalization. Components are scaled and truncated toward zero.
62+ ///
63+ /// The caller supplies finite components of an approximately unit or shorter
64+ /// vector that remains nonzero after quantization. Small floating-point
65+ /// overshoots above unit length are accepted. Use [`Self::try_from_float`]
66+ /// when the fixed-scale length must be checked against one.
67+ #[ inline]
68+ pub fn from_float_unchecked < F : FloatNumber > ( x : F , y : F ) -> Self {
69+ let scale = Self :: DENOMINATOR . to_f64 ( ) ;
70+ Self {
71+ x : T :: from_float ( x. to_f64 ( ) * scale) ,
72+ y : T :: from_float ( y. to_f64 ( ) * scale) ,
73+ }
5974 }
6075
6176 /// Normalizes an integer vector using f64 arithmetic, returning `None` only
6277 /// for the zero vector. Supports the full built-in wide component range,
6378 /// including i128::MIN; no integer products of the input are formed.
6479 ///
65- /// Axis directions are exact. Other directions are contracted slightly
66- /// before conversion so their fixed-scale length never exceeds one .
80+ /// This is an approximate, unchecked normalization: rounding may make the
81+ /// output length slightly greater than one, including for axis directions .
6782 /// Precision is limited by f64 arithmetic and the output component scale.
6883 /// When normalizing a point difference, subtract the integer points first
6984 /// to retain small differences between large coordinates.
@@ -74,16 +89,9 @@ impl<T: IntNumber> UnitIntVector<T> {
7489 }
7590 let x = vector. x . to_f64 ( ) ;
7691 let y = vector. y . to_f64 ( ) ;
77- if x == 0.0 {
78- return Self :: try_from_float ( 0.0 , y. signum ( ) ) ;
79- }
80- if y == 0.0 {
81- return Self :: try_from_float ( x. signum ( ) , 0.0 ) ;
82- }
8392 // Squaring and adding even two i128 components fits in f64.
84- let length = FloatNumber :: sqrt ( x * x + y * y) ;
85- let inward = 1.0 - 4.0 * f64:: EPSILON ;
86- Self :: try_from_float ( x / length * inward, y / length * inward)
93+ let inv_length = 1.0 / FloatNumber :: sqrt ( x * x + y * y) ;
94+ Some ( Self :: from_float_unchecked ( x * inv_length, y * inv_length) )
8795 }
8896
8997 /// The stored integer value representing one: 2^14, 2^30, or 2^62.
0 commit comments