integrator/
lib.rs

1/// A math library
2#[cfg(all(feature = "low_precision", feature = "high_precision"))]
3compile_error!(
4    "feature \"low_precision\" and feature \"high_precision\" cannot be enabled at the same time"
5);
6
7#[cfg(not(feature = "fixed_precision"))]
8mod precision {
9    use types::FType;
10
11    use crate::traits::{Approximately, FloatExt, FromLossy};
12
13    #[cfg(feature = "low_precision")]
14    pub(crate) mod types {
15        pub type FType = f32;
16        pub type IType = i32;
17        pub type UType = u32;
18    }
19
20    #[cfg(feature = "high_precision")]
21    pub(crate) mod types {
22        pub type FType = f64;
23        pub type IType = i64;
24        pub type UType = u64;
25    }
26
27    impl FloatExt for FType {
28        const ONE: Self = 1.0;
29        const ZERO: Self = 0.0;
30    }
31
32    impl FromLossy<i32> for FType {
33        fn from_lossy(value: i32) -> Self {
34            value as Self
35        }
36    }
37
38    impl FromLossy<i64> for FType {
39        fn from_lossy(value: i64) -> Self {
40            value as Self
41        }
42    }
43
44    impl FromLossy<f32> for FType {
45        fn from_lossy(value: f32) -> Self {
46            value as Self
47        }
48    }
49
50    impl FromLossy<f64> for FType {
51        fn from_lossy(value: f64) -> Self {
52            value as Self
53        }
54    }
55
56    impl Approximately for FType {
57        fn approximately(&self, other: Self, epsilon: FType) -> bool {
58            // If either value is NaN, then they can not be equal
59            if self.is_nan() || other.is_nan() {
60                return false;
61            }
62            // If the two numbers are exactly equal (including infinities), they are approximately equal.
63            if self == &other {
64                return true;
65            }
66            // Compare the absolute difference to epsilon.
67            (self - other).abs() <= epsilon
68        }
69    }
70
71    impl Approximately for &FType {
72        fn approximately(&self, other: Self, epsilon: FType) -> bool {
73            FType::approximately(*self, *other, epsilon)
74        }
75    }
76
77    impl Approximately for &mut FType {
78        fn approximately(&self, other: Self, epsilon: FType) -> bool {
79            FType::approximately(*self, *other, epsilon)
80        }
81    }
82}
83
84#[cfg(feature = "fixed_precision")]
85mod precision {
86    use types::FType;
87
88    pub(crate) mod types {
89        pub type FType = crate::fixed::Fixed;
90        pub type IType = i64;
91        pub type UType = u64;
92    }
93
94    impl FType {
95        pub const ONE: Self = FType::from_const(1.0);
96        pub const ZERO: Self = FType::from_const(0.0);
97    }
98}
99
100pub type Float = precision::types::FType;
101pub type Int = precision::types::IType;
102pub type Unsigned = precision::types::UType;
103
104pub mod bivec;
105pub mod constant;
106pub mod fixed;
107pub mod line;
108pub mod matrix;
109pub mod percent;
110pub mod plane;
111pub mod point;
112pub mod rotor;
113pub mod segment;
114pub mod sphere;
115pub mod traits;
116pub mod vec;
117
118pub use matrix::Matrix;
119pub use point::Point;
120pub use rotor::Rotor;
121pub use vec::Vector;
122
123use traits::{Approximately, Zero};
124
125impl Zero for Float {
126    fn zero() -> Self {
127        Float::from(0.0)
128    }
129}
130
131trait One {
132    fn one() -> Self;
133}
134
135impl One for Float {
136    fn one() -> Self {
137        Float::from(1.0)
138    }
139}
140
141#[cfg(test)]
142mod equality_tests {
143    use super::*;
144    use std::f64::{INFINITY, NAN, NEG_INFINITY};
145
146    #[test]
147    fn exact_equality() {
148        let a: Float = 1.0.into();
149        let b: Float = 1.0.into();
150        assert!(a.approximately(b, Float::from(0.0)));
151        assert!(a.approximately(b, Float::EPSILON));
152    }
153
154    #[cfg(feature = "fixed_precision")]
155    #[ignore]
156    #[test]
157    fn difference_equals_epsilon() {
158        let a = Float::from(1.0);
159        let b = Float::from(1.0 + 0.5);
160        assert!(a.approximately(b, Float::from(0.5)));
161        assert!(b.approximately(a, Float::from(0.5)));
162    }
163
164    #[test]
165    fn difference_exceeds_epsilon() {
166        let a = Float::from(1.0);
167        let b = Float::from(1.0 + 0.5 + f64::EPSILON);
168        assert!(!a.approximately(b, Float::from(0.5)));
169    }
170
171    #[cfg(feature = "fixed_precision")]
172    #[ignore]
173    #[test]
174    fn zero_edge_cases() {
175        assert!(Float::from(0.0).approximately(Float::from(0.0), Float::from(0.0)));
176        assert!(Float::from(0.0).approximately(Float::from(1e-10), Float::from(1e-5)));
177        assert!(!Float::from(0.0).approximately(Float::from(1e-5), Float::from(1e-6)));
178    }
179
180    #[cfg(feature = "fixed_precision")]
181    #[ignore]
182    #[test]
183    fn opposite_signs() {
184        assert!(!Float::from(5.0).approximately(Float::from(-5.0), Float::from(9.9)));
185        assert!(Float::from(5.0).approximately(Float::from(-5.0), Float::from(10.1)));
186    }
187
188    #[test]
189    fn subnormal_numbers() {
190        let min = Float::from(f64::MIN_POSITIVE);
191        let a = Float::from(min);
192        let b = Float::from(min + min / Float::from(2.0));
193        assert!(a.approximately(b, min));
194    }
195
196    #[cfg(feature = "fixed_precision")]
197    #[ignore]
198    #[test]
199    fn nan_handling() {
200        assert!(!Float::from(NAN).approximately(Float::from(NAN), Float::from(f64::MAX)));
201        assert!(!Float::from(NAN).approximately(Float::from(1.0), Float::from(f64::MAX)));
202        assert!(!Float::from(1.0).approximately(Float::from(NAN), Float::from(f64::MAX)));
203    }
204
205    #[test]
206    fn infinity_handling() {
207        assert!(Float::from(INFINITY).approximately(Float::from(INFINITY), Float::from(0.0)));
208        assert!(
209            !Float::from(INFINITY).approximately(Float::from(NEG_INFINITY), Float::from(f64::MAX))
210        );
211        assert!(!Float::from(INFINITY).approximately(Float::from(1.0), Float::from(f64::MAX)));
212        assert!(!Float::from(1.0).approximately(Float::from(INFINITY), Float::from(f64::MAX)));
213    }
214
215    #[cfg(feature = "fixed_precision")]
216    #[ignore]
217    #[test]
218    fn large_numbers() {
219        let a = Float::from(1e20);
220        let b = Float::from(1e20 + 1e15);
221        assert!(a.approximately(b, Float::from(1e16)));
222        assert!(!a.approximately(b, Float::from(1e14)));
223    }
224
225    #[cfg(feature = "fixed_precision")]
226    #[ignore]
227    #[test]
228    fn tiny_epsilon() {
229        let a = Float::from(1.0 + 2.0 * f64::EPSILON);
230        let b = Float::from(1.0);
231        assert!(!a.approximately(b, Float::from(f64::EPSILON)));
232        assert!(a.approximately(b, Float::from(3.0 * f64::EPSILON)));
233    }
234
235    #[test]
236    fn symmetry_property() {
237        let a = Float::from(1.0);
238        let b = Float::from(1.000_000_1);
239        let eps = Float::from(0.000_000_2);
240        assert_eq!(a.approximately(b, eps), b.approximately(a, eps));
241    }
242
243    #[test]
244    fn transitive_property() {
245        let a = Float::from(1.0);
246        let b = Float::from(1.000_000_05);
247        let c = Float::from(1.000_000_1);
248        let eps = Float::from(0.000_000_2);
249        assert!(a.approximately(b, eps));
250        assert!(b.approximately(c, eps));
251        assert!(a.approximately(c, eps));
252    }
253}