almost/lib.rs
1//! A crate to test if floats are almost equal.
2//!
3//! ```
4//! # let (x, y, z) = (0.5, 1.0, 2.0);
5//! // Compare two variables.
6//! if almost::equal(x, y) {
7//! println!("They're almost equal!");
8//! }
9//! // Or, if you need need to compare with a constant zero:
10//! if almost::zero(z) {
11//! println!("It's almost zero!");
12//! }
13//! ```
14//!
15//! # Why another crate?
16//!
17//! There are a lot of crates for doing this already.
18//!
19//! The author crate has fairly strong opinions on how this should be done, and
20//! thinks most of the similar crates in the wild make dubious choices, make it
21//! easy for the user to misuse, or follow poor numerical robustness practices.
22//!
23//! Specific differences / benefits compared to other crates:
24//!
25//! 1. Better choice of default tolerances for unknown inputs. Often the value
26//! of the `EPSILON` is used as a value for tolerance, or its use is
27//! encouraged by the API).
28//!
29//! This is the wrong choice more often than it is right. The machine epsilon
30//! is a quite strict bound for comparison, and after just a few arithmetic
31//! operations you will no longer be within it.
32//!
33//! This library chooses a default tolerance value that is much more
34//! forgiving while still tight enough for it to be unlikely to cause false
35//! positives (specifically, it assumes roughly half of the bits have been
36//! lost to rounding, e.g. the *square root* of the machine epsilon).
37//!
38//! 2. Relative comparison by default. Most of the crates in the wild seem to
39//! use a hybrid between relative and absolute comparison. This is bad for
40//! arbitrary numbers which may have any scale, and gives up a number of
41//! desirable properties of the floating point number system.
42//!
43//! 3. Absolute comparison with zero. The only downside to using relative
44//! comparison by default is that it is essentially never useful to use
45//! relative comparison where one of the values is known in advance to be
46//! zero.
47//!
48//! As a result, this library provides `almost::zero(v)` as well, which uses
49//! absolute comparison.
50//!
51//! 4. Properly handling both overflow and underflow.
52//!
53//! Because this library uses relative comparison, denormal numbers behave
54//! properly, as well as comparisons where one of the values has overflowed
55//! to infinity. The second might sound impossible, but we can just rescale
56//! both values, and compare with the same tolerance.
57//!
58//! 5. Simple API. We don't expose other ways of comparing numbers, most of
59//! which are either dubious choices for non-niche use cases.
60//!
61//! That said, there's no one size fits all here. Numerical robustness is full
62//! of tradeoffs, and while I believe the ones made by this library are good for
63//! most cases, they do not and cannot satisfy every possible case.
64#![no_std]
65
66pub(crate) mod imp;
67
68/// Returns `true` if `lhs` and `rhs` are almost equal.
69///
70/// ```
71/// assert!(almost::equal(0.1 + 0.2, 0.3));
72/// ```
73///
74/// Do not use this to compare a value with a constant zero. Instead, for this
75/// you should use [`almost::zero`](zero).
76///
77/// This uses a relative comparison with a threshold chosen to be good for
78/// values produced by arbitrary computation, while still tight enough for it to
79/// be unlikely to cause false positives in practice This is a good default, but
80/// you have a tighter bound you need to use,
81/// [`almost::equal_with`](equal_with) is also available.
82///
83/// Note that this returns false in the case that both values are NaN.
84#[inline]
85pub fn equal<T: AlmostEqual>(lhs: T, rhs: T) -> bool {
86 lhs.almost_equals(rhs)
87}
88
89/// Returns `true` if `a` is almost zero.
90///
91/// ```
92/// # use core as std;
93/// assert!(almost::zero(std::f32::EPSILON));
94/// ```
95///
96/// This is the correct function to use when comparing to see if a value is
97/// almost zero.
98///
99/// Testing if a value is zero is a unique case where a relative comparison
100/// becomes almost useless, and an absolute comparison becomes the obviously
101/// correct choice.
102///
103/// As such, this performs comparison with a *absolute* threshold. The threshold
104/// used assumes half of the bits have been lost to rounding, which is a good
105/// default for user code that does not keep track of this, while still tight
106/// enough for it to be unlikely to cause false positives in practice. However,
107/// if you need a tighter bound, the function
108/// [`almost::zero_with`](zero_with) can be used.
109#[inline]
110pub fn zero<T: AlmostEqual>(a: T) -> bool {
111 a.almost_zero()
112}
113
114/// Returns `true` if `a` is almost zero, using the specified absolute
115/// tolerance.
116///
117/// ```
118/// # use core as std;
119/// assert!(!almost::zero_with(std::f32::EPSILON, std::f32::EPSILON));
120/// ```
121///
122/// This is a version of [`almost::zero`](zero) which does not define a
123/// tolerance value for you.
124///
125/// The correct choice of tolerance value is tricky and differs depending on:
126///
127/// - The type of numerical operations you're doing.
128/// - The scale of the values used.
129/// - The number of operations performed.
130///
131/// However, for comparison with zero it is possible to give broad guidelines
132/// (note that these do *not* apply to
133/// [`almost::equal_with`](equal_with), which for which the
134/// correct decision is more challenging).
135///
136/// # Panics
137/// This function panics in debug mode if `tolerance` is not greater than zero,
138/// as the results are unlikely to be sensible.
139///
140/// In release builds it should never panic.
141#[inline]
142pub fn zero_with<T: AlmostEqual>(v: T, tolerance: T::Float) -> bool{
143 v.almost_zero_with(tolerance)
144}
145
146/// Returns `true` if `lhs` and `rhs` are almost equal using the provided
147/// relative tolerance.
148///
149/// ```
150/// const MY_TOLERANCE: f32 = almost::F32_TOLERANCE / 2.0;
151/// assert!(almost::equal_with(0.1 + 0.2, 0.3f32, MY_TOLERANCE));
152/// ```
153///
154/// Do not use this to compare a value with a constant zero. Instead, for this
155/// you should use [`almost::zero_with`](zero_with).
156///
157/// # Panics
158/// This function panics in debug mode if `tolerance` is less than `T::EPSILON`
159/// or greater than 1.0, as the results are unlikely to be sensible.
160///
161/// In release builds it should never panic.
162#[inline]
163pub fn equal_with<T: AlmostEqual>(lhs: T, rhs: T, tolerance: T::Float) -> bool {
164 lhs.almost_equals_with(rhs, tolerance)
165}
166
167/// A trait for comparing floating point numbers. Not broadly intended to be
168/// used by most code (instead, use the functions at the crate root), however it
169/// could be useful for generic code too.
170pub trait AlmostEqual {
171 /// The floating point type. For f32 and f64 this is Self, but for custom
172 /// aggregate types it could be different.
173 type Float;
174
175 /// The default tolerance value for this type. Typically equivalent to
176 /// `T::EPSILON.sqrt()`, as we assume that around half of the precision bits
177 /// of any arbitrary computation have been rounded away.
178 const DEFAULT_TOLERANCE: Self::Float;
179
180 /// The machine epsilon for this type. This generally should not be used as
181 /// a tolerance value (it's frequently too strict), however it can be useful
182 /// when computing tolerances.
183 const MACHINE_EPSILON: Self::Float;
184
185 /// Equivalent to [`almost::zero`](zero).
186 /// ```
187 /// # let v = 0.000001f32;
188 /// # use almost::AlmostEqual;
189 /// assert!(v.almost_zero());
190 /// ```
191 #[inline]
192 fn almost_zero(self) -> bool where Self: Sized {
193 self.almost_zero_with(Self::DEFAULT_TOLERANCE)
194 }
195
196 /// Equivalent to [`almost::equal`](equal).
197 /// ```
198 /// # let (a, b) = (0.5, 0.5);
199 /// # use almost::AlmostEqual;
200 /// assert!(a.almost_equals(b));
201 /// ```
202 #[inline]
203 fn almost_equals(self, rhs: Self) -> bool where Self: Sized {
204 self.almost_equals_with(rhs, Self::DEFAULT_TOLERANCE)
205 }
206
207 /// Equivalent to [`almost::equal_with`](equal_with).
208 /// ```
209 /// # let (a, b) = (0.5f32, 0.5);
210 /// # use almost::AlmostEqual;
211 /// const MY_TOLERANCE: f32 = almost::F32_TOLERANCE / 2.0;
212 /// assert!(a.almost_equals_with(b, MY_TOLERANCE));
213 /// ```
214 fn almost_equals_with(self, rhs: Self, tol: Self::Float) -> bool;
215
216 /// Equivalent to [`almost::zero_with`](zero_with).
217 /// ```
218 /// # use almost::AlmostEqual;
219 /// assert!(0.01.almost_zero_with(0.05));
220 /// ```
221 fn almost_zero_with(self, tol: Self::Float) -> bool;
222}
223
224/// The default tolerance used for `f64`. Equivalent to `f64::EPSILON.sqrt()`
225/// (or `0.000000014901161193847656_f64`), as we assume that around half of the
226/// precision bits of any arbitrary value have been rounded away.
227pub const F64_TOLERANCE: f64 = 0.000000014901161193847656_f64;
228/// The default tolerance used for `f32`. Equivalent to `f32::EPSILON.sqrt()`
229/// (or `0.00034526698_f32`), as we assume that around half of the precision
230/// bits of any arbitrary value have been rounded away.
231pub const F32_TOLERANCE: f32 = 0.00034526698_f32;
232
233impl AlmostEqual for f64 {
234 type Float = f64;
235
236 const MACHINE_EPSILON: Self::Float = core::f64::EPSILON;
237
238 const DEFAULT_TOLERANCE: Self::Float = F64_TOLERANCE;
239
240 fn almost_equals_with(self, rhs: Self, tol: Self::Float) -> bool {
241 debug_assert!(tol < 1.0, "Tolerance should not be greater than 1.0");
242 debug_assert!(tol >= Self::MACHINE_EPSILON, "Tolerance should not be smaller than the machine epsilon");
243 crate::imp::f64::eq_with_tol_impl(self, rhs, tol)
244 }
245
246 fn almost_zero_with(self, tol: Self::Float) -> bool {
247 debug_assert!(tol > 0.0);
248 crate::imp::f64::abs(self) < tol
249 }
250}
251
252
253impl AlmostEqual for f32 {
254 type Float = f32;
255
256 const MACHINE_EPSILON: Self::Float = core::f32::EPSILON;
257
258 const DEFAULT_TOLERANCE: Self::Float = F32_TOLERANCE;
259
260 fn almost_equals_with(self, rhs: Self, tol: Self::Float) -> bool {
261 debug_assert!(tol < 1.0, "Tolerance should not be greater than 1.0");
262 debug_assert!(tol >= Self::MACHINE_EPSILON, "Tolerance should not be smaller than the machine epsilon");
263 crate::imp::f32::eq_with_tol_impl(self, rhs, tol)
264 }
265
266 fn almost_zero_with(self, tol: Self::Float) -> bool {
267 debug_assert!(tol > 0.0);
268 crate::imp::f32::abs(self) < tol
269 }
270}
271