Перевірка, чи є подвійний (або плаваючий) NaN в C ++


368

Чи є функція isnan ()?

PS: Я в MinGW (якщо це має значення).

У мене це було вирішено, використовуючи isnan () від <math.h>, якого не існує, у <cmath>якому я працював #includeспочатку.


2
Я не чистий, ви можете це зробити портативно. Хто каже, що C ++ вимагає IEEE754?
Девід Геффернан


Лише зауважте, 1 унція профілактики краще, ніж 1 фунт лікування. Іншими словами, запобігти виконанню 0.f / 0.f набагато краще, ніж зворотно перевірити наявність nanу вашому коді. nanЦе може бути дуже руйнівним для вашої програми, якщо дозволити її розповсюдження, це може ввести важкі помилки. Це тому nan, що токсичний, (5 * nan= nan), nanне дорівнює нічого ( nan! = nan), nanНе більший за все ( nan!> 0), nanне менший за все ( nan! <0).
bobobobo

1
@bobobobo: Ця функція дозволяє централізовану перевірку помилок. Так само, як винятки проти повернених значень.
Бен Войгт

2
Чому у <cmath> немає isnan ()? It in std ::
frankliuao

Відповіді:


349

Відповідно до стандарту IEEE, значення NaN мають непарну властивість, що порівняння, пов’язане з ними, завжди хибне. Тобто, для поплавця f f != fбуде істинно лише, якщо f - NaN.

Зауважте, що, як зазначалося в деяких коментарях нижче, не всі компілятори дотримуються цього при оптимізації коду.

Для будь-якого компілятора, який претендує на використання IEEE з плаваючою точкою, цей трюк повинен працювати. Але я не можу гарантувати, що це буде працювати на практиці. Якщо у вас виникли сумніви, зверніться до свого компілятора.


4
Компілятору краще не видаляти це, якщо він працює в режимі IEEE. Перевірте документацію свого компілятора, звичайно ...
dmckee --- кошеня колишнього модератора

38
-1 працює лише в теорії, а не на практиці: компілятори, такі як g ++ (з -fastmath), що вкручуються. єдиний загальний спосіб, поки c ++ 0x, - це перевірити наявність біттертеру.
ура та хт. - Альф

66
@Alf: Документація для цього -ffast-mathпараметра прямо говорить про те, що це може призвести до неправильного виведення програм, які залежать від точної реалізації, якщо правила / специфікації IEEE або ISO для математичних функцій. Без цієї опції ввімкнено використання x != xідеально вірного і портативного способу тестування для NaN.
Адам Розенфілд

7
@Adam: у документації відкрито зазначено, що вона не відповідає, так. і так, я раніше стикався з цим аргументом, довго обговорюючи це з Габріелем Дос Рейсом. це зазвичай використовується для захисту дизайну, в круговому аргументі (я не знаю, чи ви мали намір пов'язати це, але варто знати про це - це речі полум'я війни). ваш висновок, що x != xє дійсним без цього варіанту, не випливає логічно. це може бути правдою для певної версії g ++, чи ні. у будь-якому випадку, ви, як правило, не можете гарантувати, що опція fastmath не буде використана.
ура та хт. - Альф

7
@Alf: Ні, мені не було відомо про вашу дискусію з Габріелем Дос Рейсом. Стів Джессоп відзначився іншим питанням щодо припущення в 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.


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)

Визначає, чи задане число аргументу з плаваючою комою не є a-число ( 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.

Однак останній варіант може не працювати з кожним компілятором і деякими налаштуваннями (особливо налаштуваннями оптимізації), тому в крайньому випадку ви завжди можете перевірити біт-шаблон ...


8
Однозначно заслуговує на прийняту відповідь і заслуговує на більшу кількість нагород. Дякую за пораду
LBes

3
−1 std::isnan досі залишається недоброю рекомендацією з лютого 2017 року, оскільки не працює з оптимізацією плаваючої точки g ++.
ура та хт. - Альф

@ 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 ++, але невідповідна.
ура та хт. - Альф

1
Попередження: isnan (x) не працює з опцією -ffinite-math-only у gcc та clang
A Fog

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

Якщо у вас є час, то перегляньте весь інструментарій Math від Boost, він має багато корисних інструментів і швидко зростає.

Також при роботі з плаваючими і неплаваючими точками може бути непоганою ідеєю переглянути числові перетворення .


1
Дякую! Тільки те, що я шукав.
Доктор Ватсон,

він був доданий у Boost 1,35 (я щойно виявив, що моя програма не компілюється на старому Linux-дистрибутиві).
marcin

2
якщо ви компілюєте з параметром --fast-math, ця функція не буде працювати, як очікувалося.
Гаетано Мендола

43

Існує три "офіційних" способи: isnanмакрос posix , isnanшаблон функції 0x 0x або візуальна _isnanфункція c ++ .

На жаль, виявити, хто з них використовувати, недоцільно.

І, на жаль, немає надійного способу визначити, чи є у вас представлення IEEE 754 з NaNs. Стандартна бібліотека пропонує офіційний такий спосіб ( numeric_limits<double>::is_iec559). Але на практиці компілятори, такі як г ++ гвинт, що вгору.

Теоретично можна використовувати просто x != x, але компілятори, такі як g ++ та візуальний c ++ гвинт, що вгору.

Отже, врешті-решт, протестуйте на конкретні біт-шаблони NaN , передбачаючи (і, сподіваємось, у певний момент!) Певне представлення, таке як IEEE 754.


EDIT : розглянемо як приклад "компіляторів, таких як 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: \ програмні файли \ @commands \ gnuc.bat"
@rem -finput-charset = windows-1252
@ g ++ -O -пеністичний -std = c ++ 98 -Wall -Wwrite-рядки% * -Wno-long-long

C: \ test> gnuc x.cpp

C: \ test> && echo працює ... || ехо! не вдалося
працює ...

C: \ test> gnuc x.cpp - швидка математика

C: \ test> && echo працює ... || ехо! не вдалося
Затвердження не вдалося: a! = B, файл x.cpp, рядок 6

Ця програма попросила Runtime припинити її незвичним чином.
Для отримання додаткової інформації зверніться до служби підтримки програми.
! не вдалося

C: \ test> _

4
@Alf: Ваш приклад працює, як і очікувалося, для Mac OS X та Linux у різних версіях g ++ між 4.0 та 4.5. У документації до -ffast-mathопції прямо вказано, що це може призвести до неправильного виведення програм, які залежать від точної реалізації, якщо правила / специфікації IEEE або ISO для математичних функцій. Без цієї опції ввімкнено використання x != xідеально вірного і портативного способу тестування для NaN.
Адам Розенфілд

6
@Adam: Що вам не вистачає, це те, що стандарт C ++ не вимагає представлення IEEE або математики для плавців. Що стосується довідкової сторінки, gcc -ffast-mathце все ще відповідна реалізація C ++ (ну, якщо припустити, що це numeric_limits::is_iec559правильно, це є, хоча Альф пропонує вище, що це не так): C ++ код, що покладається на IEEE, не є портативним C ++ і не має права очікувати реалізації, щоб забезпечити його.
Стів Джессоп

5
І Альф правильний, швидкий тест на gcc 4.3.4 і is_iec559правда з -ffast-math. Отже, проблема полягає в тому, що документи GCC -ffast-mathговорять лише про те, що це не IEEE / ISO для математичних функцій, тоді як вони повинні говорити, що це не-C ++, оскільки його реалізація numeric_limitsє захищеною. Я б здогадувався, що GCC не завжди може сказати, що визначено шаблон, чи справді вхідний сервер має відповідні поплавці, і тому навіть не намагається. IIRC є подібні проблеми у списку помилок, що відповідають вимогам CCC щодо G99
Стів Джессоп

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
Зробивши це, це шанс, компілятор оптимізує порівняння, завжди повертаючи істину.
CTT

23
Ні, немає. Компілятор, який робить це, зламаний. Ви також можете сказати, що є ймовірність, що стандартна бібліотека isnanповерне неправильний результат. Технічно правда, компілятор міг би бути баггі, але на практиці - Not Gonna Happen. Те саме, що var != var. Він працює, тому що саме так визначаються значення плаваючої точки IEEE.
джельф

29
якщо встановлено -ffast-math, isnan () не зможе повернути правильний результат для gcc. Звичайно, ця оптимізація задокументована як порушення семантики IEEE ...
Меттью Геррман

Якщо встановлено -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 версії 3.2.3, здається, є помилка numeric_limits, оскільки вона повертає 0,0 для silent_NaN. Пізніші версії GCC, на мій досвід, добре.
Кухня Натана

@Nathan: Добре знати. Я використовую версію 4.3.2, тому я добре вийшов з лісу.
Білл Ящірка

18

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

#include <cmath>

Оскільки ця функція є частиною C99, вона доступна не скрізь. Якщо ваш постачальник не постачає функцію, ви також можете визначити свій варіант сумісності.

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

Я використовував <cmath> і в ньому немає існану! до речі , я дізнався, що цеisnan в <math.h>
HASEN

1
Як я вже сказав, це частина С99. Оскільки 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) повертає 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 Перевірки на nanvia x != xіноді недостовірні ( x != xїх знімають деякі оптимізуючі компілятори, які порушують відповідність IEEE, зокрема, коли -ffast-mathкомутатор увімкнено).


Дякуємо, що вказали на це; подібне програмування безумовно допоможе вирішити проблему як таку. Але наступного разу намагайтеся не надто зловживати функціями форматування тексту. Переміщення розмірів шрифту, ваги та подібного стилю робить їх дуже важким для читання.
Магнус

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
"Запобігання NaNs" означає, що вам потрібно потрапити всередину основних арифметичних операцій, а не лише поділу. Вам потрібно буде спостерігати за ∞ / ∞, 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-ness, як для C ++ 14, є:

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

  • (fpclassify(value) == FP_NAN) )
    Страждає від тієї ж проблеми, що і std::isnan, тобто, не є надійною.

  • (value != value) )
    Рекомендується у багатьох відповідях на відповідь. Страждає від тієї ж проблеми, що і 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 = вірно

v = nan, (std :: isnan (значення)) = справжній Успіх
u = 3,14, (std :: isnan (значення)) = помилковий Успіх
w = inf, (std :: isnan (значення)) = хибний Успіх

v = nan, ((fpclassify (значення) == 0x0100)) = справжній Успіх
u = 3,14, ((fpclassify (значення) == 0x0100)) = хибний Успіх
w = inf, ((fpclassify (значення) == 0x0100)) = помилковий успіх

v = nan, ((value! = value)) = true Успіх
u = 3,14, ((значення! = значення)) = помилковий Успіх
w = inf, ((value! = value)) = хибний Успіх

v = nan, ((значення == Fp_info :: silent_NaN ())) = false FAILED
u = 3.14, ((значення == Fp_info :: silent_NaN ())) = помилковий Успіх
w = inf, ((значення == Fp_info :: silent_NaN ())) = помилковий Успіх

v = nan, ((ilogb (значення) == ((int) 0x80000000))) = справжній Успіх
u = 3.14, ((ilogb (значення) == ((int) 0x80000000))) = помилковий успіх
w = inf, ((ilogb (значення) == ((int) 0x80000000))) = хибний Успіх

v = nan, (не упорядковане (1,2345, значення)) = справжній Успіх
u = 3,14, (невпорядковано (1,2345, значення)) = хибний Успіх
w = inf, (невпорядкований (1,2345, значення)) = хибний Успіх

v = nan, (is_ieee754_nan (значення)) = справжній Успіх
u = 3,14, (is_ieee754_nan (значення)) = помилковий Успіх
w = inf, (is_ieee754_nan (значення)) = помилковий Успіх

[C: \ my \ forums \ so \ 282 (виявити NaN)]
> g ++ foo.cpp -фаст-математика && a
Компілятор стверджує, що IEEE 754 = вірно

v = nan, (std :: isnan (значення)) = false FAILED
u = 3,14, (std :: isnan (значення)) = помилковий Успіх
w = inf, (std :: isnan (значення)) = хибний Успіх

v = nan, ((fpclassify (значення) == 0x0100)) = false FAILED
u = 3,14, ((fpclassify (значення) == 0x0100)) = хибний Успіх
w = inf, ((fpclassify (значення) == 0x0100)) = помилковий успіх

v = nan, ((value! = value)) = false FAILED
u = 3,14, ((значення! = значення)) = помилковий Успіх
w = inf, ((value! = value)) = хибний Успіх

v = nan, ((значення == Fp_info :: silent_NaN ())) = справжній Успіх
u = 3,14, ((значення == Fp_info :: тихий_NaN ())) = справжній НЕПРАВНИЙ
w = inf, ((значення == Fp_info :: silent_NaN ())) = true FAILED

v = nan, ((ilogb (значення) == ((int) 0x80000000))) = справжній Успіх
u = 3.14, ((ilogb (значення) == ((int) 0x80000000))) = помилковий успіх
w = inf, ((ilogb (значення) == ((int) 0x80000000))) = хибний Успіх

v = nan, (невпорядковане (1,2345, значення)) = помилково НЕПРАВНО
u = 3,14, (невпорядковано (1,2345, значення)) = хибний Успіх
w = inf, (невпорядкований (1,2345, значення)) = хибний Успіх

v = nan, (is_ieee754_nan (значення)) = справжній Успіх
u = 3,14, (is_ieee754_nan (значення)) = помилковий Успіх
w = inf, (is_ieee754_nan (значення)) = помилковий Успіх

[C: \ my \ forums \ so \ 282 (виявити NaN)]
> _

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

[C: \ my \ forums \ so \ 282 (виявити NaN)]
> кл / нолого- 2> & 1 | знайти "++"
Microsoft (R) C / C ++ Оптимізація компілятора версія 19.00.23725 для x86

[C: \ my \ forums \ so \ 282 (виявити NaN)]
> cl foo.cpp / лютий && b
foo.cpp
Компілятор стверджує, що IEEE 754 = вірно

v = nan, (std :: isnan (значення)) = справжній Успіх
u = 3,14, (std :: isnan (значення)) = помилковий Успіх
w = inf, (std :: isnan (значення)) = хибний Успіх

v = nan, ((fpclassify (значення) == 2)) = справжній Успіх
u = 3.14, ((fpclassify (значення) == 2)) = хибний Успіх
w = inf, ((fpclassify (значення) == 2)) = хибний Успіх

v = nan, ((value! = value)) = true Успіх
u = 3,14, ((значення! = значення)) = помилковий Успіх
w = inf, ((value! = value)) = хибний Успіх

v = nan, ((значення == Fp_info :: silent_NaN ())) = false FAILED
u = 3.14, ((значення == Fp_info :: silent_NaN ())) = помилковий Успіх
w = inf, ((значення == Fp_info :: silent_NaN ())) = помилковий Успіх

v = nan, ((ilogb (значення) == 0x7fffffff)) = справжній успіх
u = 3.14, ((ilogb (значення) == 0x7fffffff)) = помилковий успіх
w = inf, ((ilogb (значення) == 0x7fffffff)) = справжній НЕПРАВНИЙ

v = nan, (не упорядковане (1,2345, значення)) = справжній Успіх
u = 3,14, (невпорядковано (1,2345, значення)) = хибний Успіх
w = inf, (невпорядкований (1,2345, значення)) = хибний Успіх

v = nan, (is_ieee754_nan (значення)) = справжній Успіх
u = 3,14, (is_ieee754_nan (значення)) = помилковий Успіх
w = inf, (is_ieee754_nan (значення)) = помилковий Успіх

[C: \ my \ forums \ so \ 282 (виявити NaN)]
> cl foo.cpp / лют / fp: швидко && b
foo.cpp
Компілятор стверджує, що IEEE 754 = вірно

v = nan, (std :: isnan (значення)) = справжній Успіх
u = 3,14, (std :: isnan (значення)) = помилковий Успіх
w = inf, (std :: isnan (значення)) = хибний Успіх

v = nan, ((fpclassify (значення) == 2)) = справжній Успіх
u = 3.14, ((fpclassify (значення) == 2)) = хибний Успіх
w = inf, ((fpclassify (значення) == 2)) = хибний Успіх

v = nan, ((value! = value)) = true Успіх
u = 3,14, ((значення! = значення)) = помилковий Успіх
w = inf, ((value! = value)) = хибний Успіх

v = nan, ((значення == Fp_info :: silent_NaN ())) = false FAILED
u = 3.14, ((значення == Fp_info :: silent_NaN ())) = помилковий Успіх
w = inf, ((значення == Fp_info :: silent_NaN ())) = помилковий Успіх

v = nan, ((ilogb (значення) == 0x7fffffff)) = справжній успіх
u = 3.14, ((ilogb (значення) == 0x7fffffff)) = помилковий успіх
w = inf, ((ilogb (значення) == 0x7fffffff)) = справжній НЕПРАВНИЙ

v = nan, (не упорядковане (1,2345, значення)) = справжній Успіх
u = 3,14, (невпорядковано (1,2345, значення)) = хибний Успіх
w = inf, (невпорядкований (1,2345, значення)) = хибний Успіх

v = nan, (is_ieee754_nan (значення)) = справжній Успіх
u = 3,14, (is_ieee754_nan (значення)) = помилковий Успіх
w = inf, (is_ieee754_nan (значення)) = помилковий Успіх

[C: \ my \ forums \ so \ 282 (виявити NaN)]
> _

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


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


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.

Під час роботи це лише порівняння, кастинги не займають часу. Він просто змінює конфігурацію прапорців порівняння, щоб перевірити рівність.


Також зауважте, вона обмежена представленням IEEE 754.
ура та хт. - Альф

Зауважте, що цей склад порушує суворе правило псевдоніму g ++, і цей компілятор, як відомо, робив нерозбірливі речі ™, коли виявляє офіційний UB. Замість ефективних ролях, g ++ вам потрібно використовувати memcpyчерез байтовий масив, щоб бути впевненим. Код цього у моїй відповіді №2 .
ура та хт. - Альф

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, тому ваш пробіг може відрізнятися.


Це насправді недоліки так само, f != fяк і недоліки. Я бачив, як llvm оптимізував майже однаковий фрагмент коду. Оптимізатор може поширювати інформацію про перше порівняння та з'ясувати, що друге порівняння ніколи не може бути правдивим, якщо перше. (якщо компілятор суворо дотримується правил IEEE, f != fвсе одно набагато простіше)
Маркус,

Не працює з -ffast-mathопцією g ++ . Працює з Visual C ++. Див. ( Stackoverflow.com/a/42138465/464581 ).
ура та хт. - Альф

3

Як на мене, рішення може бути макросом, щоб зробити його явно вбудованим і, таким чином, досить швидким. Він також працює для будь-якого типу поплавця. Він ґрунтується на тому, що єдиний випадок, коли значення не дорівнює собі, це коли значення не є числом.

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

Це одна з найкращих відповідей на це питання! Дякую, що поділились.
Анрі Менке

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


1

Мені здається, що найкращим по-справжньому крос-платформним підходом було б використовувати об'єднання та протестувати бітовий малюнок подвійного, щоб перевірити наявність NaNs.

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

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

Зауважте, що "не визначена поведінка читати від члена профспілки, про який не було написано останнім часом". Тож використання цього unionтипу для набору пунктів між двома типами може не працювати як слід (: sad_panda :). Правильним (хоч насправді і не таким портативним, як бажано) було б взагалі уникнути об'єднання і зробити memcpy з doubleіншої uint64_tзмінної, а потім зробити тест, використовуючи цю змінну helper.
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. Подвійний - 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")


2
Чи не спричинить це переповнення буфера, якщо a = 234324.0f?
Мазюд

Так, або 340282346638528859811704183484516925440.000якщо a = FLT_MAX. Йому доведеться використовувати char s[7]; sprintf(s, "%.0g", a);, що буде 6 годин, якщо a=-FLT_MAX, або-3e+38
bobobobo

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

Перевірте визначення FLT_MIN, DBL_MINі LDBL_MINбільш ретельно. Вони визначаються як найменші нормовані значення для кожного типу. Наприклад, одна точність має понад 8 мільйонів законних значень денорми, які перевищують нуль і менше FLT_MIN(і не є NaN).
Стів Холлаш
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.