Skip to main content

test_that/matchers/
near_matcher.rs

1// Copyright 2022 Google LLC
2// Copyright 2026 Bradford Hovinen <bradford@hovinen.me>
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::{
17    description::Description,
18    matcher::{Describable, Matcher, MatcherResult},
19};
20use core::fmt::Debug;
21use num_traits::{Float, FloatConst};
22
23/// Matches a value equal within `max_abs_error` of `expected`.
24///
25/// The type `T` of the actual, `expected`, and `max_abs_error` values must
26/// implement [`Float`].
27///
28/// The values `expected` and `max_abs_error` may not be NaN. The value
29/// `max_abs_error` must be non-negative. The matcher panics on construction
30/// otherwise.
31///
32/// ```
33/// # use test_that::prelude::*;
34/// # fn should_pass_1() -> TestResult<()> {
35/// verify_that!(1.0, near(1.0, 0.1))?; // Passes
36/// verify_that!(1.01, near(1.0, 0.1))?; // Passes
37/// verify_that!(1.25, near(1.0, 0.25))?; // Passes
38/// verify_that!(0.75, near(1.0, 0.25))?; // Passes
39/// #     Ok(())
40/// # }
41/// # fn should_fail_1() -> TestResult<()> {
42/// verify_that!(1.101, near(1.0, 0.1))?; // Fails
43/// #     Ok(())
44/// # }
45/// # fn should_fail_2() -> TestResult<()> {
46/// verify_that!(0.899, near(1.0, 0.1))?; // Fails
47/// #     Ok(())
48/// # }
49/// # fn should_pass_2() -> TestResult<()> {
50/// verify_that!(100.25, near(100.0, 0.25))?; // Passes
51/// #     Ok(())
52/// # }
53/// # should_pass_1().unwrap();
54/// # should_fail_1().unwrap_err();
55/// # should_fail_2().unwrap_err();
56/// # should_pass_2().unwrap();
57/// ```
58///
59/// The default behaviour for special values is consistent with the IEEE
60/// floating point standard. Thus infinity is infinitely far away from any
61/// floating point value:
62///
63/// ```
64/// # use test_that::prelude::*;
65/// # fn should_fail_1() -> TestResult<()> {
66/// verify_that!(f64::INFINITY, near(0.0, f64::MAX))?; // Fails
67/// #     Ok(())
68/// # }
69/// # fn should_fail_2() -> TestResult<()> {
70/// verify_that!(0.0, near(f64::INFINITY, f64::MAX))?; // Fails
71/// #     Ok(())
72/// # }
73/// # fn should_fail_3() -> TestResult<()> {
74/// verify_that!(f64::INFINITY, near(f64::INFINITY, f64::MAX))?; // Fails
75/// #     Ok(())
76/// # }
77/// # should_fail_1().unwrap_err();
78/// # should_fail_2().unwrap_err();
79/// # should_fail_3().unwrap_err();
80/// ```
81///
82/// Similarly, by default, `NaN` is infinitely far away from any value:
83///
84/// ```
85/// # use test_that::prelude::*;
86/// # fn should_fail_1() -> TestResult<()> {
87/// verify_that!(f64::NAN, near(0.0, f64::MAX))?; // Fails
88/// #     Ok(())
89/// # }
90/// # fn should_fail_2() -> TestResult<()> {
91/// verify_that!(0.0, near(f64::NAN, f64::MAX))?; // Fails
92/// #     Ok(())
93/// # }
94/// # fn should_fail_3() -> TestResult<()> {
95/// verify_that!(f64::NAN, near(f64::NAN, f64::MAX))?; // Fails
96/// #     Ok(())
97/// # }
98/// # should_fail_1().unwrap_err();
99/// # should_fail_2().unwrap_err();
100/// # should_fail_3().unwrap_err();
101/// ```
102///
103/// To treat two `NaN` values as equal, use the method
104/// [`NearMatcher::nans_are_equal`].
105///
106/// ```
107/// # use test_that::prelude::*;
108/// # fn should_pass() -> TestResult<()> {
109/// verify_that!(f64::NAN, near(f64::NAN, f64::MAX).nans_are_equal())?; // Passes
110/// #     Ok(())
111/// # }
112/// # should_pass().unwrap();
113/// ```
114pub fn near<T: Debug + Float + Copy>(expected: T, max_abs_error: T) -> NearMatcher<T> {
115    if max_abs_error.is_nan() {
116        panic!("max_abs_error must not be NaN");
117    }
118    if max_abs_error < T::zero() {
119        panic!("max_abs_error must be non-negative");
120    }
121    NearMatcher { expected, max_abs_error, nans_are_equal: false }
122}
123
124/// Matches a value approximately equal to `expected`.
125///
126/// This automatically computes a tolerance from the magnitude of `expected` and
127/// matches any actual value within this tolerance of the expected value. The
128/// tolerance is chosen to account for the inaccuracies in most ordinary
129/// floating point calculations.
130///
131/// Otherwise this works analogously to [`near`]; see its documentation for
132/// further notes.
133pub fn approx_eq<T: Debug + Float + FloatConst + Copy>(expected: T) -> NearMatcher<T> {
134    // The FloatConst trait doesn't offer 2 as a constant but does offer 1.
135    let five_bits_of_mantissa = (T::one() + T::one()).powi(5);
136    let abs_tolerance = five_bits_of_mantissa * T::epsilon();
137    let max_abs_error = T::max(expected.abs() * abs_tolerance, abs_tolerance);
138    NearMatcher { expected, max_abs_error, nans_are_equal: false }
139}
140
141/// A matcher which matches floating-point numbers approximately equal to its
142/// expected value.
143pub struct NearMatcher<T: Debug> {
144    expected: T,
145    max_abs_error: T,
146    nans_are_equal: bool,
147}
148
149impl<T: Debug> NearMatcher<T> {
150    /// Configures this instance to treat two NaNs as equal.
151    ///
152    /// This behaviour differs from the IEEE standad for floating point which
153    /// treats two NaNs as infinitely far apart.
154    pub fn nans_are_equal(mut self) -> Self {
155        self.nans_are_equal = true;
156        self
157    }
158
159    /// Configures this instance to treat two NaNs as not equal.
160    ///
161    /// This behaviour complies with the IEEE standad for floating point. It is
162    /// the default behaviour for this matcher, so invoking this method is
163    /// usually redunant.
164    pub fn nans_are_not_equal(mut self) -> Self {
165        self.nans_are_equal = false;
166        self
167    }
168}
169
170impl<T: Debug + Float> Matcher<T> for NearMatcher<T> {
171    fn matches(&self, actual: &T) -> MatcherResult {
172        if self.nans_are_equal && self.expected.is_nan() && actual.is_nan() {
173            return MatcherResult::Match;
174        }
175
176        let delta = *actual - self.expected;
177        if delta >= -self.max_abs_error && delta <= self.max_abs_error {
178            MatcherResult::Match
179        } else {
180            MatcherResult::NoMatch
181        }
182    }
183}
184
185impl<T: Debug> Describable for NearMatcher<T> {
186    fn describe(&self, matcher_result: MatcherResult) -> Description {
187        match matcher_result {
188            MatcherResult::Match => {
189                format!("is within {:?} of {:?}", self.max_abs_error, self.expected).into()
190            }
191            MatcherResult::NoMatch => {
192                format!("isn't within {:?} of {:?}", self.max_abs_error, self.expected).into()
193            }
194        }
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::{approx_eq, near};
201    use crate::matcher::{Matcher, MatcherResult};
202    use crate::prelude::*;
203
204    #[test]
205    fn matches_value_inside_range() -> TestResult<()> {
206        let matcher = near(1.0f64, 0.1f64);
207
208        let result = matcher.matches(&1.0f64);
209
210        verify_that!(result, eq(MatcherResult::Match))
211    }
212
213    #[test]
214    fn matches_value_at_low_end_of_range() -> TestResult<()> {
215        let matcher = near(1.0f64, 0.1f64);
216
217        let result = matcher.matches(&0.9f64);
218
219        verify_that!(result, eq(MatcherResult::Match))
220    }
221
222    #[test]
223    fn matches_value_at_high_end_of_range() -> TestResult<()> {
224        let matcher = near(1.0f64, 0.25f64);
225
226        let result = matcher.matches(&1.25f64);
227
228        verify_that!(result, eq(MatcherResult::Match))
229    }
230
231    #[test]
232    fn does_not_match_value_below_low_end_of_range() -> TestResult<()> {
233        let matcher = near(1.0f64, 0.1f64);
234
235        let result = matcher.matches(&0.899999f64);
236
237        verify_that!(result, eq(MatcherResult::NoMatch))
238    }
239
240    #[test]
241    fn does_not_match_value_above_high_end_of_range() -> TestResult<()> {
242        let matcher = near(1.0f64, 0.1f64);
243
244        let result = matcher.matches(&1.100001f64);
245
246        verify_that!(result, eq(MatcherResult::NoMatch))
247    }
248
249    #[test]
250    fn nan_is_not_near_a_number() -> TestResult<()> {
251        let matcher = near(0.0f64, f64::MAX);
252
253        let result = matcher.matches(&f64::NAN);
254
255        verify_that!(result, eq(MatcherResult::NoMatch))
256    }
257
258    #[test]
259    fn nan_is_not_near_nan_by_default() -> TestResult<()> {
260        verify_that!(f64::NAN, not(near(f64::NAN, f64::MAX)))
261    }
262
263    #[test]
264    fn nan_is_not_near_nan_when_explicitly_configured() -> TestResult<()> {
265        verify_that!(f64::NAN, not(near(f64::NAN, f64::MAX).nans_are_not_equal()))
266    }
267
268    #[test]
269    fn nan_is_near_nan_if_nans_are_equal() -> TestResult<()> {
270        verify_that!(f64::NAN, near(f64::NAN, f64::MAX).nans_are_equal())
271    }
272
273    #[test]
274    fn nan_is_not_near_number_when_nans_are_equal() -> TestResult<()> {
275        verify_that!(f64::NAN, not(near(0.0, f64::MAX).nans_are_equal()))
276    }
277
278    #[test]
279    fn number_is_not_near_nan_when_nans_are_equal() -> TestResult<()> {
280        verify_that!(0.0, not(near(f64::NAN, f64::MAX).nans_are_equal()))
281    }
282
283    #[test]
284    fn inf_is_not_near_inf() -> TestResult<()> {
285        let matcher = near(f64::INFINITY, f64::MAX);
286
287        let result = matcher.matches(&f64::INFINITY);
288
289        verify_that!(result, eq(MatcherResult::NoMatch))
290    }
291
292    #[test]
293    fn inf_is_not_near_a_number() -> TestResult<()> {
294        let matcher = near(f64::INFINITY, f64::MAX);
295
296        let result = matcher.matches(&f64::MIN);
297
298        verify_that!(result, eq(MatcherResult::NoMatch))
299    }
300
301    #[test]
302    fn any_two_numbers_are_within_inf_of_each_other() -> TestResult<()> {
303        let matcher = near(f64::MIN, f64::INFINITY);
304
305        let result = matcher.matches(&f64::MAX);
306
307        verify_that!(result, eq(MatcherResult::Match))
308    }
309
310    #[::core::prelude::v1::test]
311    #[should_panic]
312    fn panics_if_max_abs_error_is_nan() {
313        near(0.0, f64::NAN);
314    }
315
316    #[::core::prelude::v1::test]
317    #[should_panic]
318    fn panics_if_tolerance_is_negative() {
319        near(0.0, -1.0);
320    }
321
322    #[test]
323    fn approx_eq_matches_equal_number() -> TestResult<()> {
324        verify_that!(1.0f64, approx_eq(1.0f64))
325    }
326
327    #[test]
328    fn approx_eq_matches_really_close_f64_number() -> TestResult<()> {
329        verify_that!(1.0f64, approx_eq(1.0 + 16.0 * f64::EPSILON))
330    }
331
332    #[test]
333    fn approx_eq_matches_really_close_f64_number_to_large_number() -> TestResult<()> {
334        verify_that!(1000f64, approx_eq(1000.0 + 16000.0 * f64::EPSILON))
335    }
336
337    #[test]
338    fn approx_eq_matches_really_close_f64_number_to_zero() -> TestResult<()> {
339        verify_that!(16.0 * f64::EPSILON, approx_eq(0.0))
340    }
341
342    #[test]
343    fn approx_eq_matches_really_close_f32_number() -> TestResult<()> {
344        verify_that!(1.0f32, approx_eq(1.0 + 16.0 * f32::EPSILON))
345    }
346
347    #[test]
348    fn approx_eq_does_not_match_distant_number() -> TestResult<()> {
349        verify_that!(0.0f64, not(approx_eq(1.0f64)))
350    }
351}