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 
9 use std::fmt;
10 
11 use float_cmp::ApproxEq;
12 use float_cmp::Ulps;
13 
14 use crate::reflection;
15 use 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)]
22 pub struct IsClosePredicate {
23     target: f64,
24     epsilon: f64,
25     ulps: <f64 as Ulps>::U,
26 }
27 
28 impl 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     /// ```
distance(mut self, distance: <f64 as Ulps>::U) -> Self42     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     /// ```
epsilon(mut self, epsilon: f64) -> Self61     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     /// ```
ulps(mut self, ulps: <f64 as Ulps>::U) -> Self78     pub fn ulps(mut self, ulps: <f64 as Ulps>::U) -> Self {
79         self.ulps = ulps;
80         self
81     }
82 }
83 
84 impl Predicate<f64> for IsClosePredicate {
eval(&self, variable: &f64) -> bool85     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 
find_case<'a>(&'a self, expected: bool, variable: &f64) -> Option<reflection::Case<'a>>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 
115 impl reflection::PredicateReflection for IsClosePredicate {
parameters<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Parameter<'a>> + 'a>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 
125 impl fmt::Display for IsClosePredicate {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result126     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127         let palette = crate::Palette::new(f.alternate());
128         write!(
129             f,
130             "{} {} {}",
131             palette.var("var"),
132             palette.description("!="),
133             palette.expected(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 /// ```
is_close(target: f64) -> IsClosePredicate152 pub fn is_close(target: f64) -> IsClosePredicate {
153     IsClosePredicate {
154         target,
155         epsilon: 2.0 * ::std::f64::EPSILON,
156         ulps: 2,
157     }
158 }
159