Spacechild1
Spacechild1

Reputation: 98

Floating point comparison with quiet_NaN on GCC with "-ffast-math" always yields true(!)

Under certain circumstances, comparing a normal floating point number with std::numeric_limits<float>::quiet_NaN() always yields "true" when compiled with GCC + "-ffast-math" (tested on Linux and MinGW). On other circumstances it always yields "false" (which would be the IEEE compliant behavior).

If one of the values is known to be a quiet_NaN at compile time, then the behavior is IEEE compliant and branches get optimized away.

I know that "-ffast-math" allows to break IEEE rules and that it implies "-ffinite-math-only" which assumes that there are no NaNs. But even with "-ffast-math" the result of quiet_NaN() has a specific bit pattern (e.g. 7FF80000), so how can a comparison with a normal float like 0.5f possibly yield true?

Here's a code sample showing the different cases. Please compile with "g++ -O3 -ffast-math".

#include <iostream>
#include <limits>

int main() {
#if 1
    // make sure that the value of 'x' is not known at compile time
    bool isnan;
    std::cout << "use nan (0|1): ";
    std::cin >> isnan;
#else
    // otherwise GCC correctly applies IEEE rules for NaN and the branch below is optimized away accordingly
    bool isnan = true;
#endif
    float x = isnan ? std::numeric_limits<float>::quiet_NaN() : 0.5f;
    std::cout << "x: " << x << std::endl;
    float a;
    std::cout << "type a float: ";
    std::cin >> a;
#if 1
    // *always* prints 1 (!)
    std::cout << a << " equal to " << x << ": " << (x == a) << std::endl;
#else
    // always prints false - the opposite from above!
    std::cout << a << " equal to " << x << ": " << ((x == a) ? "true" : "false") << std::endl;
#endif
    return 0;
}

also, here's a godbolt link. I guess the relevant part is at line 66 (with "-ffast-math") resp. 67 (without "-ffast-math"). Can someone explain to me the difference between those instructions?

Is this behavior of GCC acceptable or should I file a bug report?

EDIT: I want to make clear that I don't need to know whether a particular number is a NaN (I know that this is unspecified with "-ffast-math"), I'm only interested whether two numbers are (not) equal. In my actual code, I have a cache of floating point values and I perform update operations only if the input is different than the cached value. I've initialized the cache with quite NaNs, so they won't compare equal to any normal float and the first input is guaranteed to cause an update. This worked fine but as soon as I've added "-ffast-math", the check for newval != oldval always returned false, so there would never be an update. I've seen this quiet_NaN pattern in the SuperCollider source code and found it quite elegant.

Upvotes: 4

Views: 1349

Answers (1)

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136306

gcc documentation says:

-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.

In addition, -ffinite-math-only (included in -ffast-math) causes std::isnan, std::isinf, as well as x != x, to always return false, std::isfinite to always return true.


https://www.felixcloutier.com/x86/ucomisd

The COMISD instruction differs from the UCOMISD instruction in that it signals a SIMD floating-point invalid operation exception (#I) when a source operand is either a QNaN or SNaN. The UCOMISD instruction signals an invalid numeric exception only if a source operand is an SNaN.

-ffinite-math-only causes the compiler to emit COMISD instead of UCOMISD and omit checking the result of the comparison for NaN

The generated code is:

comiss  xmm2, DWORD PTR [rsp+28]
sete    sil

Comparing with NaN sets ZF, PF, CF flags, causing sete sil to produce true value when comparing with NaN, but no NaN is expected with -ffinite-math-only, so this is how undefined behaviour manifests itself here.

When NaN is expected the emitted assembly checks PF flag which is set when at least one operand is NaN:

ucomiss xmm1, DWORD PTR [rsp+28]
mov     eax, 0
setnp   sil # <---- condition on NaN
cmovne  esi, eax

With -ffast-math you may like to use these: Checking if a double (or float) is NaN in C++

Upvotes: 6

Related Questions