Проверка, является ли double (или float) NaN в C++

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

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

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

21 ответ

Решение

Согласно стандарту IEEE, значения NaN имеют странное свойство, заключающееся в том, что сравнивающие их всегда ложны. То есть для поплавка е, f != f будет истинным, только если f является NaN.

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

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

Первое решение: если вы используете C++11

После того, как об этом спросили, появилось немного новых разработок: важно знать, что std::isnan() является частью C++11

конспект

Определено в заголовке <cmath>

bool isnan( float arg ); (since C++11)
bool isnan( double arg ); (since C++11)
bool isnan( long double arg ); (since C++11)

Определяет, является ли данный аргумент числа с плавающей точкой не-число (NaN).

параметры

arg: значение с плавающей точкой

Возвращаемое значение

true если аргумент NaN, false иначе

Ссылка

http://en.cppreference.com/w/cpp/numeric/math/isnan

Обратите внимание, что это несовместимо с -fast-math, если вы используете g++, другие предложения приведены ниже.


Другие решения: если вы используете инструменты, не совместимые с C++11

Для C99, в C это реализовано как макрос isnan(c)который возвращает значение int. Тип x должен быть плавающим, двойным или длинным двойным.

Различные поставщики могут включать или не включать функцию isnan(),

Предположительно портативный способ проверки NaN использовать свойство IEEE 754, которое NaN не равно себе: т.е. x == x будет ложным для x являющийся NaN,

Однако последний вариант может не работать с каждым компилятором и некоторыми настройками (в частности, настройками оптимизации), поэтому, в крайнем случае, вы всегда можете проверить битовую комбинацию...

Здесь нет isnan() функция доступна в текущей стандартной библиотеке C++. Он был введен в C99 и определен как макрос, а не функция. Элементы стандартной библиотеки, определенные C99, не являются частью текущего стандарта C++ ISO/IEC 14882:1998 и не являются его обновлением ISO/IEC 14882:2003.

В 2005 году был предложен Технический отчет 1. TR1 обеспечивает совместимость с C99 для C++. Несмотря на то, что он никогда официально не принимался стать стандартом C++, многие (реализации GCC 4.0+ или Visual C++ 9.0+ C++ предоставляют функции TR1, все они или только некоторые (Visual C++ 9.0 не предоставляет математические функции C99),

Если TR1 доступен, то cmath включает в себя элементы C99, такие как isnan(), isfinite()и т. д., но они определяются как функции, а не макросы, обычно в std::tr1:: пространства имен, хотя многие реализации (например, GCC 4+ в Linux или в XCode в Mac OS X 10.5+) внедряют их непосредственно в std::, так std::isnan хорошо определено.

Более того, некоторые реализации C++ все еще делают C99 isnan() макрос, доступный для C++ (включен через cmath или же math.h), что может вызвать больше недоразумений, и разработчики могут предположить, что это стандартное поведение.

Замечание о Viusal C++, как уже упоминалось выше, в нем не предусмотрено std::isnan ни std::tr1::isnan, но он предоставляет функцию расширения, определенную как _isnan() который был доступен начиная с Visual C++ 6.0

На XCode еще больше веселья. Как уже упоминалось, GCC 4+ определяет std::isnan, Для более старых версий компилятора и библиотеки XCode, кажется (здесь уместно обсуждение), не было возможности проверить себя) определены две функции, __inline_isnand() на Intel и __isnand() на Power PC.

В Boost также есть библиотека только для заголовков, в которой есть удобные инструменты для работы с типами данных с плавающей запятой.

#include <boost/math/special_functions/fpclassify.hpp>

Вы получаете следующие функции:

template <class T> bool isfinite(T z);
template <class T> bool isinf(T t);
template <class T> bool isnan(T t);
template <class T> bool isnormal(T t);

Если у вас есть время, взгляните на весь математический инструментарий от Boost, он имеет много полезных инструментов и быстро растет.

Также при работе с плавающими и не плавающими точками было бы неплохо взглянуть на числовые преобразования.

Есть три "официальных" способа: posix isnan макрос, с ++0x isnan шаблон функции или Visual C++ _isnan функция

К сожалению, определить, какой из них использовать, довольно непрактично.

И, к сожалению, нет надежного способа определить, есть ли у вас представление IEEE 754 с NaN. Стандартная библиотека предлагает официальный способ (numeric_limits<double>::is_iec559). Но на практике компиляторы, такие как g ++, облажались.

В теории можно было бы просто использовать x != x, но компиляторы, такие как g ++ и visual C++, все испортили.

Итак, в конце, протестируйте для конкретных битовых шаблонов NaN, предполагая (и, надеюсь, принудительно применяя, в какой-то момент!) Конкретное представление, такое как IEEE 754.


РЕДАКТИРОВАТЬ: в качестве примера "компиляторы, такие как g ++... напортачить", рассмотрим

#include <limits>
#include <assert.h>

void foo( double a, double b )
{
    assert( a != b );
}

int main()
{
    typedef std::numeric_limits<double> Info;
    double const nan1 = Info::quiet_NaN();
    double const nan2 = Info::quiet_NaN();
    foo( nan1, nan2 );
}

Компиляция с g ++ (TDM-2 mingw32) 4.4.1:

C: \ test> введите "C:\Program Files\@commands\gnuc.bat"
@rem -finput-charset=windows-1252
@g++ -O -pedantic -std= C++98 -Wall -Write-strings% * -Wno-long-long

C: \ test> gnuc x.cpp

C: \ test> && echo работает... || эхо! не удалось
работает...

C: \ test> gnuc x.cpp - fast-math

C: \ test> && echo работает... || эхо! не удалось
Ошибка подтверждения: a!= B, файл x.cpp, строка 6

Это приложение запросило среду выполнения прекратить его необычным способом.
Пожалуйста, обратитесь в службу поддержки приложения для получения дополнительной информации.!не удалось

C:\test> _

Существует std::isnan, если ваш компилятор поддерживает расширения c99, но я не уверен, что mingw поддерживает.

Вот небольшая функция, которая должна работать, если ваш компилятор не имеет стандартной функции:

bool custom_isnan(double var)
{
    volatile double d = var;
    return d != d;
}

Ты можешь использовать numeric_limits<float>::quiet_NaN( ) определены в limits стандартная библиотека для тестирования. Там есть отдельная константа, определенная для 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;
}

Я не знаю, работает ли это на всех платформах, так как я тестировал только с g++ на Linux.

Вы можете использовать isnan() функция, но вам нужно включить математическую библиотеку C.

#include <cmath>

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

inline bool isnan(double x) {
    return x != x;
}

Начиная с 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. Это абсолютно надежно, но код в некоторой степени зависит от системы.


В следующем полном тестовом коде "успех" означает, что выражение сообщает о значении значения. Для большинства выражений эта мера успеха, цель обнаружения 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  (обнаружение NaN)]
> g ++ - версия | найти "++"
g++ (x86_64-win32-sjlj-rev1, созданный проектом MinGW-W64) 6.3.0

[C:\my\forums\so\282  (обнаружение NaN)]
> g ++ foo.cpp && a
Заявление компилятора IEEE 754 = true

v = nan, (std::isnan(value)) = истинный успех
u = 3.14, (std::isnan(значение)) = ложь Успех
w = inf, (std::isnan(value)) = false Успех

v = nan, ((fpclassify(value) == 0x0100)) = true Успех
u = 3.14, ((fpclassify(value) == 0x0100)) = false Успех
w = inf, ((fpclassify(value) == 0x0100)) = false Успех

v = nan, ((значение!= значение)) = истинный успех
и = 3.14, ((значение!= значение)) = ложь Успех
w = inf, ((значение!= значение)) = ложь Успех

v = nan, ((значение == Fp_info::quiet_NaN())) = false            FAILED
u = 3.14, ((значение == Fp_info:: quiet_NaN ())) = false Успешно
w = inf, ((значение == Fp_info:: quiet_NaN ())) = false Успех

v = nan, ((ilogb(значение) == ((int)0x80000000))) = true Успех
u = 3.14, ((ilogb(значение) == ((int)0x80000000))) = false Успех
w = inf, ((ilogb(значение) == ((int)0x80000000))) = false Успех

v = nan, (isorordered(1.2345, value)) = истинный успех
u = 3,14, (неупорядочено (1,2345, значение)) = ложь Успех
w = inf, (isorordered(1.2345, value)) = false Успех

v = nan, (is_ieee754_nan( value)) = истинный успех
u = 3,14, (is_ieee754_nan(значение)) = ложь Успех
w = inf, (is_ieee754_nan( value)) = false Успех

[C:\my\forums\so\282  (обнаружение NaN)]
> g ++ foo.cpp -ffast-math && a
Заявление компилятора IEEE 754 = true

v = nan, (std::isnan(value)) = false                          FAILED
u = 3.14, (std::isnan(значение)) = ложь Успех
w = inf, (std::isnan(value)) = false Успех

v = nan, ((fpclassify(value) == 0x0100)) = false              FAILED
u = 3.14, ((fpclassify(value) == 0x0100)) = false Успех
w = inf, ((fpclassify(value) == 0x0100)) = false Успех

v = nan, ((value!= value)) = false                           FAILED
и = 3.14, ((значение!= значение)) = ложь Успех
w = inf, ((значение!= значение)) = ложь Успех

v = nan, ((value == Fp_info::quiet_NaN())) = истинный успех
u = 3.14, ((значение == Fp_info::quiet_NaN())) = true            FAILED
w = inf, ((значение == Fp_info::quiet_NaN())) = true             FAILED

v = nan, ((ilogb(значение) == ((int)0x80000000))) = true Успех
u = 3.14, ((ilogb(значение) == ((int)0x80000000))) = false Успех
w = inf, ((ilogb(значение) == ((int)0x80000000))) = false Успех

v = nan, (isorordered(1.2345, value)) = false                 FAILED
u = 3,14, (неупорядочено (1,2345, значение)) = ложь Успех
w = inf, (isorordered(1.2345, value)) = false Успех

v = nan, (is_ieee754_nan( value)) = истинный успех
u = 3,14, (is_ieee754_nan(значение)) = ложь Успех
w = inf, (is_ieee754_nan( value)) = false Успех

[C:\my\forums\so\282  (обнаружение NaN)]
> _

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

[C:\my\forums\so\282  (обнаружение NaN)]> cl / nologo- 2> & 1 | find "++" Оптимизирующий компилятор Microsoft (R) C/C++ версии 19.00.23725 для x86

[C:\my\forums\so\282  (обнаружение NaN)]> cl foo.cpp / Feb && b foo.cpp Заявления компилятора IEEE 754 = true v = nan, (std:: isnan (значение)) = true Успех u = 3.14, (std::isnan(значение)) = false Успех w = inf, (std:: isnan (значение)) = false Успех v = nan, ((fpclassify(value) == 2)) = true Успех u = 3.14, ((fpclassify(value) == 2)) = false Успех w = inf, ((fpclassify(value) == 2)) = false Успех v = nan, ((значение!= Значение)) = true Успех u = 3.14, ((значение!= Значение)) = false Успех w = inf, ((значение!= Значение)) = false Success v = nan, ((значение == Fp_info::quiet_NaN())) = false            FAILED
u = 3.14, ((value == Fp_info::quiet_NaN())) = false           Success
w = inf, ((value == Fp_info::quiet_NaN())) = false Успех v = nan, ((ilogb(значение) == 0x7fffffff)) = true Успех u = 3.14, ((ilogb(значение) == 0x7fffffff)) = false Успех w = inf, ((ilogb(значение) == 0x7fffffff)) = истинно НЕ УДАЛЕН v = nan, (неупорядочено (1.2345, значение)) = верно Успех u = 3.14, (isunordered(1.2345, значение)) = false Успех w = inf, (isorordered(1.2345, value)) = false Успех v = nan, (is_ieee754_nan( value)) = true Успех u = 3.14, (is_ieee754_nan(значение)) = false Успех w = inf, (is_ieee754_nan (значение)) = false Успех [C:\my\forums\so\282  (обнаружение NaN)]> cl foo.cpp / Feb / fp: fast && b foo.cpp Заявления компилятора IEEE 754 = true v = nan, (std:: isnan (значение)) = true Успех u = 3.14, (std::isnan(значение)) = false Успех w = inf, (std:: isnan (значение)) = false Успех v = nan, ((fpclassify(value) == 2)) = true Успех u = 3.14, ((fpclassify(value) == 2)) = false Успех w = inf, ((fpclassify(value) == 2)) = false Успех v = nan, ((значение!= Значение)) = true Успех u = 3.14, ((значение!= Значение)) = false Успех w = inf, ((значение!= Значение)) = ложный успех v = nan, ((значение == Fp_info:: quiet_NaN ())) = ложный СБОЙ u = 3.14, ((значение == Fp_info:: quiet_NaN ())) = ложный успех w = inf, ((значение == Fp_info:: quiet_NaN ())) = false Успех v = nan, ((ilogb(значение) == 0x7ffffff f)) = true Успех u = 3.14, ((ilogb(значение) == 0x7fffffff)) = false Успех w = inf, ((ilogb(значение) == 0x7fffffff)) = true FAILED v = nan, (isorordered (1.2345, значение)) = true Успех u = 3.14, (isorordered(1.2345, value)) = false Успех w = inf, (isorordered(1.2345, value)) = false Успех v = nan, (is_ieee754_nan( value)) = true Успех u = 3.14, (is_ieee754_nan(значение)) = false Успех w = inf, (is_ieee754_nan (значение)) = false Успех [C:\my\forums\so\282  (обнаружение NaN)]> _ _ 

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


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

Нан профилактика

Мой ответ на этот вопрос: не используйте обратные проверки для nan, Используйте профилактические проверки для подразделений формы 0.0/0.0 вместо.

#include <float.h>
float x=0.f ;             // I'm gonna divide by x!
if( !x )                  // Wait! Let me check if x is 0
  x = FLT_MIN ;           // oh, since x was 0, i'll just make it really small instead.
float y = 0.f / x ;       // whew, `nan` didn't appear.

nan результаты операции 0.f/0.f, или же 0.0/0.0, nan это ужасная неприятность для стабильности вашего кода, которая должна быть обнаружена и предотвращена очень осторожно 1. Свойства nan которые отличаются от нормальных чисел:

  • nan токсичен, (5* nan знак равно nan)
  • nan не равно ни чему, даже самому себе (nan знак равно nan)
  • nan не больше чем что-либо (nan!> 0)
  • nan не меньше чем ничего (nan! <0)

Последние 2 перечисленных свойства противоречивы и приведут к странному поведению кода, который основан на сравнении с nan число (третье последнее свойство тоже странно, но вы, вероятно, никогда не увидите x != x ? в вашем коде (если вы не проверяете NAN (ненадежно))).

В своем собственном коде я заметил, что nan значения имеют тенденцию приводить к трудностям поиска ошибок. (Обратите внимание, что это не так для inf или же -inf, (-inf <0) возвращает TRUE, (0 < inf) возвращает TRUE и даже (-inf < inf) возвращает TRUE. Таким образом, по моему опыту, поведение кода часто остается желаемым.

что делать под нан

То, что вы хотите случиться под 0.0/0.0 должен обрабатываться как особый случай, но то, что вы делаете, должно зависеть от чисел, которые вы ожидаете получить из кода.

В приведенном выше примере результат (0.f/FLT_MIN) будет 0, в принципе. Вы можете хотеть 0.0/0.0 чтобы генерировать HUGE вместо. Так,

float x=0.f, y=0.f, z;
if( !x && !y )    // 0.f/0.f case
  z = FLT_MAX ;   // biggest float possible
else
  z = y/x ;       // regular division.

Таким образом, в приведенном выше, если х были 0.f, inf результат (который имеет довольно хорошее / неразрушающее поведение, как упомянуто выше на самом деле).

Помните, целочисленное деление на 0 вызывает исключение времени выполнения. Поэтому вы всегда должны проверять целочисленное деление на 0. Просто потому, что 0.0/0.0 спокойно оценивает nan не означает, что вы можете быть ленивым и не проверять 0.0/0.0 прежде чем это произойдет.

1 Проверки для nan с помощью x != x иногда ненадежны ( x != x удаляются некоторыми оптимизирующими компиляторами, которые нарушают соответствие IEEE, особенно когда -ffast-math переключатель включен).

В следующем коде используется определение NAN (все установленные биты экспоненты, как минимум один набор дробных битов) и предполагается, что sizeof(int) = sizeof(float) = 4. Вы можете найти NAN в Википедии для получения подробной информации.

bool IsNan( float value ) { return ((*(UINT*)&value) & 0x7fffffff) > 0x7f800000; }

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.

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

После прочтения других ответов я хотел что-то, что прошло бы через предупреждение сравнения с плавающей запятой и не сломалось бы при быстрой математике. Следующий код работает:

/*
  Portable warning-free NaN test:
    * Does not emit warning with -Wfloat-equal (does not use float comparisons)
    * Works with -O3 -ffast-math (floating-point optimization)
    * Only call to standard library is memset and memcmp via <cstring>
    * Works for IEEE 754 compliant floating-point representations
    * Also works for extended precision long double
*/

#include <cstring>
template <class T> bool isNaN(T x)
{
  /*Initialize all bits including those used for alignment to zero. This sets
  all the values to positive zero but does not clue fast math optimizations as
  to the value of the variables.*/
  T z[4];
  memset(z, 0, sizeof(z));
  z[1] = -z[0];
  z[2] = x;
  z[3] = z[0] / z[2];

  /*Rationale for following test:
    * x is 0 or -0                                --> z[2] = 0, z[3] = NaN
    * x is a negative or positive number          --> z[3] = 0
    * x is a negative or positive denormal number --> z[3] = 0
    * x is negative or positive infinity          --> z[3] = 0
      (IEEE 754 guarantees that 0 / inf is zero)
    * x is a NaN                                  --> z[3] = NaN != 0.
  */

  //Do a bitwise comparison test for positive and negative zero.
  bool z2IsZero = memcmp(&z[2], &z[0], sizeof(T)) == 0 ||
                  memcmp(&z[2], &z[1], sizeof(T)) == 0;

  bool z3IsZero = memcmp(&z[3], &z[0], sizeof(T)) == 0 ||
                  memcmp(&z[3], &z[1], sizeof(T)) == 0; 

  //If the input is bitwise zero or negative zero, then it is not NaN.
  return !z2IsZero && !z3IsZero;
}

//NaN test suite
#include <iostream>

/*If printNaN is true then only expressions that are detected as NaN print and
vice versa.*/
template <class T> void test(bool printNaN)
{
  T v[10] = {-0.0, 0.0, -1.0, 1.0,
    std::numeric_limits<T>::infinity(),
    -std::numeric_limits<T>::infinity(),
    std::numeric_limits<T>::denorm_min(),
    -std::numeric_limits<T>::denorm_min(),
    std::numeric_limits<T>::quiet_NaN(),
    std::numeric_limits<T>::signaling_NaN()};
  for(int i = 0; i < 10; i++)
  {
    for(int j = 0; j < 10; j++)
    {
      if(isNaN(v[i] + v[j]) == printNaN)
        std::cout << v[i] << "+" << v[j] << " = " << v[i] + v[j] << std::endl;
      if(isNaN(v[i] - v[j]) == printNaN)
        std::cout << v[i] << "-" << v[j] << " = " << v[i] - v[j] << std::endl;
      if(isNaN(v[i] * v[j]) == printNaN)
        std::cout << v[i] << "*" << v[j] << " = " << v[i] * v[j] << std::endl;
      if(isNaN(v[i] / v[j]) == printNaN)
        std::cout << v[i] << "/" << v[j] << " = " << v[i] / v[j] << std::endl;
    }
  }
}

//Test each floating-point type.
int main()
{
  std::cout << "NaNs:" << std::endl;
  test<float>(true);
  test<double>(true);
  test<long double>(true);
  std::cout << std::endl << "Not NaNs:" << std::endl;
  test<float>(false);
  test<double>(false);
  test<long double>(false);
  return 0;
}

Возможное решение, которое не будет зависеть от конкретного представления 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) );
}

Учитывая, что (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, поэтому ваш пробег может отличаться.

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

#ifndef isnan
  #define isnan(a) (a != a)
#endif

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

#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

На x86-64 у вас могут быть чрезвычайно быстрые методы проверки NaN и бесконечности, которые работают независимо от -ffast-mathвариант компилятора. (f != f, std::isnan, std::isinf всегда уступать false с -ffast-math).


Проверка на NaN, бесконечность и конечные числа может быть легко выполнена путем проверки максимального показателя степени. бесконечность - максимальный показатель степени с нулевой мантиссой, NaN - максимальный показатель степени и ненулевой мантиссой. Показатель экспоненты сохраняется в следующих битах после самого верхнего знакового бита, поэтому мы можем просто сдвиг влево, чтобы избавиться от знакового бита и сделать экспоненту самыми верхними битами, без маскировки (operator&) это необходимо:

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

В std версии isinf а также isfinite нагрузка 2 double/float константы из .dataсегмент и в худшем случае они могут вызвать 2 промаха кэша данных. Вышеупомянутые версии не загружают никаких данных,inf_double_shl1 а также inf_float_shl1 константы кодируются как непосредственные операнды в инструкции сборки.


Быстрее isnan2 всего 2 инструкции по сборке:

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

Использует тот факт, что ucomisd инструкция устанавливает флаг четности, если какой-либо аргумент равен NaN. Вот какstd::isnan работает когда нет -ffast-math параметры указаны.

Мне кажется, что лучший действительно кроссплатформенный подход - это использовать объединение и тестировать битовую комбинацию двойника для проверки на наличие 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;
}

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

Как указано выше, 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, если переменная не является допустимым целым числом /float. Поэтому этот код проверяет, является ли первый символ строки 'n' (как в "nan")

Это обнаруживает бесконечность, а также 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;
Другие вопросы по тегам