Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VC++ optimisations break comparisons with NaN?

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

like image 201
moonshadow Avatar asked Apr 03 '13 11:04

moonshadow


3 Answers

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

like image 62
moonshadow Avatar answered Nov 03 '22 00:11

moonshadow


That's because the QNaN function invokes UB by violating strict aliasing. The VS compiler is well within its rights to produce any behaviour.

like image 27
Puppy Avatar answered Nov 02 '22 23:11

Puppy


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.

like image 45
Angew is no longer proud of SO Avatar answered Nov 02 '22 22:11

Angew is no longer proud of SO