assertables/assert_in/
assert_in_epsilon.rs

1//! Assert a number is within epsilon of another.
2//!
3//! Pseudocode:<br>
4//! | a - b | ≤ ε * min(a, b)
5//!
6//! # Example
7//!
8//! ```rust
9//! use assertables::*;
10//!
11//! let a: i8 = 10;
12//! let b: i8 = 20;
13//! let epsilon: i8 = 1;
14//! assert_in_epsilon!(a, b, epsilon);
15//! ```
16//!
17//!
18//! ## Comparisons
19//!
20//! This crate provides macro groups that test approximations and nearness:
21//!
22//! * [`assert_approx_eq`](macro@crate::assert_approx_eq) and
23//!   [`assert_approx_ne`](macro@crate::assert_approx_ne) test the approximate
24//!   equality within 1e-6. The macro name and the approximate value are chosen
25//!   to be similar to the longtime popular rust crate `assert_approx_eq`.
26//!
27//! * [`assert_in_delta`](macro@crate::assert_in_delta) tests the absolute error
28//!   (i.e. delta). This is the magnitude of the difference between the exact
29//!   value and the approximation.
30//!
31//! * [`assert_in_epsilon`](macro@crate::assert_in_epsilon) tests the relative
32//!   error (i.e. epsilon). This is the absolute error divided by the magnitude
33//!   of the exact value. This can be used to compare approximations of numbers
34//!   of wildly differing size.
35//!
36//! Examples:
37//!
38//! * Approximating the number 100 and 103 has an absolute error (delta) of 3
39//!   and a relative error (epsilon) of 0.03.
40//!
41//! * Approximating the number 1,000,000 and 1,000,003 has an absolute error
42//!   (delta) of 3, and a relative error (epsilon) of 0.000003.
43//!
44//! * For many kinds of applications, the relative error is more important than
45//!   the absolute error.
46//!
47//!
48//! ## Thanks
49//!
50//! * Thanks to [Ashley Williams](https://github.com/ashleygwilliams) for
51//!   creating and maintaining the `assert_approx_eq` crate.
52//!
53//! * Thanks to [Ryan Davis](https://github.com/zenspider) and Ruby minitest for
54//!   creating and maintaining `assert_in_delta` and `assert_in_epsilon` code.
55//!
56//!
57//! # Module macros
58//!
59//! * [`assert_in_epsilon`](macro@crate::assert_in_epsilon)
60//! * [`assert_in_epsilon_as_result`](macro@crate::assert_in_epsilon_as_result)
61//! * [`debug_assert_in_epsilon`](macro@crate::debug_assert_in_epsilon)
62
63/// Assert a number is within epsilon of another.
64///
65/// Pseudocode:<br>
66/// | a - b | ≤ ε * min(a, b)
67///
68/// * If true, return Result `Ok((lhs, rhs))`.
69///
70/// * When false, return [`Err`] with a message and the values of the
71///   expressions with their debug representations.
72///
73/// This macro is useful for runtime checks, such as checking parameters,
74/// or sanitizing inputs, or handling different results in different ways.
75///
76/// # Module macros
77///
78/// * [`assert_in_epsilon`](macro@crate::assert_in_epsilon)
79/// * [`assert_in_epsilon_as_result`](macro@crate::assert_in_epsilon_as_result)
80/// * [`debug_assert_in_epsilon`](macro@crate::debug_assert_in_epsilon)
81///
82#[macro_export]
83macro_rules! assert_in_epsilon_as_result {
84    ($a:expr, $b:expr, $epsilon:expr $(,)?) => {{
85        match (&$a, &$b, &$epsilon) {
86            (a, b, epsilon) => {
87                let abs_diff = if (a >= b) { a - b } else { b - a };
88                let min = if (a < b) { a } else { b };
89                let rhs = *epsilon * min;
90                if abs_diff <= rhs {
91                    Ok((abs_diff, rhs))
92                } else {
93                    Err(
94                        format!(
95                            concat!(
96                                "assertion failed: `assert_in_epsilon!(a, b, ε)`\n",
97                                "https://docs.rs/assertables/9.5.4/assertables/macro.assert_in_epsilon.html\n",
98                                "                   a label: `{}`,\n",
99                                "                   a debug: `{:?}`,\n",
100                                "                   b label: `{}`,\n",
101                                "                   b debug: `{:?}`,\n",
102                                "                   ε label: `{}`,\n",
103                                "                   ε debug: `{:?}`,\n",
104                                "                 | a - b |: `{:?}`,\n",
105                                "             ε * min(a, b): `{:?}`,\n",
106                                " | a - b | ≤ ε * min(a, b): {}",
107                            ),
108                            stringify!($a),
109                            a,
110                            stringify!($b),
111                            b,
112                            stringify!($epsilon),
113                            epsilon,
114                            abs_diff,
115                            rhs,
116                            false
117                        )
118                    )
119                }
120            }
121        }
122    }};
123}
124
125#[cfg(test)]
126mod test_assert_in_epsilon_as_result {
127
128    #[test]
129    fn success() {
130        let a: i8 = 10;
131        let b: i8 = 20;
132        let epsilon: i8 = 1;
133        let actual = assert_in_epsilon_as_result!(a, b, epsilon);
134        assert_eq!(actual.unwrap(), (10, 10));
135    }
136
137    #[test]
138    fn failure() {
139        let a: i8 = 10;
140        let b: i8 = 30;
141        let epsilon: i8 = 1;
142        let actual = assert_in_epsilon_as_result!(a, b, epsilon);
143        let message = concat!(
144            "assertion failed: `assert_in_epsilon!(a, b, ε)`\n",
145            "https://docs.rs/assertables/9.5.4/assertables/macro.assert_in_epsilon.html\n",
146            "                   a label: `a`,\n",
147            "                   a debug: `10`,\n",
148            "                   b label: `b`,\n",
149            "                   b debug: `30`,\n",
150            "                   ε label: `epsilon`,\n",
151            "                   ε debug: `1`,\n",
152            "                 | a - b |: `20`,\n",
153            "             ε * min(a, b): `10`,\n",
154            " | a - b | ≤ ε * min(a, b): false"
155        );
156        assert_eq!(actual.unwrap_err(), message);
157    }
158
159    use std::sync::Once;
160    #[test]
161    fn once() {
162
163        static A: Once = Once::new();
164        fn a() -> i8 {
165            if A.is_completed() { panic!("A.is_completed()") } else { A.call_once(|| {}) }
166            10
167        }
168
169        static B: Once = Once::new();
170        fn b() -> i8 {
171            if B.is_completed() { panic!("B.is_completed()") } else { B.call_once(|| {}) }
172            11
173        }
174
175        static EPSILON: Once = Once::new();
176        fn epsilon() -> i8 {
177            if EPSILON.is_completed() { panic!("EPSILON.is_completed()") } else { EPSILON.call_once(|| {}) }
178            1
179        }
180
181        assert_eq!(A.is_completed(), false);
182        assert_eq!(B.is_completed(), false);
183        assert_eq!(EPSILON.is_completed(), false);
184        let result = assert_in_epsilon_as_result!(a(), b(), epsilon());
185        assert!(result.is_ok());
186        assert_eq!(A.is_completed(), true);
187        assert_eq!(B.is_completed(), true);
188        assert_eq!(EPSILON.is_completed(), true);
189
190    }
191
192}
193
194/// Assert a number is within epsilon of another.
195///
196/// Pseudocode:<br>
197/// | a - b | ≤ ε * min(a, b)
198///
199/// * If true, return `(lhs, rhs)`.
200///
201/// * Otherwise, call [`panic!`] with a message and the values of the
202///   expressions with their debug representations.
203///
204/// # Examples
205///
206/// ```rust
207/// use assertables::*;
208/// # use std::panic;
209///
210/// # fn main() {
211/// let a: i8 = 10;
212/// let b: i8 = 20;
213/// let epsilon: i8 = 1;
214/// assert_in_epsilon!(a, b, epsilon);
215///
216/// # let result = panic::catch_unwind(|| {
217/// // This will panic
218/// let a: i8 = 10;
219/// let b: i8 = 30;
220/// let epsilon: i8 = 1;
221/// assert_in_epsilon!(a, b, epsilon);
222/// # });
223/// // assertion failed: `assert_in_epsilon!(a, b, epsilon)`
224/// // https://docs.rs/assertables/9.5.4/assertables/macro.assert_in_epsilon.html
225/// //                    a label: `a`,
226/// //                    a debug: `10`,
227/// //                    b label: `b`,
228/// //                    b debug: `30`,
229/// //                    ε label: `epsilon`,
230/// //                    ε debug: `1`,
231/// //                  | a - b |: `20`,
232/// //              ε * min(a, b): `10`,\n",
233/// //  | a - b | ≤ ε * min(a, b): false"
234/// # let actual = result.unwrap_err().downcast::<String>().unwrap().to_string();
235/// # let message = concat!(
236/// #     "assertion failed: `assert_in_epsilon!(a, b, ε)`\n",
237/// #     "https://docs.rs/assertables/9.5.4/assertables/macro.assert_in_epsilon.html\n",
238/// #     "                   a label: `a`,\n",
239/// #     "                   a debug: `10`,\n",
240/// #     "                   b label: `b`,\n",
241/// #     "                   b debug: `30`,\n",
242/// #     "                   ε label: `epsilon`,\n",
243/// #     "                   ε debug: `1`,\n",
244/// #     "                 | a - b |: `20`,\n",
245/// #     "             ε * min(a, b): `10`,\n",
246/// #     " | a - b | ≤ ε * min(a, b): false"
247/// # );
248/// # assert_eq!(actual, message);
249/// # }
250/// ```
251///
252/// The macros `assert_in_delta` and `assert_in_epsilon` can test
253/// approximations:
254///
255/// * For an approximation, the absolute error (i.e. delta) is the magnitude of
256///   the difference between the exact value and the approximation. For this,
257///  use the macro
258///
259/// * For an approximation, the relative error (i.e. epsilon) is the absolute
260///   error divided by the magnitude of the exact value. This can be used to
261///   compare approximations of numbers of wildly differing size.
262///
263/// * For example, approximating the number 1,000 with an absolute error of 3
264///   is, in most applications, much worse than approximating the number
265///   1,000,000 with an absolute error of 3; in the first case the relative
266///   error is 0.003 and in the second it is only 0.000003.
267///
268/// * Thanks to Ruby minitest for the example and documentation.
269///
270/// # Module macros
271///
272/// * [`assert_in_epsilon`](macro@crate::assert_in_epsilon)
273/// * [`assert_in_epsilon_as_result`](macro@crate::assert_in_epsilon_as_result)
274/// * [`debug_assert_in_epsilon`](macro@crate::debug_assert_in_epsilon)
275///
276#[macro_export]
277macro_rules! assert_in_epsilon {
278    ($a:expr, $b:expr, $epsilon:expr $(,)?) => {{
279        match $crate::assert_in_epsilon_as_result!($a, $b, $epsilon) {
280            Ok(x) => x,
281            Err(err) => panic!("{}", err),
282        }
283    }};
284    ($a:expr, $b:expr, $epsilon:expr, $($message:tt)+) => {{
285        match $crate::assert_in_epsilon_as_result!($a, $b, $epsilon) {
286            Ok(x) => x,
287            Err(err) => panic!("{}\n{}", format_args!($($message)+), err),
288        }
289    }};
290}
291
292#[cfg(test)]
293mod test_assert_in_epsilon {
294    use std::panic;
295
296    #[test]
297    fn success() {
298        let a: i8 = 10;
299        let b: i8 = 20;
300        let epsilon: i8 = 1;
301        let actual = assert_in_epsilon!(a, b, epsilon);
302        assert_eq!(actual, (10, 10));
303    }
304
305    #[test]
306    fn failure() {
307        let a: i8 = 10;
308        let b: i8 = 30;
309        let epsilon: i8 = 1;
310        let result = panic::catch_unwind(|| {
311            let _actual = assert_in_epsilon!(a, b, epsilon);
312        });
313        let message = concat!(
314            "assertion failed: `assert_in_epsilon!(a, b, ε)`\n",
315            "https://docs.rs/assertables/9.5.4/assertables/macro.assert_in_epsilon.html\n",
316            "                   a label: `a`,\n",
317            "                   a debug: `10`,\n",
318            "                   b label: `b`,\n",
319            "                   b debug: `30`,\n",
320            "                   ε label: `epsilon`,\n",
321            "                   ε debug: `1`,\n",
322            "                 | a - b |: `20`,\n",
323            "             ε * min(a, b): `10`,\n",
324            " | a - b | ≤ ε * min(a, b): false"
325        );
326        assert_eq!(
327            result
328                .unwrap_err()
329                .downcast::<String>()
330                .unwrap()
331                .to_string(),
332            message
333        );
334    }
335}
336
337/// Assert a number is within epsilon of another.
338///
339/// Pseudocode:<br>
340/// | a - b | ≤ ε * min(a, b)
341///
342/// This macro provides the same statements as [`assert_in_epsilon`](macro.assert_in_epsilon.html),
343/// except this macro's statements are only enabled in non-optimized
344/// builds by default. An optimized build will not execute this macro's
345/// statements unless `-C debug-assertions` is passed to the compiler.
346///
347/// This macro is useful for checks that are too expensive to be present
348/// in a release build but may be helpful during development.
349///
350/// The result of expanding this macro is always type checked.
351///
352/// An unchecked assertion allows a program in an inconsistent state to
353/// keep running, which might have unexpected consequences but does not
354/// introduce unsafety as long as this only happens in safe code. The
355/// performance cost of assertions, however, is not measurable in general.
356/// Replacing `assert*!` with `debug_assert*!` is thus only encouraged
357/// after thorough profiling, and more importantly, only in safe code!
358///
359/// This macro is intended to work in a similar way to
360/// [`::std::debug_assert`](https://doc.rust-lang.org/std/macro.debug_assert.html).
361///
362/// # Module macros
363///
364/// * [`assert_in_epsilon`](macro@crate::assert_in_epsilon)
365/// * [`assert_in_epsilon`](macro@crate::assert_in_epsilon)
366/// * [`debug_assert_in_epsilon`](macro@crate::debug_assert_in_epsilon)
367///
368#[macro_export]
369macro_rules! debug_assert_in_epsilon {
370    ($($arg:tt)*) => {
371        if $crate::cfg!(debug_assertions) {
372            $crate::assert_in_epsilon!($($arg)*);
373        }
374    };
375}