[personal profile] kpreid

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 compare NaN 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 the eql 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.

From: [identity profile] http://openid.aliz.es/Mathematically-floating-point-is-not (from livejournal.com)
I don't think this is a huge issue because mathematically floating point is not very consistent. You can't form a ring or field with any of its operations.

NaN and inf really bugger up the properties of floating point, as well as negative zero.
From: [identity profile] kpreid.livejournal.com
Not a ring or field, no, but what I'm looking at here is its properties as a set of values (floats) and the defined relations on that set.
From: [identity profile] http://openid.aliz.es/Mathematically-floating-point-is-not (from livejournal.com)
NaN and [+-]inf really throw a monkey wrench in the works then. Heck even without them division can cause the same problems.

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)
From: (Anonymous)
Since NaN does not represent a single specific number but a class of objects comparing NaN to NaN is like comparing {a,b,c} to {a,b,c}. Then, depending on which concrete object is represented by NaN_1 and NaN_2, the answer may be true or false. Strictly speaking, the Boolean answer should be "MayBe".

http://en.wikipedia.org/wiki/Trivalent_logic

Makes any sense to you?

Re: (NaN == NaN) is "MayBe"

Date: 2010-04-01 20:31 (UTC)
From: [identity profile] kpreid.livejournal.com
I know what NaN means. I'm saying that NaN ≠ NaN is not when you want in specific contexts For example, if you were memoizing: you want to remember that f(NaN) = NaN, so you need to be able to define (the generalized-to-all-particular-values-not-just-NaN version (in practice using a hashtable) of) f'(x) = if (x = NaN) then NaN else f(x), which can't be done with the NaN ≠ NaN definition.

(no subject)

Date: 2010-04-01 20:30 (UTC)
From: (Anonymous)
Nobody should be writing epsilon error comparisons unless they know the right error bounds for their computation.

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)
From: (Anonymous)
"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"

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)
From: [identity profile] ksleet.livejournal.com
I've seen real-world bugs caused by something similar to this. If the floating-point number is in a CPU register, it might not have the same precision as a floating-point number elsewhere. Boom, comparison fails.
From: (Anonymous)
Betrand Meyer was recently delving into floating arithmetic (presumably in the context of the Eiffel language) and wrote a small rant about NaN not being equal to NaN:

http://bertrandmeyer.com/2010/02/06/reflexivity-and-other-pillars-of-civilization/