Проверка, является ли double (или float) NaN в C ++ - PullRequest
349 голосов
/ 20 февраля 2009

Есть ли функция isnan ()?

PS: я в MinGW (если это имеет значение).

Я решил эту проблему с помощью isnan () из <math.h>, которого нет в <cmath>, который я #include поначалу описал.

Ответы [ 20 ]

8 голосов
/ 09 февраля 2017

Начиная с C ++ 14, есть несколько способов проверить, является ли число с плавающей запятой value NaN.

Из этих способов только проверка битов представления числа, работает надежно, как отмечено в моем первоначальном ответе. В частности, std::isnan и часто предлагаемая проверка v != v не работают надежно и не должны использоваться, иначе ваш код перестанет работать правильно, когда кто-то решит, что необходима оптимизация с плавающей запятой, и попросит компилятор сделать это. Эта ситуация может измениться, компиляторы могут стать более соответствующими, но для этой проблемы, которая не возникала в течение 6 лет с момента первоначального ответа.

Около 6 лет моим первоначальным ответом было выбранное решение для этого вопроса, которое было в порядке. Но недавно был выбран ответ с сильным голосом, рекомендующий ненадежный тест v != v. Отсюда и этот дополнительный более актуальный ответ (теперь у нас есть стандарты C ++ 11 и C ++ 14 и C ++ 17 на горизонте).


Основные способы проверки NaN-сущности, начиная с C ++ 14:

  • std::isnan(value) )
    это стандартный библиотечный путь начиная с C ++ 11. isnan явно конфликтует с Макрос Posix с тем же именем, но на практике это не проблема. Основная проблема что при запросе арифметической оптимизации с плавающей запятой, по крайней мере с одним основным компилятором, а именно g ++, std::isnan возвращает false для аргумента NaN .

  • (fpclassify(value) == FP_NAN) )
    Страдает от той же проблемы, что и std::isnan, то есть не надежна.

  • (value != value) )
    Рекомендуется во многих ответах SO. Страдает от той же проблемы, что и std::isnan, т.е. не является надежным.

  • (value == Fp_info::quiet_NaN()) )
    Это тест, который при стандартном поведении не должен обнаруживать NaN, но при оптимизированное поведение, возможно, может обнаружить NaN (из-за оптимизированного кода, просто сравнивая представления на уровне битов непосредственно), и, возможно, в сочетании с другим способом охватывают стандартное неоптимизированное поведение, может надежно определять NaN к несчастью оказалось, что не работает надежно.

  • (ilogb(value) == FP_ILOGBNAN) )
    Страдает от той же проблемы, что и std::isnan, то есть не надежна.

  • isunordered(1.2345, value) )
    Страдает от той же проблемы, что и std::isnan, то есть не надежна.

  • is_ieee754_nan( value ) )
    Это не стандартная функция. Это проверка битов в соответствии с IEEE 754 стандарт. Это полностью надежный , но код в некоторой степени зависит от системы.


В следующем полном тестовом коде & ldquo; success & rdquo; является ли выражение сообщает Nanness значения. Для большинства выражений эта мера успеха, цель обнаружения NaN и только NaN, соответствует их стандартной семантике. Однако для выражения (value == Fp_info::quiet_NaN()) ) стандартное поведение состоит в том, что оно не работает как NaN-детектор.

#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 ) );
}

Результаты с g ++ (еще раз отметим, что стандартное поведение (value == Fp_info::quiet_NaN()) заключается в том, что он не работает как NaN-детектор, здесь просто очень много интересного):

[C:\my\forums\so\282  (detect NaN)]
> <b><i>g++ --version | find "++"</i></b>
g++ (x86_64-win32-sjlj-rev1, Built by MinGW-W64 project) 6.3.0

[C:\my\forums\so\282  (detect NaN)]
> <b><i>g++ foo.cpp && a</i></b>
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)]
> <b><i>g++ foo.cpp -ffast-math && a</i></b>
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)]
> _

Результаты с Visual C ++:

[C:\my\forums\so\282  (detect NaN)]
> <b><i>cl /nologo- 2>&1 | find "++"</i></b>
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23725 for x86

[C:\my\forums\so\282  (detect NaN)]
> <b><i>cl foo.cpp /Feb && b</i></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)]
> <b><i>cl foo.cpp /Feb /fp:fast && b</i></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)]
> _

Суммируя вышеприведенные результаты, только прямое тестирование представления на битовом уровне с использованием функции is_ieee754_nan, определенной в этой тестовой программе, надежно работало во всех случаях как с g ++, так и с Visual C ++.


Добавление:
После публикации вышеизложенного мне стало известно о еще одном возможном тесте на NaN, упомянутом в другом ответе здесь, а именно ((value < 0) == (value >= 0)). Это оказалось хорошо работать с Visual C ++, но не удалось с опцией g ++ -ffast-math. Надежно работает только прямое тестирование на наличие бит-паттернов.

7 голосов
/ 03 октября 2013
inline bool IsNan(float f)
{
    const uint32 u = *(uint32*)&f;
    return (u&0x7F800000) == 0x7F800000 && (u&0x7FFFFF);    // Both NaN and qNan.
}

inline bool IsNan(double d)
{
    const uint64 u = *(uint64*)&d;
    return (u&0x7FF0000000000000ULL) == 0x7FF0000000000000ULL && (u&0xFFFFFFFFFFFFFULL);
}

Это работает, если sizeof(int) равно 4, а sizeof(long long) равно 8.

Во время выполнения это только сравнение, кастинги не занимают время. Он просто изменяет конфигурацию флагов сравнения, чтобы проверить равенство.

4 голосов
/ 26 января 2016

Учитывая, что (x! = X) не всегда гарантируется для NaN (например, при использовании опции -ffast-math), я использовал:

#define IS_NAN(x) (((x) < 0) == ((x) >= 0))

Числа не могут быть одновременно <0 и> = 0, поэтому в действительности эта проверка проходит только в том случае, если число не меньше, не больше или не равно нулю. Который в основном не число вообще или NaN.

Вы также можете использовать это, если вы предпочитаете:

#define IS_NAN(x) (!((x)<0) && !((x)>=0)

Я не уверен, как это влияет на -ffast-math, поэтому ваш пробег может отличаться.

4 голосов
/ 11 декабря 2012

Что касается меня, решение может быть макросом, чтобы сделать его явно встроенным и, следовательно, достаточно быстрым. Это также работает для любого типа поплавка. Он основан на том факте, что единственный случай, когда значение не равно себе, - это когда значение не является числом.

#ifndef isnan
  #define isnan(a) (a != a)
#endif
4 голосов
/ 04 апреля 2012

Возможное решение, которое не будет зависеть от конкретного представления IEEE для используемого NaN, будет следующим:

template<class T>
bool isnan( T f ) {
    T _nan =  (T)0.0/(T)0.0;
    return 0 == memcmp( (void*)&f, (void*)&_nan, sizeof(T) );
}
3 голосов
/ 04 августа 2014

Это работает:

#include <iostream>
#include <math.h>
using namespace std;

int main ()
{
  char ch='a';
  double val = nan(&ch);
  if(isnan(val))
     cout << "isnan" << endl;

  return 0;
}

вывод: isnan

1 голос
/ 08 октября 2016

Мне кажется, что наилучшим по-настоящему кроссплатформенным подходом было бы использование объединения и тестирование битовой комбинации двойного кода для проверки на наличие NaN.

Я не полностью протестировал это решение, и, возможно, существует более эффективный способ работы с битовыми шаблонами, но я думаю, что оно должно работать.

#include <stdint.h>
#include <stdio.h>

union NaN
{
    uint64_t bits;
    double num;
};

int main()
{
    //Test if a double is NaN
    double d = 0.0 / 0.0;
    union NaN n;
    n.num = d;
    if((n.bits | 0x800FFFFFFFFFFFFF) == 0xFFFFFFFFFFFFFFFF)
    {
        printf("NaN: %f", d);
    }

    return 0;
}
0 голосов
/ 30 ноября 2018

Это обнаруживает бесконечность, а также NaN в Visual Studio, проверяя, что оно в двойных пределах:

//#include <float.h>
double x, y = -1.1; x = sqrt(y);
if (x >= DBL_MIN && x <= DBL_MAX )
    cout << "DETECTOR-2 of errors FAILS" << endl;
else
    cout << "DETECTOR-2 of errors OK" << endl;
0 голосов
/ 31 января 2014

Стандарт IEEE гласит когда показатель равен 1 а также мантисса не ноль, число является NaN. Двойной является 1 знаковым битом, 11 экспонентными битами и 52 битами мантиссы. Проверь немного.

0 голосов
/ 27 июня 2013

Как указано выше, a! = A не будет работать в g ++ и некоторых других компиляторах, но этот прием должен. Это может быть не так эффективно, но это все-таки способ:

bool IsNan(float a)
{
    char s[4];
    sprintf(s, "%.3f", a);
    if (s[0]=='n') return true;
    else return false;
}

По сути, в g ++ (хотя я не уверен насчет других) printf печатает 'nan' в форматах% d или% .f, если переменная не является допустимым целым числом / с плавающей точкой. Поэтому этот код проверяет, является ли первый символ строки 'n' (как в "nan")

...