lox_test_utils/approx_eq/macros.rs
1// SPDX-FileCopyrightText: 2025 Helge Eichhorn <git@helgeeichhorn.de>
2//
3// SPDX-License-Identifier: MPL-2.0
4
5//! Macros for approximate equality testing.
6//!
7//! This module defines the core macros for testing floating-point equality:
8//! - [`approx_eq!`](crate::approx_eq!) - Returns `bool`, checks if values are approximately equal
9//! - [`approx_ne!`](crate::approx_ne!) - Returns `bool`, checks if values are not approximately equal
10//! - [`assert_approx_eq!`](crate::assert_approx_eq!) - Panics if values are not approximately equal (for tests)
11//! - [`assert_approx_ne!`](crate::assert_approx_ne!) - Panics if values are approximately equal (for tests)
12
13/// Checks if two values are approximately equal.
14///
15/// Returns `true` if the values are approximately equal within the specified tolerances,
16/// `false` otherwise. This macro is useful for conditional logic, while
17/// [`assert_approx_eq!`](crate::assert_approx_eq!) should be used in tests.
18///
19/// # Syntax
20///
21/// ```text
22/// approx_eq!(left, right) // Default tolerances
23/// approx_eq!(left, right, atol <= tolerance) // Custom absolute tolerance
24/// approx_eq!(left, right, rtol <= tolerance) // Custom relative tolerance
25/// approx_eq!(left, right, atol <= a, rtol <= r) // Both tolerances
26/// approx_eq!(left, right, rtol <= r, atol <= a) // Order doesn't matter
27/// ```
28///
29/// # Default Tolerances
30///
31/// When no tolerances are specified:
32/// - `atol = 0.0`
33/// - `rtol = sqrt(f64::EPSILON)` ≈ 1.49e-8
34///
35/// # Examples
36///
37/// ```
38/// use lox_test_utils::approx_eq;
39///
40/// // Default tolerances
41/// assert!(approx_eq!(1.0, 1.0 + f64::EPSILON));
42/// assert!(!approx_eq!(1.0, 1.1));
43///
44/// // Custom absolute tolerance
45/// assert!(approx_eq!(1.0, 1.001, atol <= 0.01));
46///
47/// // Custom relative tolerance (1% difference allowed)
48/// assert!(approx_eq!(100.0, 100.5, rtol <= 0.01));
49///
50/// // Both tolerances
51/// assert!(approx_eq!(1.0, 1.001, atol <= 0.01, rtol <= 0.01));
52///
53/// // Works with vectors
54/// use glam::DVec3;
55/// let v1 = DVec3::new(1.0, 2.0, 3.0);
56/// let v2 = DVec3::new(1.0 + f64::EPSILON, 2.0, 3.0);
57/// assert!(approx_eq!(v1, v2));
58/// ```
59///
60/// # See Also
61///
62/// - [`approx_ne!`](crate::approx_ne!) - For checking inequality
63/// - [`assert_approx_eq!`](crate::assert_approx_eq!) - For test assertions with error messages
64#[macro_export]
65macro_rules! approx_eq {
66 ($lhs:expr, $rhs:expr) => {
67 approx_eq!(
68 $lhs,
69 $rhs,
70 atol <= 0.0,
71 rtol <= $crate::approx_eq::default_rtol(0.0)
72 )
73 };
74 ($lhs:expr, $rhs:expr, rtol <= $rtol:expr) => {
75 approx_eq!($lhs, $rhs, atol <= 0.0, rtol <= $rtol)
76 };
77 ($lhs:expr, $rhs:expr, atol <= $atol:expr) => {
78 approx_eq!(
79 $lhs,
80 $rhs,
81 atol <= $atol,
82 rtol <= $crate::approx_eq::default_rtol($atol)
83 )
84 };
85 ($lhs:expr, $rhs:expr, rtol <= $rtol:expr, atol <= $atol:expr) => {
86 approx_eq!($lhs, $rhs, atol <= $atol, rtol <= $rtol)
87 };
88 ($lhs:expr, $rhs:expr, atol <= $atol:expr, rtol <= $rtol:expr) => {
89 $crate::approx_eq::approx_eq_helper(&$lhs, &$rhs, $atol, $rtol).is_approx_eq()
90 };
91}
92
93/// Checks if two values are not approximately equal.
94///
95/// Returns `true` if the values are not approximately equal (differ beyond the specified
96/// tolerances), `false` otherwise. This is the logical negation of [`approx_eq!`](crate::approx_eq!).
97///
98/// # Syntax
99///
100/// ```text
101/// approx_ne!(left, right) // Default tolerances
102/// approx_ne!(left, right, atol <= tolerance) // Custom absolute tolerance
103/// approx_ne!(left, right, rtol <= tolerance) // Custom relative tolerance
104/// approx_ne!(left, right, atol <= a, rtol <= r) // Both tolerances
105/// approx_ne!(left, right, rtol <= r, atol <= a) // Order doesn't matter
106/// ```
107///
108/// # Examples
109///
110/// ```
111/// use lox_test_utils::approx_ne;
112///
113/// // Values that differ significantly
114/// assert!(approx_ne!(1.0, 2.0));
115/// assert!(approx_ne!(1.0, 1.1));
116///
117/// // Values within epsilon are considered equal, so approx_ne returns false
118/// assert!(!approx_ne!(1.0, 1.0 + f64::EPSILON));
119///
120/// // Custom tolerances
121/// assert!(approx_ne!(1.0, 1.1, atol <= 0.01));
122/// assert!(!approx_ne!(1.0, 1.001, atol <= 0.01));
123/// ```
124///
125/// # See Also
126///
127/// - [`approx_eq!`](crate::approx_eq!) - For checking equality
128/// - [`assert_approx_ne!`](crate::assert_approx_ne!) - For test assertions with error messages
129#[macro_export]
130macro_rules! approx_ne {
131 ($lhs:expr, $rhs:expr) => {
132 approx_ne!(
133 $lhs,
134 $rhs,
135 atol <= 0.0,
136 rtol <= $crate::approx_eq::default_rtol(0.0)
137 )
138 };
139 ($lhs:expr, $rhs:expr, rtol <= $rtol:expr) => {
140 approx_ne!($lhs, $rhs, atol <= 0.0, rtol <= $rtol)
141 };
142 ($lhs:expr, $rhs:expr, atol <= $atol:expr) => {
143 approx_ne!(
144 $lhs,
145 $rhs,
146 atol <= $atol,
147 rtol <= $crate::approx_eq::default_rtol($atol)
148 )
149 };
150 ($lhs:expr, $rhs:expr, rtol <= $rtol:expr, atol <= $atol:expr) => {
151 approx_ne!($lhs, $rhs, atol <= $atol, rtol <= $rtol)
152 };
153 ($lhs:expr, $rhs:expr, atol <= $atol:expr, rtol <= $rtol:expr) => {
154 $crate::approx_eq::approx_eq_helper(&$lhs, &$rhs, $atol, $rtol).is_approx_ne()
155 };
156}
157
158/// Asserts that two values are approximately equal.
159///
160/// This macro is intended for use in tests. If the values are not approximately equal,
161/// it panics with a detailed error message showing:
162/// - The left and right values
163/// - The tolerances used
164/// - Which specific fields failed (for composite types)
165/// - The actual difference and effective tolerance for failed comparisons
166///
167/// # Syntax
168///
169/// ```text
170/// assert_approx_eq!(left, right) // Default tolerances
171/// assert_approx_eq!(left, right, atol <= tolerance) // Custom absolute tolerance
172/// assert_approx_eq!(left, right, rtol <= tolerance) // Custom relative tolerance
173/// assert_approx_eq!(left, right, atol <= a, rtol <= r) // Both tolerances
174/// assert_approx_eq!(left, right, rtol <= r, atol <= a) // Order doesn't matter
175/// ```
176///
177/// # Examples
178///
179/// ```
180/// use lox_test_utils::assert_approx_eq;
181///
182/// // This passes - values are within default tolerance
183/// assert_approx_eq!(1.0, 1.0 + f64::EPSILON);
184///
185/// // Custom tolerance
186/// assert_approx_eq!(1.0, 1.005, atol <= 0.01);
187///
188/// // Works with vectors
189/// use glam::DVec3;
190/// let v1 = DVec3::new(1.0, 2.0, 3.0);
191/// let v2 = DVec3::new(1.0, 2.0, 3.0 + f64::EPSILON);
192/// assert_approx_eq!(v1, v2);
193/// ```
194///
195/// # Panics
196///
197/// Panics with a detailed error message if the values are not approximately equal:
198///
199/// ```should_panic
200/// use lox_test_utils::assert_approx_eq;
201///
202/// // This will panic with a detailed error message
203/// assert_approx_eq!(1.0, 2.0);
204/// ```
205///
206/// # See Also
207///
208/// - [`approx_eq!`](crate::approx_eq!) - For non-panicking boolean check
209/// - [`assert_approx_ne!`](crate::assert_approx_ne!) - For asserting inequality
210#[macro_export]
211macro_rules! assert_approx_eq {
212 ($lhs:expr, $rhs:expr) => {
213 assert_approx_eq!(
214 $lhs,
215 $rhs,
216 atol <= 0.0,
217 rtol <= $crate::approx_eq::default_rtol(0.0)
218 )
219 };
220 ($lhs:expr, $rhs:expr, rtol <= $rtol:expr) => {
221 assert_approx_eq!($lhs, $rhs, atol <= 0.0, rtol <= $rtol);
222 };
223 ($lhs:expr, $rhs:expr, atol <= $atol:expr) => {
224 assert_approx_eq!(
225 &$lhs,
226 &$rhs,
227 atol <= $atol,
228 rtol <= $crate::approx_eq::default_rtol($atol)
229 )
230 };
231 ($lhs:expr, $rhs:expr, rtol <= $rtol:expr, atol <= $atol:expr) => {
232 assert_approx_eq!($lhs, $rhs, atol <= $atol, rtol <= $rtol)
233 };
234 ($lhs:expr, $rhs:expr, atol <= $atol:expr, rtol <= $rtol:expr) => {{
235 let result = $crate::approx_eq::approx_eq_helper(&$lhs, &$rhs, $atol, $rtol);
236 assert!(
237 result.is_approx_eq(),
238 "{} ≉ {}\n\nAbsolute tolerance: {:?}\nRelative tolerance: {:?}\n\n{}",
239 stringify!($lhs),
240 stringify!($rhs),
241 $atol,
242 $rtol,
243 result
244 )
245 }};
246}
247
248/// Asserts that two values are not approximately equal.
249///
250/// This macro is intended for use in tests. If the values are approximately equal
251/// (within the specified tolerances), it panics with an error message.
252///
253/// # Syntax
254///
255/// ```text
256/// assert_approx_ne!(left, right) // Default tolerances
257/// assert_approx_ne!(left, right, atol <= tolerance) // Custom absolute tolerance
258/// assert_approx_ne!(left, right, rtol <= tolerance) // Custom relative tolerance
259/// assert_approx_ne!(left, right, atol <= a, rtol <= r) // Both tolerances
260/// assert_approx_ne!(left, right, rtol <= r, atol <= a) // Order doesn't matter
261/// ```
262///
263/// # Examples
264///
265/// ```
266/// use lox_test_utils::assert_approx_ne;
267///
268/// // Values differ significantly
269/// assert_approx_ne!(1.0, 2.0);
270/// assert_approx_ne!(1.0, 1.1);
271///
272/// // Custom tolerance - these values differ beyond 1%
273/// assert_approx_ne!(1.0, 1.5, atol <= 0.01);
274///
275/// // Works with vectors
276/// use glam::DVec3;
277/// let v1 = DVec3::new(1.0, 2.0, 3.0);
278/// let v2 = DVec3::new(1.0, 2.0, 5.0); // z differs significantly
279/// assert_approx_ne!(v1, v2);
280/// ```
281///
282/// # Panics
283///
284/// Panics if the values are approximately equal within the tolerances:
285///
286/// ```should_panic
287/// use lox_test_utils::assert_approx_ne;
288///
289/// // This will panic because the values are equal
290/// assert_approx_ne!(1.0, 1.0);
291/// ```
292///
293/// # See Also
294///
295/// - [`approx_ne!`](crate::approx_ne!) - For non-panicking boolean check
296/// - [`assert_approx_eq!`](crate::assert_approx_eq!) - For asserting equality
297#[macro_export]
298macro_rules! assert_approx_ne {
299 ($lhs:expr, $rhs:expr) => {
300 assert_approx_ne!(
301 &$lhs,
302 &$rhs,
303 atol <= 0.0,
304 rtol <= $crate::approx_eq::default_rtol(0.0)
305 )
306 };
307 ($lhs:expr, $rhs:expr, rtol <= $rtol:expr) => {
308 assert_approx_ne!($lhs, $rhs, 0.0, $rtol)
309 };
310 ($lhs:expr, $rhs:expr, atol <= $atol:expr) => {
311 assert_approx_ne!(
312 $lhs,
313 $rhs,
314 atol <= $atol,
315 rtol <= $crate::approx_eq::default_rtol($atol)
316 )
317 };
318 ($lhs:expr, $rhs:expr, rtol <= $rtol:expr, atol <= $atol:expr) => {
319 assert_approx_ne!($lhs, $rhs, atol <= $atol, rtol <= $rtol)
320 };
321 ($lhs:expr, $rhs:expr, atol <= $atol:expr, rtol <= $rtol:expr) => {{
322 let result = $crate::approx_eq::approx_eq_helper(&$lhs, &$rhs, $atol, $rtol);
323 assert!(
324 result.is_approx_ne(),
325 "{:?} ≈ {:?}\n\nAbsolute tolerance: {:?}\nRelative tolerance: {:?}",
326 $lhs,
327 $rhs,
328 $atol,
329 $rtol,
330 )
331 }};
332}