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 |