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