Why does isnan(x) exist if x != x gives the same result?
Asked Answered
P

2

9

It is well known that for any variable of floating-point type x != x iff (if and only if) x is NaN (not-a-number). Or inverse version: x == x iff x is not NaN. Then why did WG14 decide to define isnan(x) (math.h) if the same result can be obtained using the natural capabilities of the language? What was the motivation? Better code readability?

Extra question: Are there any functional differences between isnan(x) and x != x?

Pachston answered 29/1, 2021 at 21:17 Comment(8)
I don't know what the committee's reasons were, but to me, isnan() is clean and self-documenting, while x != x, no matter how well-defined it may be per IEEE-754, looks like a kludge that might happen to work today but not tomorrow.Sombrero
Think about how x != x has to be implemented for floating point numbers - the memory can't be compared directly because that would ignore NaN values, so there has to be an additional check done on the values beforehand...Devland
@AndrewHenle The memory can't be compared... what do you mean?Wanyen
@JohnKugelman The comparison can't be implemented as a simple bitwise comparison - there has to be a check that's "is this value NaN?" That check is isnan(). isnan() functionality is a prerequisite for defining x != x if x is NaN.Devland
Is floating point equality not a standalone processor instruction? Do compilers need multiple instructions to implement !=?Wanyen
@AndrewHenle: x == 0 also cannot be implemented as a simple bitwise operation (or it would fail to recognize that −0 equals zero), but we do not have an iszero macro. There is a floating-point comparison instruction that produces the desired result. None of which is relevant to == versus isnan, as either may be implemented with one instruction or many as required by the hardware.Marcello
Performance.Influential
@EricPostpischil but we do not have an iszero macro Well, we actually do. It just has a different name. Because we have fpclassify( value ) == FP_ZERODevland
L
17

if the same result can be obtained using natural capabilities of the language?

C does not specify x == x iff x is not NaN. Many implementations do that though. C does not require adherence to IEEE_754. isnan(x) is well defined.

Use isnan(x) for portable code.


C in Representations of types (since C99) has

Two values (other than NaNs) with the same object representation compare equal, but values that compare equal may have different object representations.

... but that does not specify the behavior of comparing 2 NANs.


When __STDC_IEC_559__ (akin to adherence to IEEE-754) is defined as 1 (something not required by C), then

"The expression x != x is true if x is a NaN."

"The expression x == x is false if x is a NaN."

When __STDC_IEC_559__ is not defined as 1, exercise caution about assuming behavior in the edges of floating point math such as NAN equality.


[Edit to address some comments]

C, on the corners of FP math, lack the specifics of IEEE-754. C89 allowed NANs as evidenced by references to IEEE-754, yet lacked isnan(x). There was no "Two values (other than NaNs) with the same object representation compare equal, ..." to guide either. At that time, x==x for NAN was not specified. With C99, rather than break or invalidate prior code, isnan(x) is defined as a clear NAN test. As I see it, x==x remains unspecified for NANs, yet it is commonly results in false. isnan(x) also provides code clarity. Much about C and NAN is fuzzy: round tripping payload sequences, signaling encoding/discernment, NAN availability, ...


Are there any functional differences between isnan(x) and x != x?

In addition to the well defined functionality of isnan(x) versus x != x discussed above, some obscure ones:

  • isnan(x) evaluates x once versus twice for x != x. Makes a difference if x was some expression like y++.

  • isnan(x) operates on the sematic type. This makes a difference when "the implementation supports NaNs in the evaluation type but not in the semantic type.". x != x operates on the evaluation type. Research FLT_EVAL_METHOD for more detail.

Leucippus answered 29/1, 2021 at 21:22 Comment(14)
cppreference.com says that x != x is another way to test for NaN. Are they wrong?Wanyen
@JohnKugelman I see nothing in the C spec to support that claim even if it is common practice.Leucippus
but that does not specify the behavior of comparing 2 NANs I don't read it that way. To me, this says that NaN values with the same object representation are not equal. Admittedly it's awfully unclear if NaN are excluded from "but values that compare equal may have different object representations", but since doing that would make the NaN exclusion from the first part completely useless, I conclude it has to mean "x == x is false if x is NaN". To me, though, the entire statement definitely could be worded better.Devland
@AndrewHenle Excluding something from a guarantee just excludes it. It does not guarantee the opposite, or even something else.Wallack
@Wallack If two objects of a particular type with the same representation are excluded from the set of being compared equal, they're in the set of being compared unequal. Things are either equal or they're not. Excluding them from one side of that divide guarantees that they must be on the other. The question is what does that statement in the C standard mean.Devland
@AndrewHenle That's where you go wrong. They are only excluded from the set guaranteed to compare equal.Wallack
@Wallack The exact phrase is "Two values (other than NaNs) with the same object representation compare equal". So NaN of the same object representation do not compare equal. So tell me, given that, what do two NaNs that have the same representation compare? Remember, objects with the same representation compare equal unless they are NaNs.Devland
@AndrewHenle The sentence is about two values with the same object representations, excluding those that are NaN. Not following it with a sentence about those excluded values might confuse those assuming everything to be rigidly defined, without room to accommodate different implementations. A mind-set somewhat alien to C.Wallack
@Wallack Again - tell me, per the C standard, do two NaN values with the same object representation compare equal or not? That's a real simple question. The second part about objects with different representations does not apply - the object representations of these two NaN values are identical. Do they compare equal or not?Devland
@AndrewHenle Guaranteed if IEEE-conforming floating-point is advertised. Otherwise, maybe, the standard doesn't say.Wallack
@Wallack You're going to have to explain how being excluded from the set of two objects that compare equal ever allows those two objects to compare equal.Devland
@AndrewHenle Being excluded the set of objects whose behavior is defined gives no guarantees. Even if that might feel unsatisfying.Wallack
@Wallack I just don't see it that way, but thanks for that final explanation. I now understand what you're saying, but I'm still of the opinion being excluded from that set precludes that behavior.. I did say up front the statement could be worded better. This is probably a good candidate for a language-lawyer question.Devland
@AndrewHenle Answer appended to address some of these issues.Leucippus
D
3

Why isnan(x) exists if x != x (or x == x) gives the same result?

Because they don't always give the same result.

For example, GCC when compiling with -funsafe-math-optimizations replaces

x - x

with

0.0

so ( x == x ) can be true even if x is NaN.

-funsafe-math-optimizations is also enabled if either -fast-math or -Ofast is specified:

...

-ffast-math

Sets the options -fno-math-errno, -funsafe-math-optimizations, -ffinite-math-only, -fno-rounding-math, -fno-signaling-nans, -fcx-limited-range and -fexcess-precision=fast.

This option causes the preprocessor macro __FAST_MATH__ to be defined.

This option is not turned on by any -O option besides -Ofast since it can result in incorrect output for programs that depend on an exact implementation of IEEE or ISO rules/specifications for math functions. It may, however, yield faster code for programs that do not require the guarantees of these specifications.

...

-funsafe-math-optimizations

Allow optimizations for floating-point arithmetic that (a) assume that arguments and results are valid and (b) may violate IEEE or ANSI standards. When used at link time, it may include libraries or startup files that change the default FPU control word or other similar optimizations.

This option is not turned on by any -O option since it can result in incorrect output for programs that depend on an exact implementation of IEEE or ISO rules/specifications for math functions. It may, however, yield faster code for programs that do not require the guarantees of these specifications. Enables -fno-signed-zeros, -fno-trapping-math, -fassociative-math and -freciprocal-math.

...

So there are cases where you might want to use floating point optimizations for performance reasons but still need to check for NaN, and the only way to do that is to explicitly check with something like isnan().

Also, the C standard states in 6.2.6.1p4:

Two values (other than NaNs) with the same object representation compare equal

The functionality necessary to implement that requires the ability to check for NaN in some way separate from comparing the object representation (bits). So isnan() functionality is a prerequisite for implementing "x == x is false if x is NaN".

There has to be some type of functionality to check for NaN independent of x == x else it's just an infinitely recursive definition that can't be implemented.

If all you need to do is check for NaN and don't need to waste CPU cycles actually doing the comparison, exposing that functionality in something like isnan() can be a performance benefit.

Devland answered 29/1, 2021 at 23:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.