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

后端 未结 21 1857
北恋
北恋 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 <cmath>        // std::isnan, std::fpclassify
    #include <iostream>
    #include <iomanip>      // std::setw
    #include <limits>
    #include <limits.h>     // CHAR_BIT
    #include <sstream>
    #include <stdint.h>     // 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<double>;
    
    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<uint64_t const&>( 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.

    0 讨论(0)
  • 2020-11-22 05:49

    You can use numeric_limits<float>::quiet_NaN( ) defined in the limits standard library to test with. There's a separate constant defined for double.

    #include <iostream>
    #include <math.h>
    #include <limits>
    
    using namespace std;
    
    int main( )
    {
       cout << "The quiet NaN for type float is:  "
            << numeric_limits<float>::quiet_NaN( )
            << endl;
    
       float f_nan = numeric_limits<float>::quiet_NaN();
    
       if( isnan(f_nan) )
       {
           cout << "Float was Not a Number: " << f_nan << endl;
       }
    
       return 0;
    }
    

    I don't know if this works on all platforms, as I only tested with g++ on Linux.

    0 讨论(0)
  • 2020-11-22 05:50

    On x86-64 you can have extremely fast methods for checking for NaN and infinity, which work regardless of -ffast-math compiler option. (f != f, std::isnan, std::isinf always yield false with -ffast-math).


    Testing for NaN, infinity and finite numbers can easily be done by checking for maximum exponent. infinity is maximum exponent with zero mantissa, NaN is maximum exponent and non-zero mantissa. The exponent is stored in the next bits after the topmost sign bit, so that we can just left shift to get rid of the sign bit and make the exponent the topmost bits, no masking (operator&) is necessary:

    static inline uint64_t load_ieee754_rep(double a) {
        uint64_t r;
        static_assert(sizeof r == sizeof a, "Unexpected sizes.");
        std::memcpy(&r, &a, sizeof a); // Generates movq instruction.
        return r;
    }
    
    static inline uint32_t load_ieee754_rep(float a) {
        uint32_t r;
        static_assert(sizeof r == sizeof a, "Unexpected sizes.");
        std::memcpy(&r, &a, sizeof a); // Generates movd instruction.
        return r;
    }
    
    constexpr uint64_t inf_double_shl1 = UINT64_C(0xffe0000000000000);
    constexpr uint32_t inf_float_shl1 = UINT32_C(0xff000000);
    
    // The shift left removes the sign bit. The exponent moves into the topmost bits,
    // so that plain unsigned comparison is enough.
    static inline bool isnan2(double a)    { return load_ieee754_rep(a) << 1  > inf_double_shl1; }
    static inline bool isinf2(double a)    { return load_ieee754_rep(a) << 1 == inf_double_shl1; }
    static inline bool isfinite2(double a) { return load_ieee754_rep(a) << 1  < inf_double_shl1; }
    static inline bool isnan2(float a)     { return load_ieee754_rep(a) << 1  > inf_float_shl1; }
    static inline bool isinf2(float a)     { return load_ieee754_rep(a) << 1 == inf_float_shl1; }
    static inline bool isfinite2(float a)  { return load_ieee754_rep(a) << 1  < inf_float_shl1; }
    

    The std versions of isinf and isfinite load 2 double/float constants from .data segment and in the worst case scenario they can cause 2 data cache misses. The above versions do not load any data, inf_double_shl1 and inf_float_shl1 constants get encoded as immediate operands into the assembly instructions.


    Faster isnan2 is just 2 assembly instructions:

    bool isnan2(double a) {
        bool r;
        asm(".intel_syntax noprefix"
            "\n\t ucomisd %1, %1"
            "\n\t setp %b0"
            "\n\t .att_syntax prefix"
            : "=g" (r)
            : "x" (a)
            : "cc"
            );
        return r;
    }
    

    Uses the fact that ucomisd instruction sets parity flag if any argument is NaN. This is how std::isnan works when no -ffast-math options is specified.

    0 讨论(0)
提交回复
热议问题