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

369

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

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

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

Hasen
источник
2
Я не чистая ты можешь это сделать переносимо. Кто сказал, что C ++ требует IEEE754?
Дэвид Хеффернан
Просто обратите внимание, 1 унция профилактики лучше, чем 1 фунт лечения. Другими словами, предотвращение выполнения 0.f / 0.f намного лучше, чем обратная проверка nanв вашем коде. nanЭто может быть ужасно разрушительным для вашей программы, если ее распространять, то это может привести к трудностям в поиске ошибок. Это потому, что nanядовито, (5 * nan= nan), nanне равно чему-либо ( nan! = nan), nanНе больше чем что-либо ( nan!> 0), nanне меньше чем что-либо ( nan! <0).
бобобо
1
@bobobobo: это функция, позволяющая централизованную проверку ошибок. Так же, как исключения против возвращаемых значений.
Бен Фойгт
2
Почему у <cmath> нет isnan ()? Это в std ::
frankliuao

Ответы:

351

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

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

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

jalf
источник
4
Компилятору лучше не удалять это, если он работает в режиме IEEE. Проверьте документацию для вашего компилятора, конечно ...
dmckee --- ex-moderator kitten
38
-1 работает только в теории, а не на практике: такие компиляторы, как g ++ (с -fastmath), облажаются. единственный общий способ, до c ++ 0x, - это проверка на наличие битового паттерна.
ура и hth. - Альф
66
@Alf: в документации для -ffast-mathопции явно сказано, что это может привести к неправильному выводу для программ, которые зависят от точной реализации, если правила / спецификации IEEE или ISO для математических функций. Если эта опция не включена, использование x != xявляется вполне допустимым и переносимым способом тестирования для NaN.
Адам Розенфилд
7
@ Adam: документация открыто заявляет, что она не соответствует, да. и да, я сталкивался с этим аргументом прежде, обсуждая это подробно с Габриэлем Дос Рейсом. это обычно используется для защиты дизайна, в круглом аргументе (я не знаю, собирались ли вы с этим что-то связать, но стоит знать об этом - это пламя войны). Ваш вывод, который x != xдействителен без этой опции, не следует логически. это может быть правдой для конкретной версии g ++ или нет. В любом случае, у вас нет возможности гарантировать, что опция fastmath не будет использоваться.
ура и hth. - Альф
7
@ Альф: Нет, я не знал о вашей беседе с Габриэлем Дос Рейсом. В другом вопросе Стив Джессоп высказал большое мнение о предположении о представлении IEEE. Если вы предполагаете, что IEEE 754 и что компилятор работает соответствующим образом (т.е. без -ffast-mathопции), то x != xэто допустимое и переносимое решение. Вы даже можете проверить это -ffast-math, протестировав __FAST_MATH__макрос и переключившись на другую реализацию в этом случае (например, использовать объединения и битовое перемешивание).
Адам Розенфилд
220

В 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 ++ все еще делают isnan()макрос C99 доступным для 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.

mloskot
источник
21
Всем нужны такие функции, как isNan или isInfinity. Почему ответственные лица не просто включают в свои стандарты ???? - Я постараюсь выяснить, как взять на себя ответственность и поставить свой голос за это. Шутки в сторону.
Шухало
8
@shuhalo Ответственный еще?
Томаш Зато - Восстановить Монику
11
Этот ответ должен быть обновлен, поскольку std::isnanтеперь он является частью стандарта C ++ 11, и его поддержка уже распространена. std :: isnan был реализован в Visual Studio начиная с Visual Studio 2013. Возможно, @shuhalo стал ответственным за :-)
aberaud
170

Первое решение: если вы используете 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)

Определяет, является ли данный аргумент числа с плавающей точкой ar-not-a-number ( 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должен быть float, double или long double.

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

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

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

BlueTrin
источник
8
Определенно заслуживает того, чтобы быть принятым ответом и заслуживает большего количества голосов. Спасибо за совет
LBes
3
-1 std::isnan по-прежнему плохая рекомендация по состоянию на февраль 2017 года, поскольку он не работает с оптимизацией с плавающей запятой g ++.
ура и hth. - Альф
@ Cheersandhth.-Alf: совместим ли этот вариант с IEEE? Ответ отредактировал
BlueTrin
@BlueTrin: x != xи то, и другое isnanтребуется для соответствия требованиям IEEE 754. Что касается последнего, стандарт IEEE 754-2008 гласит, что «Реализации должны обеспечивать следующие не вычислительные операции для всех поддерживаемых арифметических форматов», а «isNaN (x) истинно тогда и только тогда, когда x является NaN». Для проверки соответствия, которого требует стандарт, is754version1985()и is754version2008(), где вместо этого предлагает C ++ std::numeric_limits<Fp>::is_iec559()(IEC 559 - тот же стандарт). К сожалению, с -ffast-mathоптимизацией, например, g ++ заявляет о соответствии, но не соответствует.
ура и hth. - Альф
1
Предупреждение: isnan (x) не работает с опцией -ffinite-math-only в gcc и clang
Туман
82

В 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, он имеет много полезных инструментов и быстро растет.

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

анонимное
источник
1
Спасибо! Как раз то, что я искал.
Доктор Ватсон,
он был добавлен в Boost 1.35 (я только что обнаружил, что моя программа не компилируется в старом дистрибутиве Linux).
Марчин
2
если вы компилируете с опцией --fast-math, то эта функция не будет работать должным образом.
Гаэтано Мендола
43

Существует три «официальных» способа: isnanмакрос posix , isnanшаблон функции c ++ 0x или визуальная _isnanфункция c ++ .

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

И, к сожалению, нет надежного способа определить, есть ли у вас представление 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

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

C: \ test> _
Ура и hth. Альф
источник
4
@Alf: Ваш пример работает для меня как на Mac OS X, так и на Linux на различных версиях g ++ между 4.0 и 4.5. В документации к этой -ffast-mathопции явно сказано, что она может привести к неправильному выводу для программ, которые зависят от точной реализации, если правила / спецификации IEEE или ISO для математических функций. Если эта опция не включена, использование x != xявляется вполне допустимым и переносимым способом тестирования для NaN.
Адам Розенфилд
6
@ Adam: Что вам не хватает, так это того, что стандарт C ++ не требует представления IEEE или математических операций для чисел с плавающей запятой. Насколько man-страница говорит вам, gcc -ffast-mathэто все еще соответствующая реализация C ++ (ну, если предположить, что это numeric_limits::is_iec559правильно, то, хотя Alf предполагает, что это не так): код C ++, полагающийся на IEEE, не является переносимым C ++ и не имеет права ожидать реализации, чтобы обеспечить это.
Стив Джессоп
5
И Альф прав, быстрый тест на gcc 4.3.4 и is_iec559верно с -ffast-math. Таким образом, проблема в том, что документы GCC -ffast-mathговорят только о том, что это не IEEE / ISO для математических функций, тогда как они должны сказать, что это не C ++, потому что его реализация numeric_limitsне работает. Я полагаю, что GCC не всегда может сказать, когда шаблон определен, имеет ли конечный бэкэнд соответствующие плавающие значения, и поэтому даже не пытается. IIRC есть похожие проблемы в списке выдающихся ошибок для соответствия GCC C99.
Стив Джессоп
1
@ Alf, @Steve, я не знал, что в стандарте C ++ нет спецификации для значений с плавающей запятой. Это довольно шокирует меня. Он выглядит лучше, обрабатывая IEEE 754 и NaN как расширение для конкретной платформы, а не как стандарт. Не так ли? И можно ли ожидать какой-либо isnan () или IEEE754, добавленный в C ++ 0x?
Эонил
3
@Eonil: C ++ 0x по-прежнему имеет, например, «Представление значений типов с плавающей точкой определяется реализацией». И C, и C ++ нацелены на поддержку реализаций на машинах без аппаратного обеспечения с плавающей запятой, и правильные значения с плавающей запятой IEEE 754 могут быть немного медленнее, чем разумно точные альтернативы. Теория заключается в том, что вы можете утверждать, is_iec559если вам нужен IEEE, на практике это не работает в GCC. В C ++ 0x есть isnanфункция, но, поскольку GCC is_iec559теперь не реализуется правильно , я полагаю, что в C ++ 0x ее тоже нет, и -ffast-mathвполне может нарушить ее isnan.
Стив Джессоп
39

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

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

bool custom_isnan(double var)
{
    volatile double d = var;
    return d != d;
}
СТТ
источник
6
почему не просто var! = var?
Брайан Р. Бонди
8
При этом у вас есть шанс, что компилятор оптимизирует сравнение, всегда возвращая true.
CTT
23
Нет, нет Компилятор, который делает это, не работает. Можно также сказать, что есть вероятность, что стандартная библиотека isnanвыдаст неверный результат. Технически это правда, компилятор может содержать ошибки, но на практике это не так. То же самое var != var. Это работает, потому что именно так определяются значения с плавающей точкой IEEE.
jalf
29
если -ffast-math установлен, isnan () не сможет вернуть правильный результат для gcc. Конечно, эта оптимизация задокументирована как нарушающая семантику IEEE ...
Мэтью Херрманн
Если -ffast-math установлен, то компилятор глючит. Вернее, если установлена ​​-ffast-math, все ставки выключены, и вы все равно не можете полагаться на NaN.
Адриан Ратнапала
25

Вы можете использовать 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.

Билл Ящерица
источник
2
Однако будьте внимательны - в numeric_limits в GCC версии 3.2.3, похоже, есть ошибка, поскольку она возвращает 0.0 для quiet_NaN. Более поздние версии GCC, по моему опыту, в порядке.
Натан Китчен
@ Натан: Полезно знать. Я пользуюсь версией 4.3.2, так что я в порядке.
Билл Ящерица
18

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

#include <cmath>

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

inline bool isnan(double x) {
    return x != x;
}
raimue
источник
Я использовал <cmath> и в нем нет isnan! кстати , я узнал, что этоisnan в <math.h>
HASEN
1
Как я уже сказал, это часть C99. Поскольку C99 не является частью какого-либо текущего стандарта C ++, я предоставил альтернативу. Но так как вполне вероятно, что isnan () будет включен в будущий стандарт C ++, я поместил вокруг него директиву #ifndef.
raimue
12

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

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

Ян
источник
Я считаю, что это также будет работать на платформах с прямым порядком байтов. Литерал 0x7fffffffпросто сидел бы в памяти как ff ff ff 7f. valueимеет тот же порядок 0x7f800000, что и у всех, так что все операции выстраиваются в ряд (обмен байтов отсутствует). Мне было бы интересно, если бы кто-то мог проверить это на платформе с прямым порядком байтов.
Брайан В. Вагнер
0x7fff1234также NaN. Так и есть0xffffffff
Стив Холлаш
12

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

Мой ответ на этот вопрос - не используйте обратные проверки для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значения имеют тенденцию приводить к трудностям поиска ошибок. (Обратите внимание, что это не относится к случаям infили -inf. ( -inf<0) возвращает TRUE, (0 < inf) возвращает ИСТИНА, и даже ( -inf< inf) возвращает ИСТИНА. Таким образом, по моему опыту, поведение кода часто остается желаемым).

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

То, с чем вы хотите столкнуться, 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.

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

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

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

bobobobo
источник
Спасибо за указание на это; подобное программирование определенно поможет решить проблему как таковую. Но в следующий раз постарайтесь не злоупотреблять функциями форматирования текста. Переключение размеров шрифта, веса и стиля делает его действительно трудным для чтения.
Магнус
4
Обратите внимание, что 0.0 / 0.0 - не единственная операция, которая может привести к NaN. Квадратный корень из отрицательного числа возвращает NaN. Косинус + бесконечности также возвращает NaN. операция acos (x), где x не находится в диапазоне [0, pi], также может привести к NaN. Короче говоря, нужно быть особенно осторожным, чтобы также взглянуть на эти потенциально опасные операции, а не только на 0,0 / 0,0.
Борис Дальштейн
Полностью согласен с Борисом. По моему опыту, NaN практически всегда происходил из чего-то вроде sqrt (-1.302e-53), то есть промежуточных результатов вычислений с близким к нулю, которые вводились в sqrt без проверки на отрицательность.
hans_meine
1
«Предотвращение NaN» означает, что вам нужно разбираться во всех основных арифметических операциях, а не только в деление. Вам нужно следить за ∞ / ∞, 0 * ∞, ∞% x, x% 0, ∞ - ∞, 0 ^ 0, ∞ ^ 0 и многими другими. Быть «превентивным» в таких базовых арифметических операциях означает, что вы полностью улучшите свою производительность (и, вероятно, пропустите дополнительные случаи, о которых вы не задумывались).
Стив Холлаш
11

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

Из этих способов только проверка битов представления числа работает надежно, как отмечено в моем первоначальном ответе. В частности, 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, ((значение == 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 | найти "++"
Оптимизирующий компилятор 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 (value)) = истинный успех
u = 3,14, (std :: isnan (значение)) = ложь Успех
w = inf, (std :: isnan (value)) = false Успех

v = nan, ((fpclassify (value) == 2)) = true Успех
u = 3.14, ((fpclassify (value) == 2)) = false Успешно
w = inf, ((fpclassify (value) == 2)) = 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 (значение) == 0x7fffffff)) = истинный успех
u = 3.14, ((ilogb (значение) == 0x7fffffff)) = false Успех
w = inf, ((ilogb (значение) == 0x7fffffff)) = true FAILED

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)]
> cl foo.cpp / Feb / fp: fast && b
foo.cpp
Заявление компилятора IEEE 754 = true

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

v = nan, ((fpclassify (value) == 2)) = true Успех
u = 3.14, ((fpclassify (value) == 2)) = false Успешно
w = inf, ((fpclassify (value) == 2)) = 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 (значение) == 0x7fffffff)) = истинный успех
u = 3.14, ((ilogb (значение) == 0x7fffffff)) = false Успех
w = inf, ((ilogb (значение) == 0x7fffffff)) = true FAILED

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)]
> _

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


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

Ура и hth. Альф
источник
7
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.

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

ST3
источник
Также обратите внимание, что оно ограничено представлением IEEE 754.
ура и hth. - Альф
Обратите внимание, что это приведение нарушает строгое правило псевдонимов g ++, и этот компилятор, как известно, выполняет Unmentionable Things ™, когда обнаруживает формальный UB. Вместо эффективного приведения, с g ++ вам нужно использовать memcpyбайтовый массив, чтобы быть уверенным. Код для этого в моем ответе № 2 .
ура и hth. - Альф
4

Возможное решение, которое не будет зависеть от конкретного представления 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) );
}
Дэн Натан
источник
С плавающей запятой одинарной точности имеет более 8 миллионов допустимых и различных битовых представлений для NaN, поэтому вам нужно добавить еще несколько сравнений. :)
Стив Холлаш
4

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

Jerramy
источник
Это на самом деле недостатки, так же, как f != fи недостатки. Я видел, как llvm оптимизирует почти идентичный фрагмент кода. Оптимизатор может распространить информацию о первом сравнении и выяснить, что второе сравнение никогда не может быть истинным, если первое является. (если компилятор строго подчиняется правилам IEEE, f != fто все равно намного проще)
Маркус
Не работает с -ffast-mathопцией g ++ . Работает с Visual C ++. Смотрите ( stackoverflow.com/a/42138465/464581 ).
ура и hth. - Альф
3

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

#ifndef isnan
  #define isnan(a) (a != a)
#endif
user1705817
источник
Это один из лучших ответов на этот вопрос! Спасибо, что поделились.
Анри Менке
2
Другие ответы указывают, что это может не сработать с установленной опцией -ffast-math.
Технофил
3

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

#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

EdW
источник
1

Мне кажется, что лучшим по-настоящему кроссплатформенным подходом было бы использовать объединение и проверить битовую комбинацию двойника для проверки на наличие 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;
}
Шелдон Юнкер
источник
Обратите внимание, что «это неопределенное поведение для чтения от члена союза, который не был написан совсем недавно». Так что использование a unionto type-pun между двумя типами может работать не так, как хотелось бы (: sad_panda :). Правильный (хотя на самом деле не настолько переносимый, как хотелось бы) способ состоит в том, чтобы полностью исключить объединение и создать memcpy из doubleдругой uint64_tпеременной, а затем выполнить тест с использованием этой вспомогательной переменной.
Eljay
0

На 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указано никаких параметров.

Максим Егорушкин
источник
-1

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

бибоп
источник
-3

Как указано выше, 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")

ZenJ
источник
2
Не приведет ли это к переполнению буфера, если a = 234324.0f?
Мазёд
Да, или 340282346638528859811704183484516925440.000если = FLT_MAX. Он должен будет использовать char s[7]; sprintf(s, "%.0g", a);, что будет 6 часов, если a=-FLT_MAX, или-3e+38
бобобо
-3

Это обнаруживает бесконечность, а также 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;
mathengineer
источник
Проверьте определение FLT_MIN, DBL_MINи LDBL_MINболее тщательно. Они определены как наименьшие нормализованные значения для каждого типа. Например, одинарная точность имеет более 8 миллионов допустимых значений в денормах, которые больше нуля и меньше FLT_MIN(и не являются NaN).
Стив Холлаш