3

This is a follow up question to What is the rationale for all comparisons returning false for IEEE754 NaN values? (I think this is better as another question than a comment). It has a very good answer which is missing exactly 1 important thing: why is NaN != NaN?

I understand that NaN is unordered compared to numbers and thus NaN < x and NaN > x are both always false. But that doesn't explain why equality is backwards. To quote the answer of the question I linked to:

it was necessary to provide programmers with a convenient and efficient means of detecting NaN values that didn't depend on programming languages providing something like isNan()

Ok then:

if((0/0) == x) print "I found NaN!";
//using 0/0 is more readable than using 0x7fc00000 or 0x7ff8000000000000

That's convenient, efficient, and doesn't rely on a programming language provided construct. Even if the programming language doesn't have a global NaN defined you can easily make a local (or global) variable yourself.

A single extra operation is certainly not inefficient, especially since the FPU has a hard-coded response (no calculation needed). Using 0/0 is also more programmer convenient than the illogical x!=x.

A program rarely needs to check for NaN and having a single extra operation per check is not going to be the reason a program is inefficient. Likewise there's no program so restrained that it can't handle a single extra variable, especially a temporary one.

To me the argument "I need NaN!=NaN so that I can detect it" doesn't make sense: it's exactly the same argument as "I need Infinity!=Infinity so that I can detect it". No you don't, just compare it to 1/0 like everyone else.

So my question is much more narrow than the original question: Why is NaN != NaN?

I can't tell if this has been asked before since there's plenty of duplicates of the original question.

Side note:

NaN == NaN isn't going to change now

while the FPU won't change now, some programming languages have already changed so that NaN == NaN is true.

Edit:

Let me try to clarify why I haven't accepted any answer so far. I have read the comments so far and I'm sorry I've dropped the ball: instead of explaining why I disagree I just asked "why" hoping you'd give a different reason. So let me try to explain my perspective better.

Infinity and NaN are similar in many ways.

  1. Both Infinity and NaN are concepts. Infinity isn't a number it is a concept of never ending. If x is Infinity then it represents that x is endless. NaN isn't a number (duh) and represents an invalid state or something not mathematically valid. For mathematic completeness: infinity is debatably a "number" however floating point numbers approximate real numbers (although they are closer to rational numbers) and infinity is definitively not in the real numbers. Therefore as far as the FPU and real number line are concerned infinity (all types) is not a number (as per the domain's definition of "number").
  2. Equality is not always a numeric equality. General equality is only numeric equality if both operands are numbers (not concepts) even if the FPU is asked to perform the comparison. General equality is used when one operand is Infinity or NaN in order to see if the other represents the same concept. For example x == Infinity doesn't use numeric equality (since Infinity isn't a number) instead it checks if x represents the concept of positive infinity. I would likewise expect exactly the same thing from x == NaN to return true if x is the concept of "not a number" but this isn't the case (as to why is the point of this question).
  3. Both can be retrieved by dividing by 0: 0/0 returns NaN and 1/0 returns Infinity. If your language doesn't have a constant for Infinity then x == (1/0) is the way to compare x to Infinity.
  4. Multiple flavors. There are infinitely many types of Infinity (because of cardinality). However the FPU doesn't make any distinctions: Infinity is a general purpose one and the only one available. With these constructs it isn't possible to ask if x is countable infinity for example. NaN has only 1 type conceptually ("not a number") however it has 2 ways it is handled: quiet and signaling. I know nothing about signaling but from my understanding if you tried to compare the equality of a signaling NaN it would throw, therefore since it doesn't have equality it isn't entirely on topic.
  5. Bit representation. NaN has many different bit representations but Infinity has only 1 (and 1 for -Infinity). However all representations of NaN are exactly the same value logically because they all represent the same concept of "not a number". There is no distinction to be made between 0x7FF8000000000000 and 0x7FF8000000000001: they mean exactly the same thing and the exact same math is allowed to return either result. You can't ask if x is the sqrt(-1) or if x is log(-1) because both of those will return exactly the same thing: NaN. Similar to how there is only 1 kind of Infinity, there is only 1 type of (quiet) NaN. Yes there are multiple bit representations but one is not greater than the others: the FPU uses special logic to treat them all exactly the same. This is likewise true for -0 which has a different bit than +0 but is treated exactly the same. Therefore the bits are an implementation detail unrelated to logical equality.
  6. (Off topic but they also both have special math: x-1 doesn't change the value of Infinity or NaN).

So yes I did hear you when you said "equality doesn't make sense because it isn't a number" but I disagree with that statement because if it was true then as per #1 it also wouldn't make sense to compare Infinity (which also isn't a number). Although as per #2 it does make sense to compare non-numbers.

I have in fact read every answer to the original question (and to Why are floating point infinities, unlike NaNs, equal? which has some related answers). All of the arguments for why NaN != NaN is true boil down to either "because it isn't a number" (already addressed) or "because there are many different bit representations" which is countered by #5.

I can't think of any logical basis for why NaN should have different equality. I'm not saying that I have accounted for everything: so what have I missed? Is one of my arguments wrong or is there some other reason I hadn't thought of? That's why I've been camping on the word "why". If you disagree then refute me or defend your point. I'm guessing there will be at least 1 counter-point to my above logic and I'm looking forward to hearing it.

Again I apologize for not including these in the original question since I have been thinking them all along. I'm also sorry that this may cause a big change to the existing answer.

Community
  • 1
  • 1
SkySpiral7
  • 362
  • 3
  • 8
  • You check whether x is NaN very easily by testing x ≠ x. And if you define a programming language where NaN = NaN (and presumably NaN ≥ NaN and NaN ≤ NaN), that would make a lot of floating point arithmetic quite slow. Any "=", "≥" or "≤" would have to be replaced by at least two comparisons and runtime, and three in code. – gnasher729 Jun 11 '16 at 17:12
  • 1
    There is not just one NaN. Since a NaN is not a number, it makes sense that you shouldn't be able to compare it to anything, not even to, well, another NaN. – Rudy Velthuis Jun 11 '16 at 17:34
  • If you want readability or immunity from aggressive optimization, you will be using compiler built-in like is isnan(). This may well be coded as a simple compare of the value to itself. – tim18 Jun 11 '16 at 18:33
  • 2
    @tim18: "Aggressive optimization" meaning broken compilers? You can't eliminate a test for `x != x` when `x` is floating-point unless you prove that `x` is not NaN. – tmyklebu Jun 11 '16 at 18:44
  • 1
    Anyway, 0/0 (int divide) won't produce NaN in many programming languages. – tim18 Jun 11 '16 at 19:17
  • Many compilers require an associative-math optimization setting, e.g. to enable vectorization of important sequences, which is coupled with aggressive pruning of expressions which might have generated NaN e.g. (x+y > z+y) => (x > z), not to mention the simple compile-time replacement of (x != x) by 0 rather than isnan(x). For example, gcc -ffast-math or icc default. Simply calling that broken won't change the situation. – tim18 Jun 11 '16 at 19:24
  • 1
    @tim18 I do not see your point. `isnan` will be broken too if you enable `-ffast-math`: https://godbolt.org/g/Jt1LMt – Pascal Cuoq Jun 11 '16 at 20:37
  • @tim18 0/0 works just fine in javascript. This question is language agnostic so the syntax for a language's floating point number is irrelevant. – SkySpiral7 Jun 11 '16 at 20:38
  • @gnasher729 and tmyklebu this is my point. If NaN==NaN there wouldn't be extra overhead for such languages that correct the logical flaw and the compile flag would work without exception. So my question is why IEEE put us through extra pain with no apparent gain. – SkySpiral7 Jun 11 '16 at 20:40
  • @SkySpiral7 because how programmers would explicitly test for NaN was not IEEE's concern as long as there was a concise way to do it either way. IEEE's concern was that it should be possible to express entire algorithms concisely and **not** have to test for NaN explicitly too often, or even at all, during a sequence of computations. The sophisticated system of (sticky) flags and exceptions reflect the same desire of expressing computations as elegant (and short) sequences of instructions. – Pascal Cuoq Jun 11 '16 at 20:45
  • @RudyVelthuis that doesn't make sense. Logically there are different kinds of Infinity (countable, cardinality etc) but `if(Infinity == x)` doesn't test which kind it is, only that it is one of the kinds. Likewise `if(NaN == x)` should (and does if backwards) test for any kind of "not a number". There are not and shouldn't be tests for if it is `sqrt(-2)` etc. Byte-wise this is the same statement: although there are different ways to represent Infinity and NaN there is no meaningful distinction between them: they are all considered to be the same value. – SkySpiral7 Jun 11 '16 at 20:45
  • @PascalCuoq it sounds like you said they didn't put much thought in either direction. Then why did they land on `NaN!=NaN`? If both ways were acceptable then why choose that over comparing to 0/0? – SkySpiral7 Jun 11 '16 at 20:48
  • 3
    @SkySpiral7 I did not say that at all. I said that ability to test if a number is NaN was an objective, which was satisfied by both conventions, but that having NaN != x for every floating-point value x allowed in addition to express many algorithms elegantly, which was an objective reflected through the entire standard. – Pascal Cuoq Jun 11 '16 at 20:50
  • I think my comment made lots of sense. You simply cannot compare a NaN with anything, not even with itself. The result NaN == NaN is by definition false. Note that several bit combinations represent NaN, so it is not even a single value, so a quick check of bit combinations is not possible either. – Rudy Velthuis Jun 12 '16 at 00:56
  • 1
    @SkySpiral7: It's a very valid question. I can't think of a case where I've cared that NaN != NaN. I suppose the existing semantics makes the `while (error > tol) iterate();` idiom still work when the programmer screws up `tol`. But that doesn't strike me as a very strong argument. – tmyklebu Jun 12 '16 at 02:17
  • @tim18: `-ffast-math` is designed to miscompile carefully-written floating-point code. Lots of FP code is poorly written (so you can't tell the difference between wrong code and right code) and there's a substantial body of very robust FP code, so `-ffast-math` is perfectly acceptable in a lot of situations. But it's still broken. – tmyklebu Jun 12 '16 at 02:27
  • @Pascal Cuoq `(0/0) == NaN` is also elegant and meets the objective of identification so I don't see your point... Unless you're saying that `NaN != NaN` is elegant in which case why? – SkySpiral7 Jun 13 '16 at 23:51
  • @Rudy Velthuis You say "NaN == NaN is by definition false". By IEEE's definition then yeah that's true. Or you could argue that point philosophically. Either way if you can say why that would be an answer to the question. – SkySpiral7 Jun 13 '16 at 23:52
  • Where's the "move to discussion" option? I saw it earlier... Also sorry but I'll only be able to check back on this question once or twice a day. So I wouldn't be surprised if the discussion dies out. – SkySpiral7 Jun 13 '16 at 23:52
  • @SkySpiral7 Did you even READ my comment? – Pascal Cuoq Jun 14 '16 at 07:07
  • NaN is not a number, it is an error value, so it can't be compared to anything, not even to itself, especially because there are a range of NaN values, so one bitwise representation of NaN is not even bitwise equal to another one. In oher words, you can not compare NaN with NaN and expect equality. That is why it is defined that way. You know the answers in the other question. Why don't you just read and accept what they say? – Rudy Velthuis Jun 14 '16 at 10:04

2 Answers2

2

There are several ways to produce NaN.

Imagine this:

double expr1 = 0.0 / 0.0;
double expr2 = Math.log(-1.0);
if (expr1 == expr2) {
  // They are the same
}

These two NaNs, if they had been representable as numbers, would have a high probability of being different values.

If NaN == NaN would hold, this snippet would have the unexpected result of comparing them as equal. Since most programs do the comparison in terms of what they expect (so they use ==, <=, but not !==, !<=), returning false doesn’t make these programs conclude wrong things.

Roland Illig
  • 40,703
  • 10
  • 88
  • 121
  • (assuming it was supposed to be expr1 = 0.0 / 0.0). They both yield NaN (ie are the same) so the expectation would be that `expr1 == expr2` returns true. (`Infinity == NaN` on the other hand would never make sense). – SkySpiral7 Jun 13 '16 at 23:53
  • Thanks for the note, I had left out one sentence in my written argument. Added it. – Roland Illig Jun 14 '16 at 06:11
  • 1
    @SkySpiral7: No they are **not** the same. There are several different bitwise representations of NaN and they are **not the same**, so even if you compare their bit representations, they are not (necessarily) the same. So `NaN == NaN` does not hold, not by definition, but also not by their bitwise representations. **There is not one single NaN value**. There is a range of bit combinations that are all considered NaN. – Rudy Velthuis Jun 14 '16 at 10:11
  • 1
    I find this argument unconvincing: `1.0 / 0.0 == Math.exp(1000.0)` being true would be equally unexpected, IMO. And yet it *is* true in normal IEEE 754 arithmetic. – Mark Dickinson Jun 15 '16 at 18:51
  • Sorry but I would expect them to be equal. See edit (specifically point #2). – SkySpiral7 Jun 20 '16 at 23:37
0

Consider A, B, and C are numbers. ? means undefined A/B = C; because C * B = A

in the case for B = 0 A/0 = ?, because ? * 0 = A
there is no number for ? which makes this true, unless A is 0 (see below) The limit of A/B as B goes to zero is infinity; A/0 is undefined, not infinity.

in the case of both A = 0 and B = 0 0/0 = ?, because ? * 0 = 0 ( all numbers for ? make this true)

my point is that there are a variety of mathematical calculations which have an undefined results. It is not appropriate for the comparison of an undefined result to have equality with another undefined result. Comparisons of undefined results should return undefined or false. The comparisons are testing the math and the numbers, not the concepts of infinity or NaN.

If the compiler or FPU you are using has a specific behavior for an undefined result, then adjust your code accordingly.

beyond the scope of my reply: not all infinities are equal

  • I've basically abandoned this question because it's "I know I'm right" rather than a real question. "Did the original people consider these points?" can only be answered by the original people. – SkySpiral7 Sep 25 '22 at 03:10
  • I don't see why multiple sources of undefined shouldn't be considered equal. It's a good call out on the distinction between numeric equality vs conceptual equality however it amounts to the same thing: if Infinity = Infinity then NaN = NaN. If = can be used to compare concepts then it should compare both concepts in the same way. NaN < 5 being undefined I agree with but false isn't inappropriate either. – SkySpiral7 Sep 25 '22 at 03:10
  • I'm aware that there's multiple infinities however the FPU is treating Infinity as a concept without cardinality (you can argue that but the point is that it only represents 1 kind of infinity). – SkySpiral7 Sep 25 '22 at 03:10