use crate::{
description::Description,
matcher::{Matcher, MatcherBase, MatcherResult},
};
use num_traits::{Float, FloatConst};
use std::{borrow::Borrow, fmt::Debug};
#[track_caller]
pub fn near<T: Debug + Float + Copy>(expected: T, max_abs_error: T) -> NearMatcher<T> {
if max_abs_error.is_nan() {
panic!("max_abs_error must not be NaN");
}
if max_abs_error < T::zero() {
panic!("max_abs_error must be non-negative");
}
NearMatcher { expected, max_abs_error, nans_are_equal: false }
}
pub fn approx_eq<T: Debug + Float + FloatConst + Copy>(expected: T) -> NearMatcher<T> {
let five_bits_of_mantissa = (T::one() + T::one()).powi(5);
let abs_tolerance = five_bits_of_mantissa * T::epsilon();
let max_abs_error = T::max(expected.abs() * abs_tolerance, abs_tolerance);
NearMatcher { expected, max_abs_error, nans_are_equal: false }
}
#[derive(MatcherBase)]
pub struct NearMatcher<T: Debug> {
expected: T,
max_abs_error: T,
nans_are_equal: bool,
}
impl<T: Debug> NearMatcher<T> {
pub fn nans_are_equal(mut self) -> Self {
self.nans_are_equal = true;
self
}
pub fn nans_are_not_equal(mut self) -> Self {
self.nans_are_equal = false;
self
}
}
impl<T: Borrow<F> + Debug + Copy, F: Debug + Float> Matcher<T> for NearMatcher<F> {
fn matches(&self, actual: T) -> MatcherResult {
if self.nans_are_equal && self.expected.is_nan() && actual.borrow().is_nan() {
return MatcherResult::Match;
}
let delta = *actual.borrow() - self.expected;
if delta >= -self.max_abs_error && delta <= self.max_abs_error {
MatcherResult::Match
} else {
MatcherResult::NoMatch
}
}
fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
MatcherResult::Match => {
format!("is within {:?} of {:?}", self.max_abs_error, self.expected).into()
}
MatcherResult::NoMatch => {
format!("isn't within {:?} of {:?}", self.max_abs_error, self.expected).into()
}
}
}
}
#[cfg(test)]
mod tests {
use crate::matcher::MatcherResult;
use crate::prelude::*;
use crate::Result;
#[test]
fn matches_value_inside_range() -> Result<()> {
let matcher = near(1.0f64, 0.1f64);
let result = matcher.matches(1.0f64);
verify_that!(result, eq(MatcherResult::Match))
}
#[test]
fn matches_value_at_low_end_of_range() -> Result<()> {
let matcher = near(1.0f64, 0.1f64);
let result = matcher.matches(0.9f64);
verify_that!(result, eq(MatcherResult::Match))
}
#[test]
fn matches_value_at_high_end_of_range() -> Result<()> {
let matcher = near(1.0f64, 0.25f64);
let result = matcher.matches(1.25f64);
verify_that!(result, eq(MatcherResult::Match))
}
#[test]
fn does_not_match_value_below_low_end_of_range() -> Result<()> {
let matcher = near(1.0f64, 0.1f64);
let result = matcher.matches(0.899999f64);
verify_that!(result, eq(MatcherResult::NoMatch))
}
#[test]
fn does_not_match_value_above_high_end_of_range() -> Result<()> {
let matcher = near(1.0f64, 0.1f64);
let result = matcher.matches(1.100001f64);
verify_that!(result, eq(MatcherResult::NoMatch))
}
#[test]
fn nan_is_not_near_a_number() -> Result<()> {
let matcher = near(0.0f64, f64::MAX);
let result = matcher.matches(f64::NAN);
verify_that!(result, eq(MatcherResult::NoMatch))
}
#[test]
fn nan_is_not_near_nan_by_default() -> Result<()> {
verify_that!(f64::NAN, not(near(f64::NAN, f64::MAX)))
}
#[test]
fn nan_is_not_near_nan_when_explicitly_configured() -> Result<()> {
verify_that!(f64::NAN, not(near(f64::NAN, f64::MAX).nans_are_not_equal()))
}
#[test]
fn nan_is_near_nan_if_nans_are_equal() -> Result<()> {
verify_that!(f64::NAN, near(f64::NAN, f64::MAX).nans_are_equal())
}
#[test]
fn nan_is_not_near_number_when_nans_are_equal() -> Result<()> {
verify_that!(f64::NAN, not(near(0.0, f64::MAX).nans_are_equal()))
}
#[test]
fn number_is_not_near_nan_when_nans_are_equal() -> Result<()> {
verify_that!(0.0, not(near(f64::NAN, f64::MAX).nans_are_equal()))
}
#[test]
fn inf_is_not_near_inf() -> Result<()> {
let matcher = near(f64::INFINITY, f64::MAX);
let result = matcher.matches(f64::INFINITY);
verify_that!(result, eq(MatcherResult::NoMatch))
}
#[test]
fn inf_is_not_near_a_number() -> Result<()> {
let matcher = near(f64::INFINITY, f64::MAX);
let result = matcher.matches(f64::MIN);
verify_that!(result, eq(MatcherResult::NoMatch))
}
#[test]
fn any_two_numbers_are_within_inf_of_each_other() -> Result<()> {
let matcher = near(f64::MIN, f64::INFINITY);
let result = matcher.matches(f64::MAX);
verify_that!(result, eq(MatcherResult::Match))
}
#[::core::prelude::v1::test]
#[should_panic]
fn panics_if_max_abs_error_is_nan() {
near(0.0, f64::NAN);
}
#[::core::prelude::v1::test]
#[should_panic]
fn panics_if_tolerance_is_negative() {
near(0.0, -1.0);
}
#[test]
fn approx_eq_matches_equal_number() -> Result<()> {
verify_that!(1.0f64, approx_eq(1.0f64))
}
#[test]
fn approx_eq_matches_really_close_f64_number() -> Result<()> {
verify_that!(1.0f64, approx_eq(1.0 + 16.0 * f64::EPSILON))
}
#[test]
fn approx_eq_matches_really_close_f64_number_to_large_number() -> Result<()> {
verify_that!(1000f64, approx_eq(1000.0 + 16000.0 * f64::EPSILON))
}
#[test]
fn approx_eq_matches_really_close_f64_number_to_zero() -> Result<()> {
verify_that!(16.0 * f64::EPSILON, approx_eq(0.0))
}
#[test]
fn approx_eq_matches_really_close_f32_number() -> Result<()> {
verify_that!(1.0f32, approx_eq(1.0 + 16.0 * f32::EPSILON))
}
#[test]
fn approx_eq_does_not_match_distant_number() -> Result<()> {
verify_that!(0.0f64, not(approx_eq(1.0f64)))
}
#[test]
fn approx_eq_supports_ref() -> Result<()> {
verify_that!(&0.0f64, approx_eq(0.0f64))
}
#[test]
fn approx_eq_supports_container_matchers() -> Result<()> {
verify_that!(
vec![1., 2., 3.],
unordered_elements_are![approx_eq(2.), approx_eq(3.), approx_eq(1.)]
)
}
}