Crate float_cmp [−] [src]
float-cmp
WARNING: comparing floating point numbers is very tricky and situation dependent, and best avoided if at all possible. There is no panacea that "just works".
float-cmp defines traits for approximate comparison of floating point types which have fallen
away from exact equality due to the limited precision available within floating point
representations. Implementations of these traits are provided for f32
and f64
types.
The recommended go-to solution (although it may not be appropriate in all cases) is the
approx_eq()
function in the ApproxEq
trait. An epsilon test is performed first, which
handles very small numbers, zeroes, and differing signs of very small numbers, considering
them equal if the difference is less than the given epsilon (e.g. f32::EPSILON). For larger
numbers, floating point representations are spaced further apart, and in these cases the ulps
test comes to the rescue. Numbers are considered equal if the number of floating point
representations between them is below a specified bound (Ulps are a cardinal count of
floating point representations that separate two floating point numbers).
Several other traits are provided including Ulps
, ApproxEqUlps
, ApproxOrdUlps
, and
ApproxEqRatio
.
The problem
Floating point operations must round answers to the nearest representable number. Multiple operations may result in an answer different from what you expect. In the following example, the assert will fail, even though the printed output says "0.45 == 0.45":
let a = 0.15_f32 + 0.15_f32 + 0.15_f32; let b = 0.1_f32 + 0.1_f32 + 0.25_f32; println!("{} == {}", a, b); assert!(a==b) // Fails, because they are not exactly equal
This fails because the correct answer to most operations isn't exactly representable, and so your computer's processor chooses to represent the answer with the closest value it has available. This introduces error, and this error can accumulate as multiple operations are performed.
The solution
With ApproxEq
, we can get the answer we intend:
let a = 0.15_f32 + 0.15_f32 + 0.15_f32; let b = 0.1_f32 + 0.1_f32 + 0.25_f32; println!("{} == {}", a, b); assert!(a.approx_eq(&b, 2.0 * ::std::f32::EPSILON, 2)) // They are equal, within 2 ulps
For most cases, I recommend you use a smallish integer for the ulps
parameter (1 to 5
or so), and a similar small multiple of the floating point's EPSILON constant (1.0 to 5.0
or so), but there are plenty of cases where this is insufficient.
Some explanation
We use the term ULP (units of least precision, or units in the last place) to mean the difference between two adjacent floating point representations (adjacent meaning that there is no floating point number between them). This term is borrowed from prior work (personally I would have chosen "quanta"). The size of an ULP (measured as a float) varies depending on the exponents of the floating point numbers in question. That is a good thing, because as numbers fall away from equality due to the imprecise nature of their representation, they fall away in ULPs terms, not in absolute terms. Pure epsilon-based comparisons are absolute and thus don't map well to the nature of the additive error issue. They work fine for many ranges of numbers, but not for others (consider comparing -0.0000000028 to +0.00000097).
Implementing these traits
You can implement ApproxEq
for your own complex types. The trait and type parameter
notation can be a bit tricky, especially if your type is type parameterized around
floating point types. So here is an example (you'll probably not specify the Copy trait
directly, but use some other NumTraits type floating point trait):
use float_cmp::{Ulps, ApproxEq}; pub struct Vec2<F> { pub x: F, pub y: F, } impl<F: Ulps + ApproxEq<Flt=F> + Copy> ApproxEq for Vec2<F> { type Flt = F; fn approx_eq(&self, other: &Self, epsilon: <F as ApproxEq>::Flt, ulps: <<F as ApproxEq>::Flt as Ulps>::U) -> bool { self.x.approx_eq(&other.x, epsilon, ulps) && self.y.approx_eq(&other.y, epsilon, ulps) } }
Inspiration
This crate was inspired by this Random ASCII blog post:
https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
Traits
ApproxEq |
ApproxEq is a trait for approximate equality comparisons. The associated type Flt is a floating point type which implements Ulps, and is required so that this trait can be implemented for compound types (e.g. vectors),/ not just for the floats themselves. |
ApproxEqRatio |
ApproxEqRatio is a trait for approximate equality comparisons bounding the ratio of the difference to the larger. |
ApproxEqUlps |
ApproxEqUlps is a trait for approximate equality comparisons. The associated type Flt is a floating point type which implements Ulps, and is required so that this trait can be implemented for compound types (e.g. vectors), not just for the floats themselves. |
ApproxOrdUlps |
ApproxOrdUlps is for sorting floating point values where approximate equality is considered equal. |
Ulps |
A trait for floating point numbers which computes the number of representable values or ULPs (Units of Least Precision) that separate the two given values. |