1 | // Copyright 2014-2020 Optimal Computing (NZ) Ltd. |
---|---|

2 | // Licensed under the MIT license. See LICENSE for details. |

3 | |

4 | //! # float-cmp |

5 | //! |

6 | //! float-cmp defines and implements traits for approximate comparison of floating point types |

7 | //! which have fallen away from exact equality due to the limited precision available within |

8 | //! floating point representations. Implementations of these traits are provided for `f32` |

9 | //! and `f64` types. |

10 | //! |

11 | //! When I was a kid in the '80s, the programming rule was "Never compare floating point |

12 | //! numbers". If you can follow that rule and still get the outcome you desire, then more |

13 | //! power to you. However, if you really do need to compare them, this crate provides a |

14 | //! reasonable way to do so. |

15 | //! |

16 | //! Another crate `efloat` offers another solution by providing a floating point type that |

17 | //! tracks its error bounds as operations are performed on it, and thus can implement the |

18 | //! `ApproxEq` trait in this crate more accurately, without specifying a `Margin`. |

19 | //! |

20 | //! The recommended go-to solution (although it may not be appropriate in all cases) is the |

21 | //! `approx_eq()` function in the `ApproxEq` trait (or better yet, the macros). For `f32` |

22 | //! and `f64`, the `F32Margin` and `F64Margin` types are provided for specifying margins as |

23 | //! both an epsilon value and an ULPs value, and defaults are provided via `Default` |

24 | //! (although there is no perfect default value that is always appropriate, so beware). |

25 | //! |

26 | //! Several other traits are provided including `Ulps`, `ApproxEqUlps`, `ApproxOrdUlps`, and |

27 | //! `ApproxEqRatio`. |

28 | //! |

29 | //! ## The problem |

30 | //! |

31 | //! Floating point operations must round answers to the nearest representable number. Multiple |

32 | //! operations may result in an answer different from what you expect. In the following example, |

33 | //! the assert will fail, even though the printed output says "0.45 == 0.45": |

34 | //! |

35 | //! ```should_panic |

36 | //! # extern crate float_cmp; |

37 | //! # use float_cmp::ApproxEq; |

38 | //! # fn main() { |

39 | //! let a: f32 = 0.15 + 0.15 + 0.15; |

40 | //! let b: f32 = 0.1 + 0.1 + 0.25; |

41 | //! println!("{} == {}", a, b); |

42 | //! assert!(a==b) // Fails, because they are not exactly equal |

43 | //! # } |

44 | //! ``` |

45 | //! |

46 | //! This fails because the correct answer to most operations isn't exactly representable, and so |

47 | //! your computer's processor chooses to represent the answer with the closest value it has |

48 | //! available. This introduces error, and this error can accumulate as multiple operations are |

49 | //! performed. |

50 | //! |

51 | //! ## The solution |

52 | //! |

53 | //! With `ApproxEq`, we can get the answer we intend: |

54 | //! |

55 | //! ``` |

56 | //! # #[macro_use] |

57 | //! # extern crate float_cmp; |

58 | //! # use float_cmp::{ApproxEq, F32Margin}; |

59 | //! # fn main() { |

60 | //! let a: f32 = 0.15 + 0.15 + 0.15; |

61 | //! let b: f32 = 0.1 + 0.1 + 0.25; |

62 | //! println!("{} == {}", a, b); |

63 | //! // They are equal, within 2 ulps |

64 | //! assert!( approx_eq!(f32, a, b, ulps = 2) ); |

65 | //! # } |

66 | //! ``` |

67 | //! |

68 | //! ## Some explanation |

69 | //! |

70 | //! We use the term ULP (units of least precision, or units in the last place) to mean the |

71 | //! difference between two adjacent floating point representations (adjacent meaning that there is |

72 | //! no floating point number between them). This term is borrowed from prior work (personally I |

73 | //! would have chosen "quanta"). The size of an ULP (measured as a float) varies |

74 | //! depending on the exponents of the floating point numbers in question. That is a good thing, |

75 | //! because as numbers fall away from equality due to the imprecise nature of their representation, |

76 | //! they fall away in ULPs terms, not in absolute terms. Pure epsilon-based comparisons are |

77 | //! absolute and thus don't map well to the nature of the additive error issue. They work fine |

78 | //! for many ranges of numbers, but not for others (consider comparing -0.0000000028 |

79 | //! to +0.00000097). |

80 | //! |

81 | //! ## Using this crate |

82 | //! |

83 | //! By default this crate enables the `ratio` module providing the `ApproxEqRatio` trait. This |

84 | //! feature pulls in `num-traits`. If you disable this feature, you'll need to either enable |

85 | //! `num-traits` directly or else enable the `std` feature; otherwise it won't compile. This crate |

86 | //! is `#![no_std]` unless you enable the `std` feature. |

87 | //! |

88 | //! You can use the `ApproxEq` trait directly like so: |

89 | //! |

90 | //! ``` |

91 | //! # extern crate float_cmp; |

92 | //! # use float_cmp::{ApproxEq, F32Margin}; |

93 | //! # fn main() { |

94 | //! # let a: f32 = 0.15 + 0.15 + 0.15; |

95 | //! # let b: f32 = 0.1 + 0.1 + 0.25; |

96 | //! assert!( a.approx_eq(b, F32Margin { ulps: 2, epsilon: 0.0 }) ); |

97 | //! # } |

98 | //! ``` |

99 | //! |

100 | //! We have implemented `From<(f32,i32)>` for `F32Margin` (and similarly for `F64Margin`) |

101 | //! so you can use this shorthand: |

102 | //! |

103 | //! ``` |

104 | //! # extern crate float_cmp; |

105 | //! # use float_cmp::{ApproxEq, F32Margin}; |

106 | //! # fn main() { |

107 | //! # let a: f32 = 0.15 + 0.15 + 0.15; |

108 | //! # let b: f32 = 0.1 + 0.1 + 0.25; |

109 | //! assert!( a.approx_eq(b, (0.0, 2)) ); |

110 | //! # } |

111 | //! ``` |

112 | //! |

113 | //! With macros, it is easier to be explicit about which type of margin you wish to set, |

114 | //! without mentioning the other one (the other one will be zero). But the downside is |

115 | //! that you have to specify the type you are dealing with: |

116 | //! |

117 | //! ``` |

118 | //! # #[macro_use] |

119 | //! # extern crate float_cmp; |

120 | //! # use float_cmp::{ApproxEq, F32Margin}; |

121 | //! # fn main() { |

122 | //! # let a: f32 = 0.15 + 0.15 + 0.15; |

123 | //! # let b: f32 = 0.1 + 0.1 + 0.25; |

124 | //! assert!( approx_eq!(f32, a, b, ulps = 2) ); |

125 | //! assert!( approx_eq!(f32, a, b, epsilon = 0.00000003) ); |

126 | //! assert!( approx_eq!(f32, a, b, epsilon = 0.00000003, ulps = 2) ); |

127 | //! assert!( approx_eq!(f32, a, b, (0.0, 2)) ); |

128 | //! assert!( approx_eq!(f32, a, b, F32Margin { epsilon: 0.0, ulps: 2 }) ); |

129 | //! assert!( approx_eq!(f32, a, b, F32Margin::default()) ); |

130 | //! assert!( approx_eq!(f32, a, b) ); // uses the default |

131 | //! # } |

132 | //! ``` |

133 | //! |

134 | //! For most cases, I recommend you use a smallish integer for the `ulps` parameter (1 to 5 |

135 | //! or so), and a similar small multiple of the floating point's EPSILON constant (1.0 to 5.0 |

136 | //! or so), but there are *plenty* of cases where this is insufficient. |

137 | //! |

138 | //! ## Implementing these traits |

139 | //! |

140 | //! You can implement `ApproxEq` for your own complex types like shown below. |

141 | //! The floating point type `F` must be `Copy`, but for large types you can implement |

142 | //! it for references to your type as shown. |

143 | //! |

144 | //! ``` |

145 | //! use float_cmp::ApproxEq; |

146 | //! |

147 | //! pub struct Vec2<F> { |

148 | //! pub x: F, |

149 | //! pub y: F, |

150 | //! } |

151 | //! |

152 | //! impl<'a, M: Copy + Default, F: Copy + ApproxEq<Margin=M>> ApproxEq for &'a Vec2<F> { |

153 | //! type Margin = M; |

154 | //! |

155 | //! fn approx_eq<T: Into<Self::Margin>>(self, other: Self, margin: T) -> bool { |

156 | //! let margin = margin.into(); |

157 | //! self.x.approx_eq(other.x, margin) |

158 | //! && self.y.approx_eq(other.y, margin) |

159 | //! } |

160 | //! } |

161 | //! ``` |

162 | //! |

163 | //! ## Non floating-point types |

164 | //! |

165 | //! `ApproxEq` can be implemented for non floating-point types as well, since `Margin` is |

166 | //! an associated type. |

167 | //! |

168 | //! The `efloat` crate implements (or soon will implement) `ApproxEq` for a compound type |

169 | //! that tracks floating point error bounds by checking if the error bounds overlap. |

170 | //! In that case `type Margin = ()`. |

171 | //! |

172 | //! ## Inspiration |

173 | //! |

174 | //! This crate was inspired by this Random ASCII blog post: |

175 | //! |

176 | //! [https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/) |

177 | |

178 | #![cfg_attr(not(feature = "std"), no_std)] |

179 | |

180 | #[macro_use] |

181 | mod macros; |

182 | |

183 | mod ulps; |

184 | pub use self::ulps::Ulps; |

185 | |

186 | mod ulps_eq; |

187 | pub use self::ulps_eq::ApproxEqUlps; |

188 | |

189 | mod eq; |

190 | pub use self::eq::{ApproxEq, F32Margin, F64Margin}; |

191 | |

192 | #[cfg(feature= "ratio")] |

193 | mod ratio; |

194 | #[cfg(feature= "ratio")] |

195 | pub use self::ratio::ApproxEqRatio; |

196 |