Skip to main content

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}