1// Copyright (c) 2018 The predicates-rs Project Developers.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::fmt;
10
11use float_cmp::ApproxEq;
12use float_cmp::Ulps;
13
14use crate::reflection;
15use crate::Predicate;
16
17/// Predicate that ensures two numbers are "close" enough, understanding that rounding errors
18/// occur.
19///
20/// This is created by the `predicate::float::is_close`.
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub struct IsClosePredicate {
23 target: f64,
24 epsilon: f64,
25 ulps: <f64 as Ulps>::U,
26}
27
28impl IsClosePredicate {
29 /// Set the amount of error allowed.
30 ///
31 /// Values `1`-`5` should work in most cases. Sometimes more control is needed and you will
32 /// need to set `IsClosePredicate::epsilon` separately from `IsClosePredicate::ulps`.
33 ///
34 /// # Examples
35 ///
36 /// ```
37 /// use predicates::prelude::*;
38 ///
39 /// let a = 0.15_f64 + 0.15_f64 + 0.15_f64;
40 /// let predicate_fn = predicate::float::is_close(a).distance(5);
41 /// ```
42 pub fn distance(mut self, distance: <f64 as Ulps>::U) -> Self {
43 self.epsilon = (distance as f64) * ::std::f64::EPSILON;
44 self.ulps = distance;
45 self
46 }
47
48 /// Set the absolute deviation allowed.
49 ///
50 /// This is meant to handle problems near `0`. Values `1.`-`5.` epislons should work in most
51 /// cases.
52 ///
53 /// # Examples
54 ///
55 /// ```
56 /// use predicates::prelude::*;
57 ///
58 /// let a = 0.15_f64 + 0.15_f64 + 0.15_f64;
59 /// let predicate_fn = predicate::float::is_close(a).epsilon(5.0 * ::std::f64::EPSILON);
60 /// ```
61 pub fn epsilon(mut self, epsilon: f64) -> Self {
62 self.epsilon = epsilon;
63 self
64 }
65
66 /// Set the relative deviation allowed.
67 ///
68 /// This is meant to handle large numbers. Values `1`-`5` should work in most cases.
69 ///
70 /// # Examples
71 ///
72 /// ```
73 /// use predicates::prelude::*;
74 ///
75 /// let a = 0.15_f64 + 0.15_f64 + 0.15_f64;
76 /// let predicate_fn = predicate::float::is_close(a).ulps(5);
77 /// ```
78 pub fn ulps(mut self, ulps: <f64 as Ulps>::U) -> Self {
79 self.ulps = ulps;
80 self
81 }
82}
83
84impl Predicate<f64> for IsClosePredicate {
85 fn eval(&self, variable: &f64) -> bool {
86 variable.approx_eq(
87 self.target,
88 float_cmp::F64Margin {
89 epsilon: self.epsilon,
90 ulps: self.ulps,
91 },
92 )
93 }
94
95 fn find_case<'a>(&'a self, expected: bool, variable: &f64) -> Option<reflection::Case<'a>> {
96 let actual = self.eval(variable);
97 if expected == actual {
98 Some(
99 reflection::Case::new(Some(self), actual)
100 .add_product(reflection::Product::new(
101 "actual epsilon",
102 (variable - self.target).abs(),
103 ))
104 .add_product(reflection::Product::new(
105 "actual ulps",
106 variable.ulps(&self.target).abs(),
107 )),
108 )
109 } else {
110 None
111 }
112 }
113}
114
115impl reflection::PredicateReflection for IsClosePredicate {
116 fn parameters<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Parameter<'a>> + 'a> {
117 let params = vec![
118 reflection::Parameter::new("epsilon", &self.epsilon),
119 reflection::Parameter::new("ulps", &self.ulps),
120 ];
121 Box::new(params.into_iter())
122 }
123}
124
125impl fmt::Display for IsClosePredicate {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 let palette = crate::Palette::current();
128 write!(
129 f,
130 "{} {} {}",
131 palette.var.paint("var"),
132 palette.description.paint("!="),
133 palette.expected.paint(self.target),
134 )
135 }
136}
137
138/// Create a new `Predicate` that ensures two numbers are "close" enough, understanding that
139/// rounding errors occur.
140///
141/// # Examples
142///
143/// ```
144/// use predicates::prelude::*;
145///
146/// let a = 0.15_f64 + 0.15_f64 + 0.15_f64;
147/// let b = 0.1_f64 + 0.1_f64 + 0.25_f64;
148/// let predicate_fn = predicate::float::is_close(a);
149/// assert_eq!(true, predicate_fn.eval(&b));
150/// assert_eq!(false, predicate_fn.distance(0).eval(&b));
151/// ```
152pub fn is_close(target: f64) -> IsClosePredicate {
153 IsClosePredicate {
154 target,
155 epsilon: 2.0 * ::std::f64::EPSILON,
156 ulps: 2,
157 }
158}
159