IEEE754 requires NaNs to be unordered; less than, greater than, equal etc. should all return false when one or both operands are NaN.
The sample below yields the correct F F F F F T
as expected when compiled using g++ at all optimisation levels, and when compiled using VC++'s CL.exe (32-bit version 15.00.30729.01) with no optimisation arguments or any combination of /Od, /fp:fast, /arch:SSE.
However, when compiled with /O1 or /O2 (and any/no other optimisation arguments), T T F F F T
results, even with /Op also specified.
The 64-bit version of CL.exe yields many variations - T T F F F T
, T T T F F T
, T T T F F F
etc - depending on optimisation level and whether /fp:fast is specified, but as with the 32-bit version, compliant behaviour only seems to be possible with all optimisation disabled.
Am I making some obvious mistake? Is there some way to cause the compiler to comply with standards here without sacrificing all other optimisation?
#include <limits>
#include <stdio.h>
int main( int argc, char const ** argv )
{
float test = std::numeric_limits<float>::quiet_NaN();
printf( "%c %c %c %c %c %c\n",
(test < test) ? 'T' : 'F',
(test <= test) ? 'T' : 'F',
(test == test) ? 'T' : 'F',
(test > test) ? 'T' : 'F',
(test >= test) ? 'T' : 'F',
(test != test) ? 'T' : 'F'
);
return 0;
}
An example build.cmd
that reproduces the problem:
set "PATH=c:\program files (x86)\Microsoft Visual Studio 9.0\VC\bin\amd64;c:\program files (x86)\Microsoft Visual Studio 9.0\Common7;c:\program files (x86)\Microsoft Visual Studio 9.0\Common7\IDE"
set "LIB=c:\program files (x86)\microsoft visual studio 9.0\vc\lib\x64;c:\program files\Microsoft SDKs\Windows\v6.0A\Lib\x64"
cl test.cpp /fp:fast /Od /c /I "c:\program files (x86)\microsoft visual studio 9.0\vc\include"
link "/LIBPATH:C:/Program Files (x86)/Microsoft Visual Studio 9.0/vc/lib/amd64" "/LIBPATH:C:\Program Files\Microsoft SDKs\Windows\v6.0A\/Lib/x64" /DEBUG /IGNORE:4199 /IGNORE:4221 /MACHINE:X64 /SUBSYSTEM:CONSOLE test.obj
test
EDIT
For the record, the example originally given in the question used
inline float QNaN()
{
static int const QNaNValue = 0x7fc00000;
return *(reinterpret_cast<float const*>(&QNaNValue));
}
to generate a NaN; as a number of comments and answers point out, this is undefined behaviour, and replacing it with std::numeric_limits::quiet_NaN() actually fixed the issue for some versions of the 32-bit CL.exe
So, to summarise, there were a number of separate issues:
the original example code yielded undefined behaviour by violating strict aliasing. Fixing this was sufficient to resolve the problem for some versions of the 32-bit compiler.
with that issue fixed, removing /fp:fast
resolved the problem for all versions of both the 32-bit and 64-bit compilers available to me
Martinho mentions that the problem no longer exists in cl 16.0 even with /fp:fast
That's because the QNaN function invokes UB by violating strict aliasing. The VS compiler is well within its rights to produce any behaviour.
You're invoking undefined behaviour by casting an int*
to float*
. I've tried your code with VS 2010, using std::numeric_limits<float>::quiet_NaN()
instead of the cast, and it gave the expected result (all but the last one were false
) with /O2
and /fp:fast
.
UPDATE
I've copy-pasted your revised example into both VS 2010 and VS 2005. In both of these, the 32-bit compiler produces correct results (F F F F F T
), while the 64-bit compiler does not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With