integrator/
lib.rs

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