Checking if a double (or float) is NaN in C++

后端 未结 21 1906
北恋
北恋 2020-11-22 05:10

Is there an isnan() function?

PS.: I\'m in MinGW (if that makes a difference).

I had this solved by using isnan() from , which doe

21条回答
  •  太阳男子
    2020-11-22 05:48

    As of C++14 there are a number of ways to test if a floating point number value is a NaN.

    Of these ways, only checking of the bits of the number's representation, works reliably, as noted in my original answer. In particular, std::isnan and the often proposed check v != v, do not work reliably and should not be used, lest your code stops working correctly when someone decides that floating point optimization is needed, and asks the compiler to do that. This situation can change, compilers can get more conforming, but for this issue that hasn't happened in the 6 years since the original answer.

    For about 6 years my original answer was the selected solution for this question, which was OK. But recently a highly upvoted answer recommending the unreliable v != v test has been selected. Hence this additional more up-to-date answer (we now have the C++11 and C++14 standards, and C++17 on the horizon).


    The main ways to check for NaN-ness, as of C++14, are:

    • std::isnan(value) )
      is the intended standard library way since C++11. isnan apparently conflicts with the Posix macro of the same name, but in practice that isn't a problem. The main problem is that when floating point arithmetic optimization is requested, then with at least one main compiler, namely g++, std::isnan returns false for NaN argument.

    • (fpclassify(value) == FP_NAN) )
      Suffers from the same problem as std::isnan, i.e., is not reliable.

    • (value != value) )
      Recommended in many SO answers. Suffers from the same problem as std::isnan, i.e., is not reliable.

    • (value == Fp_info::quiet_NaN()) )
      This is a test that with standard behavior should not detect NaNs, but that with the optimized behavior maybe could detect NaNs (due to optimized code just comparing the bitlevel representations directly), and perhaps combined with another way to cover the standard un-optimized behavior, could reliably detect NaN. Unfortunately it turned out to not work reliably.

    • (ilogb(value) == FP_ILOGBNAN) )
      Suffers from the same problem as std::isnan, i.e., is not reliable.

    • isunordered(1.2345, value) )
      Suffers from the same problem as std::isnan, i.e., is not reliable.

    • is_ieee754_nan( value ) )
      This isn't a standard function. It's checking of the bits according to the IEEE 754 standard. It's completely reliable but the code is somewhat system-dependent.


    In the following complete test code “success” is whether an expression reports Nan-ness of the value. For most expressions this measure of success, the goal of detecting NaNs and only NaNs, corresponds to their standard semantics. For the (value == Fp_info::quiet_NaN()) ) expression, however, the standard behavior is that it doesn't work as a NaN-detector.

    #include         // std::isnan, std::fpclassify
    #include 
    #include       // std::setw
    #include 
    #include      // CHAR_BIT
    #include 
    #include      // uint64_t
    using namespace std;
    
    #define TEST( x, expr, expected ) \
        [&](){ \
            const auto value = x; \
            const bool result = expr; \
            ostringstream stream; \
            stream << boolalpha << #x " = " << x << ", (" #expr ") = " << result; \
            cout \
                << setw( 60 ) << stream.str() << "  " \
                << (result == expected? "Success" : "FAILED") \
                << endl; \
        }()
    
    #define TEST_ALL_VARIABLES( expression ) \
        TEST( v, expression, true ); \
        TEST( u, expression, false ); \
        TEST( w, expression, false )
    
    using Fp_info = numeric_limits;
    
    inline auto is_ieee754_nan( double const x )
        -> bool
    {
        static constexpr bool   is_claimed_ieee754  = Fp_info::is_iec559;
        static constexpr int    n_bits_per_byte     = CHAR_BIT;
        using Byte = unsigned char;
    
        static_assert( is_claimed_ieee754, "!" );
        static_assert( n_bits_per_byte == 8, "!" );
        static_assert( sizeof( x ) == sizeof( uint64_t ), "!" );
    
        #ifdef _MSC_VER
            uint64_t const bits = reinterpret_cast( x );
        #else
            Byte bytes[sizeof(x)];
            memcpy( bytes, &x, sizeof( x ) );
            uint64_t int_value;
            memcpy( &int_value, bytes, sizeof( x ) );
            uint64_t const& bits = int_value;
        #endif
    
        static constexpr uint64_t   sign_mask       = 0x8000000000000000;
        static constexpr uint64_t   exp_mask        = 0x7FF0000000000000;
        static constexpr uint64_t   mantissa_mask   = 0x000FFFFFFFFFFFFF;
    
        (void) sign_mask;
        return (bits & exp_mask) == exp_mask and (bits & mantissa_mask) != 0;
    }
    
    auto main()
        -> int
    {
        double const v = Fp_info::quiet_NaN();
        double const u = 3.14;
        double const w = Fp_info::infinity();
    
        cout << boolalpha << left;
        cout << "Compiler claims IEEE 754 = " << Fp_info::is_iec559 << endl;
        cout << endl;;
        TEST_ALL_VARIABLES( std::isnan(value) );                    cout << endl;
        TEST_ALL_VARIABLES( (fpclassify(value) == FP_NAN) );        cout << endl;
        TEST_ALL_VARIABLES( (value != value) );                     cout << endl;
        TEST_ALL_VARIABLES( (value == Fp_info::quiet_NaN()) );      cout << endl;
        TEST_ALL_VARIABLES( (ilogb(value) == FP_ILOGBNAN) );        cout << endl;
        TEST_ALL_VARIABLES( isunordered(1.2345, value) );           cout << endl;
        TEST_ALL_VARIABLES( is_ieee754_nan( value ) );
    }
    

    Results with g++ (note again that the standard behavior of (value == Fp_info::quiet_NaN()) is that it doesn't work as a NaN-detector, it's just very much of practical interest here):

    [C:\my\forums\so\282  (detect NaN)]
    > g++ --version | find "++"
    g++ (x86_64-win32-sjlj-rev1, Built by MinGW-W64 project) 6.3.0
    
    [C:\my\forums\so\282  (detect NaN)]
    > g++ foo.cpp && a
    Compiler claims IEEE 754 = true
    
    v = nan, (std::isnan(value)) = true                           Success
    u = 3.14, (std::isnan(value)) = false                         Success
    w = inf, (std::isnan(value)) = false                          Success
    
    v = nan, ((fpclassify(value) == 0x0100)) = true               Success
    u = 3.14, ((fpclassify(value) == 0x0100)) = false             Success
    w = inf, ((fpclassify(value) == 0x0100)) = false              Success
    
    v = nan, ((value != value)) = true                            Success
    u = 3.14, ((value != value)) = false                          Success
    w = inf, ((value != value)) = false                           Success
    
    v = nan, ((value == Fp_info::quiet_NaN())) = false            FAILED
    u = 3.14, ((value == Fp_info::quiet_NaN())) = false           Success
    w = inf, ((value == Fp_info::quiet_NaN())) = false            Success
    
    v = nan, ((ilogb(value) == ((int)0x80000000))) = true         Success
    u = 3.14, ((ilogb(value) == ((int)0x80000000))) = false       Success
    w = inf, ((ilogb(value) == ((int)0x80000000))) = false        Success
    
    v = nan, (isunordered(1.2345, value)) = true                  Success
    u = 3.14, (isunordered(1.2345, value)) = false                Success
    w = inf, (isunordered(1.2345, value)) = false                 Success
    
    v = nan, (is_ieee754_nan( value )) = true                     Success
    u = 3.14, (is_ieee754_nan( value )) = false                   Success
    w = inf, (is_ieee754_nan( value )) = false                    Success
    
    [C:\my\forums\so\282  (detect NaN)]
    > g++ foo.cpp -ffast-math && a
    Compiler claims IEEE 754 = true
    
    v = nan, (std::isnan(value)) = false                          FAILED
    u = 3.14, (std::isnan(value)) = false                         Success
    w = inf, (std::isnan(value)) = false                          Success
    
    v = nan, ((fpclassify(value) == 0x0100)) = false              FAILED
    u = 3.14, ((fpclassify(value) == 0x0100)) = false             Success
    w = inf, ((fpclassify(value) == 0x0100)) = false              Success
    
    v = nan, ((value != value)) = false                           FAILED
    u = 3.14, ((value != value)) = false                          Success
    w = inf, ((value != value)) = false                           Success
    
    v = nan, ((value == Fp_info::quiet_NaN())) = true             Success
    u = 3.14, ((value == Fp_info::quiet_NaN())) = true            FAILED
    w = inf, ((value == Fp_info::quiet_NaN())) = true             FAILED
    
    v = nan, ((ilogb(value) == ((int)0x80000000))) = true         Success
    u = 3.14, ((ilogb(value) == ((int)0x80000000))) = false       Success
    w = inf, ((ilogb(value) == ((int)0x80000000))) = false        Success
    
    v = nan, (isunordered(1.2345, value)) = false                 FAILED
    u = 3.14, (isunordered(1.2345, value)) = false                Success
    w = inf, (isunordered(1.2345, value)) = false                 Success
    
    v = nan, (is_ieee754_nan( value )) = true                     Success
    u = 3.14, (is_ieee754_nan( value )) = false                   Success
    w = inf, (is_ieee754_nan( value )) = false                    Success
    
    [C:\my\forums\so\282  (detect NaN)]
    > _
    

    Results with Visual C++:

    [C:\my\forums\so\282  (detect NaN)]
    > cl /nologo- 2>&1 | find "++"
    Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23725 for x86
    
    [C:\my\forums\so\282  (detect NaN)]
    > cl foo.cpp /Feb && b
    foo.cpp
    Compiler claims IEEE 754 = true
    
    v = nan, (std::isnan(value)) = true                           Success
    u = 3.14, (std::isnan(value)) = false                         Success
    w = inf, (std::isnan(value)) = false                          Success
    
    v = nan, ((fpclassify(value) == 2)) = true                    Success
    u = 3.14, ((fpclassify(value) == 2)) = false                  Success
    w = inf, ((fpclassify(value) == 2)) = false                   Success
    
    v = nan, ((value != value)) = true                            Success
    u = 3.14, ((value != value)) = false                          Success
    w = inf, ((value != value)) = false                           Success
    
    v = nan, ((value == Fp_info::quiet_NaN())) = false            FAILED
    u = 3.14, ((value == Fp_info::quiet_NaN())) = false           Success
    w = inf, ((value == Fp_info::quiet_NaN())) = false            Success
    
    v = nan, ((ilogb(value) == 0x7fffffff)) = true                Success
    u = 3.14, ((ilogb(value) == 0x7fffffff)) = false              Success
    w = inf, ((ilogb(value) == 0x7fffffff)) = true                FAILED
    
    v = nan, (isunordered(1.2345, value)) = true                  Success
    u = 3.14, (isunordered(1.2345, value)) = false                Success
    w = inf, (isunordered(1.2345, value)) = false                 Success
    
    v = nan, (is_ieee754_nan( value )) = true                     Success
    u = 3.14, (is_ieee754_nan( value )) = false                   Success
    w = inf, (is_ieee754_nan( value )) = false                    Success
    
    [C:\my\forums\so\282  (detect NaN)]
    > cl foo.cpp /Feb /fp:fast && b
    foo.cpp
    Compiler claims IEEE 754 = true
    
    v = nan, (std::isnan(value)) = true                           Success
    u = 3.14, (std::isnan(value)) = false                         Success
    w = inf, (std::isnan(value)) = false                          Success
    
    v = nan, ((fpclassify(value) == 2)) = true                    Success
    u = 3.14, ((fpclassify(value) == 2)) = false                  Success
    w = inf, ((fpclassify(value) == 2)) = false                   Success
    
    v = nan, ((value != value)) = true                            Success
    u = 3.14, ((value != value)) = false                          Success
    w = inf, ((value != value)) = false                           Success
    
    v = nan, ((value == Fp_info::quiet_NaN())) = false            FAILED
    u = 3.14, ((value == Fp_info::quiet_NaN())) = false           Success
    w = inf, ((value == Fp_info::quiet_NaN())) = false            Success
    
    v = nan, ((ilogb(value) == 0x7fffffff)) = true                Success
    u = 3.14, ((ilogb(value) == 0x7fffffff)) = false              Success
    w = inf, ((ilogb(value) == 0x7fffffff)) = true                FAILED
    
    v = nan, (isunordered(1.2345, value)) = true                  Success
    u = 3.14, (isunordered(1.2345, value)) = false                Success
    w = inf, (isunordered(1.2345, value)) = false                 Success
    
    v = nan, (is_ieee754_nan( value )) = true                     Success
    u = 3.14, (is_ieee754_nan( value )) = false                   Success
    w = inf, (is_ieee754_nan( value )) = false                    Success
    
    [C:\my\forums\so\282  (detect NaN)]
    > _
    

    Summing up the above results, only direct testing of the bit-level representation, using the is_ieee754_nan function defined in this test program, worked reliably in all cases with both g++ and Visual C++.


    Addendum:
    After posting the above I became aware of yet another possible to test for NaN, mentioned in another answer here, namely ((value < 0) == (value >= 0)). That turned out to work fine with Visual C++ but failed with g++'s -ffast-math option. Only direct bitpattern testing works reliably.

提交回复
热议问题