The three ways to compare floating-point numbers
Thursday, April 1st, 2010 09:29![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
One bit of advice sometimes given to the novice programmer is don't ever compare floating-point numbers for equality, the reason being that floating-point calculations are inexact, and one should use a small epsilon, allowable error, instead, e.g. if (abs(value - 1.0) < 0.0001)
.
This advice is actually wrong, or rather, overly strong. There is a situation in which it is 100% valid to compare floats, and that is an cache or anything else which is comparing a float with, not a specific constant (in which case the epsilon notion is appropriate), but rather a previous value from the same source; floating-point numbers may be approximations of exact arithmetic, but that doesn't mean you won't get the same result from the same inputs.
So, don't get any bright ideas about outlawing aFloat == anotherFloat
.
Unfortunately, there's a case in which the common equality on floats isn't what you want for previous-value comparison anyway: for most definitions of ==
, NaN ≠≠ NaN
. This definition makes sense for numerics (and is conformant to IEEE floating point specifications), because NaN
is “not a number”; it's an error marker, provided as an alternative to exceptions (or rather, floating point error signals/traps/whateveryoucallit) which propagates to the end of your calculation rather than aborting it and requiring immediate error handling, which can be advantageous in both code simplicity and efficiency. So if you think about calculating within the space of “numbers”, then NaN is outside of that. But if you're working in the space of “results of calculations”, then you probably want to see NaN == NaN
, but that may not be what you get.
Mathematically, the floating-point comparison is not an equivalence relation, because it is not reflexive on NaN
.
(It's also typically the case that 0 == -0
, even though positive and negative zero are distinct values. Oh, and NaNs carry data, but I'm not talking about that.)
What to do about it, in a few languages:
- JavaScript
Even the
===
operator does not compare identities rather than numeric values, so if you want to compareNaN
you have to do it as a special case. Google Caja handles it this way:/** * Are x and y not observably distinguishable? */ function identical(x, y) { if (x === y) { // 0 === -0, but they are not identical return x !== 0 || 1/x === 1/y; } else { // NaN !== NaN, but they are identical. // NaNs are the only non-reflexive value, i.e., if x !== x, // then x is a NaN. return x !== x && y !== y; } }
- Common Lisp
The
=
operator generally follows the IEEE comparison (if the implementation has NaN at all) and theeql
operator does the identical-object comparison.- E
-
The
==
operator is guaranteed to be reflexive, and return false for distinguishable objects, so it is appropriate for the “cache-like” use cases, and the<=>
operator does conventional!(NaN <=> NaN), 0.0 <=> -0.0
floating-point comparison.
Mathematically floating point isn't much of anything
Date: 2010-04-01 14:40 (UTC)NaN and inf really bugger up the properties of floating point, as well as negative zero.
Re: Mathematically floating point isn't much of anything
Date: 2010-04-01 15:17 (UTC)Re: Mathematically floating point isn't much of anything
Date: 2010-04-01 15:54 (UTC)Not much is closed with floating point. It is annoying how static typing won't help you here unless you define a wrapper type which outright bans NaNs and Infs. You might get along with just banning NaN.
*sigh*
(NaN == NaN) is "MayBe"
Date: 2010-04-01 19:54 (UTC)http://en.wikipedia.org/wiki/Trivalent_logic
Makes any sense to you?
Re: (NaN == NaN) is "MayBe"
Date: 2010-04-01 20:31 (UTC)(no subject)
Date: 2010-04-01 20:30 (UTC)Floating-point numbers don't magically turn fuzzy as soon as they are instantiated; rounding error results from computation and is not innate to a number being a floating-point number.
(no subject)
Date: 2010-04-04 18:44 (UTC)True ... mostly! The one catch is that many processors will internally compute floating-point numbers to a higher accuracy than required (like 80 bits rather than 64). So depending on your code and compiler, an immediate floating-point number (in the FPU) might not compare equal to itself if one side of the test has been stored in a 64-bit storage location.
(no subject)
Date: 2010-05-01 17:22 (UTC)'because NaN is “not a number”; it's an error marker'
Date: 2010-04-06 22:06 (UTC)http://bertrandmeyer.com/2010/02/06/reflexivity-and-other-pillars-of-civilization/