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