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 | /// ``` |
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 | |
84 | impl 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 | |
115 | impl 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 | |
125 | impl 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 | /// ``` |
152 | pub fn is_close(target: f64) -> IsClosePredicate { |
153 | IsClosePredicate { |
154 | target, |
155 | epsilon: 2.0 * ::std::f64::EPSILON, |
156 | ulps: 2, |
157 | } |
158 | } |
159 | |