brepkit_math/
tolerance.rs1#[derive(Debug, Clone, Copy, PartialEq)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct Tolerance {
15 pub linear: f64,
17 pub angular: f64,
19 pub relative: f64,
24}
25
26impl Tolerance {
27 #[must_use]
31 pub const fn new() -> Self {
32 Self {
33 linear: 1e-7,
34 angular: 1e-12,
35 relative: 1e-10,
36 }
37 }
38
39 #[must_use]
43 pub const fn loose() -> Self {
44 Self {
45 linear: 1e-4,
46 angular: 1e-8,
47 relative: 1e-6,
48 }
49 }
50
51 #[must_use]
55 pub const fn tight() -> Self {
56 Self {
57 linear: 1e-10,
58 angular: 1e-15,
59 relative: 1e-14,
60 }
61 }
62
63 #[must_use]
68 pub fn approx_eq(self, a: f64, b: f64) -> bool {
69 let diff = (a - b).abs();
70 let scale = a.abs().max(b.abs());
71 diff <= self.linear.max(self.relative * scale)
72 }
73
74 #[must_use]
79 pub fn approx_eq_abs(self, a: f64, b: f64) -> bool {
80 (a - b).abs() <= self.linear
81 }
82
83 #[must_use]
92 pub fn parametric(self, derivative_mag: f64) -> f64 {
93 if derivative_mag < 1e-30 {
94 return self.linear;
95 }
96 (self.linear / derivative_mag).clamp(1e-15, 0.1)
97 }
98
99 #[must_use]
102 pub fn linear_sq(self) -> f64 {
103 self.linear * self.linear
104 }
105}
106
107impl Default for Tolerance {
108 fn default() -> Self {
109 Self::new()
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 #![allow(clippy::unwrap_used, clippy::expect_used)]
116 use super::*;
117
118 #[test]
119 fn default_tolerance() {
120 let tol = Tolerance::new();
121 assert!((tol.linear - 1e-7).abs() < 1e-20);
122 assert!((tol.angular - 1e-12).abs() < 1e-20);
123 assert!((tol.relative - 1e-10).abs() < 1e-20);
124 }
125
126 #[test]
127 fn loose_tolerance() {
128 let tol = Tolerance::loose();
129 assert!(tol.linear > Tolerance::new().linear);
130 assert!(tol.relative > Tolerance::new().relative);
131 }
132
133 #[test]
134 fn tight_tolerance() {
135 let tol = Tolerance::tight();
136 assert!(tol.linear < Tolerance::new().linear);
137 assert!(tol.relative < Tolerance::new().relative);
138 }
139
140 #[test]
141 fn approx_eq_within_tolerance() {
142 let tol = Tolerance::new();
143 assert!(tol.approx_eq(1.0, 1.0 + 1e-8));
144 assert!(tol.approx_eq(0.0, 1e-8));
145 }
146
147 #[test]
148 fn approx_eq_outside_tolerance() {
149 let tol = Tolerance::new();
150 assert!(!tol.approx_eq(1.0, 1.001));
151 assert!(!tol.approx_eq(0.0, 0.001));
152 }
153
154 #[test]
155 fn approx_eq_exact() {
156 let tol = Tolerance::new();
157 assert!(tol.approx_eq(42.0, 42.0));
158 assert!(tol.approx_eq(0.0, 0.0));
159 }
160
161 #[test]
162 fn approx_eq_scales_with_magnitude() {
163 let tol = Tolerance {
164 linear: 1e-7,
165 angular: 1e-12,
166 relative: 1e-4, };
168 assert!(tol.approx_eq(0.0, 1e-8));
170 assert!(!tol.approx_eq(0.0, 1e-3));
171
172 assert!(tol.approx_eq(1e6, 1e6 + 50.0));
175 assert!(!tol.approx_eq(1e6, 1e6 + 200.0));
176 }
177
178 #[test]
179 fn approx_eq_abs_ignores_relative() {
180 let tol = Tolerance {
181 linear: 1e-7,
182 angular: 1e-12,
183 relative: 1e-4,
184 };
185 assert!(!tol.approx_eq_abs(1e6, 1e6 + 1.0));
187 assert!(tol.approx_eq_abs(1e6, 1e6 + 1e-8));
188 }
189
190 #[test]
191 fn default_matches_new() {
192 assert_eq!(Tolerance::default(), Tolerance::new());
193 }
194}