Reputation: 98
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
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 theUCOMISD
instruction in that it signals a SIMD floating-point invalid operation exception (#I) when a source operand is either a QNaN or SNaN. TheUCOMISD
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