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