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?

8
  • 16
    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. Commented Jan 29, 2021 at 21:20
  • 5
    @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. Commented Jan 29, 2021 at 21:22
  • 1
    Is floating point equality not a standalone processor instruction? Do compilers need multiple instructions to implement !=? Commented Jan 29, 2021 at 21:27
  • 5
    @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. Commented Jan 29, 2021 at 21:33
  • 1
    Performance. Commented Jan 29, 2021 at 21:53

2 Answers 2

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.

14
  • 1
    cppreference.com says that x != x is another way to test for NaN. Are they wrong? Commented Jan 29, 2021 at 21:30
  • 2
    @AndrewHenle Excluding something from a guarantee just excludes it. It does not guarantee the opposite, or even something else. Commented Jan 30, 2021 at 0:10
  • 2
    @AndrewHenle That's where you go wrong. They are only excluded from the set guaranteed to compare equal. Commented Jan 30, 2021 at 1:20
  • 2
    @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. Commented Jan 30, 2021 at 1:27
  • 3
    @AndrewHenle Being excluded the set of objects whose behavior is defined gives no guarantees. Even if that might feel unsatisfying. Commented Jan 30, 2021 at 1:46
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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.