Просто обратите внимание, 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 с плавающей запятой, этот прием должен работать. Но я не могу гарантировать, что это будет работать на практике. Проверьте с вашим компилятором, если сомневаетесь.
Компилятору лучше не удалять это, если он работает в режиме 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.
Всем нужны такие функции, как 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
Обратите внимание, что это несовместимо с -fast-math, если вы используете g ++, другие предложения приведены ниже.
Другие решения: если вы используете инструменты, не совместимые с C ++ 11
Для C99 в C это реализовано как макрос, isnan(c)который возвращает значение типа int. Тип xдолжен быть float, double или long double.
Различные поставщики могут включать или не включать функцию isnan().
Предположительно переносимый способ проверки NaNсостоит в том, чтобы использовать свойство IEEE 754, NaNкоторое не равно самому себе: то есть x == xбудет ложным для xсуществования NaN.
Однако последний вариант может не работать с каждым компилятором и некоторыми настройками (в частности, настройками оптимизации), поэтому, в крайнем случае, вы всегда можете проверить битовую комбинацию ...
Определенно заслуживает того, чтобы быть принятым ответом и заслуживает большего количества голосов. Спасибо за совет
LBes
3
-1std::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 также есть библиотека только для заголовков, в которой есть удобные инструменты для работы с типами данных с плавающей запятой.
он был добавлен в 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;doubleconst nan1 =Info::quiet_NaN();doubleconst 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> _
@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){volatiledouble d = var;return d != d;}
При этом у вас есть шанс, что компилятор оптимизирует сравнение, всегда возвращая 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>usingnamespace 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;}return0;}
Я не знаю, работает ли это на всех платформах, так как я тестировал только с g ++ на Linux.
Однако будьте внимательны - в numeric_limits в GCC версии 3.2.3, похоже, есть ошибка, поскольку она возвращает 0.0 для quiet_NaN. Более поздние версии GCC, по моему опыту, в порядке.
Натан Китчен
@ Натан: Полезно знать. Я пользуюсь версией 4.3.2, так что я в порядке.
Билл Ящерица
18
Вы можете использовать isnan()функцию, но вам нужно включить математическую библиотеку C.
#include<cmath>
Поскольку эта функция является частью C99, она доступна не везде. Если ваш поставщик не предоставляет эту функцию, вы также можете определить свой собственный вариант совместимости.
Я использовал <cmath> и в нем нет isnan! кстати , я узнал, что этоisnan в <math.h>
HASEN
1
Как я уже сказал, это часть C99. Поскольку C99 не является частью какого-либо текущего стандарта C ++, я предоставил альтернативу. Но так как вполне вероятно, что isnan () будет включен в будущий стандарт C ++, я поместил вокруг него директиву #ifndef.
raimue
12
В следующем коде используется определение NAN (все установленные биты экспоненты, как минимум один набор дробных бит) и предполагается, что sizeof (int) = sizeof (float) = 4. Вы можете найти NAN в Википедии для получения подробной информации.
Я считаю, что это также будет работать на платформах с прямым порядком байтов. Литерал 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 possibleelse
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коммутатор включен).
Спасибо за указание на это; подобное программирование определенно поможет решить проблему как таковую. Но в следующий раз постарайтесь не злоупотреблять функциями форматирования текста. Переключение размеров шрифта, веса и стиля делает его действительно трудным для чтения.
Магнус
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-детектор.
Результаты с 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 ++ . Только прямое бит-тестирование работает надежно.
inlineboolIsNan(float f){constuint32 u =*(uint32*)&f;return(u&0x7F800000)==0x7F800000&&(u&0x7FFFFF);// Both NaN and qNan.}inlineboolIsNan(double d){constuint64 u =*(uint64*)&d;return(u&0x7FF0000000000000ULL)==0x7FF0000000000000ULL&&(u&0xFFFFFFFFFFFFFULL);}
Это работает, если sizeof(int)4 и sizeof(long long)8.
Во время выполнения это только сравнение, кастинги не занимают время. Он просто изменяет конфигурацию флагов сравнения, чтобы проверить равенство.
Также обратите внимание, что оно ограничено представлением 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;return0== 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то все равно намного проще)
Что касается меня, решение может быть макросом, чтобы сделать его явно встроенным и, следовательно, достаточно быстрым. Это также работает для любого типа поплавка. Он основан на том факте, что единственный случай, когда значение не равно себе, - это когда значение не является числом.
Мне кажется, что лучшим по-настоящему кроссплатформенным подходом было бы использовать объединение и проверить битовую комбинацию двойника для проверки на наличие NaN.
Я не полностью протестировал это решение, и может быть более эффективный способ работы с битовыми шаблонами, но я думаю, что оно должно работать.
#include<stdint.h>#include<stdio.h>unionNaN{uint64_t bits;double num;};int main(){//Test if a double is NaNdouble d =0.0/0.0;unionNaN n;
n.num = d;if((n.bits |0x800FFFFFFFFFFFFF)==0xFFFFFFFFFFFFFFFF){
printf("NaN: %f", d);}return0;}
Обратите внимание, что «это неопределенное поведение для чтения от члена союза, который не был написан совсем недавно». Так что использование 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&) не требуется:
staticinlineuint64_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;}staticinlineuint32_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;}constexpruint64_t inf_double_shl1 = UINT64_C(0xffe0000000000000);constexpruint32_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.staticinlinebool isnan2(double a){return load_ieee754_rep(a)<<1> inf_double_shl1;}staticinlinebool isinf2(double a){return load_ieee754_rep(a)<<1== inf_double_shl1;}staticinlinebool isfinite2(double a){return load_ieee754_rep(a)<<1< inf_double_shl1;}staticinlinebool isnan2(float a){return load_ieee754_rep(a)<<1> inf_float_shl1;}staticinlinebool isinf2(float a){return load_ieee754_rep(a)<<1== inf_float_shl1;}staticinlinebool 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константы кодируются как непосредственные операнды в инструкции по сборке.
Использует тот факт, что ucomisdинструкция устанавливает флаг четности, если любой аргумент равен NaN. Вот как std::isnanработает, когда не -ffast-mathуказано никаких параметров.
Стандарт IEEE гласит, что когда показатель степени равен всем 1s, а мантисса не равна нулю, число равно a NaN. Double - 1знаковый бит, 11биты экспоненты и биты 52мантиссы. Сделайте немного проверки.
Как указано выше, a! = A не будет работать в g ++ и некоторых других компиляторах, но этот прием должен. Это может быть не так эффективно, но это все же способ:
По сути, в g ++ (хотя я не уверен в других) printf печатает 'nan' в форматах% d или% .f, если переменная не является допустимым целым числом / float. Поэтому этот код проверяет, является ли первый символ строки 'n' (как в "nan")
Не приведет ли это к переполнению буфера, если 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;
Проверьте определение FLT_MIN, DBL_MINи LDBL_MINболее тщательно. Они определены как наименьшие нормализованные значения для каждого типа. Например, одинарная точность имеет более 8 миллионов допустимых значений в денормах, которые больше нуля и меньше FLT_MIN(и не являются NaN).
nan
в вашем коде.nan
Это может быть ужасно разрушительным для вашей программы, если ее распространять, то это может привести к трудностям в поиске ошибок. Это потому, чтоnan
ядовито, (5 *nan
=nan
),nan
не равно чему-либо (nan
! =nan
),nan
Не больше чем что-либо (nan
!> 0),nan
не меньше чем что-либо (nan
! <0).Ответы:
Согласно стандарту IEEE, значения NaN имеют странное свойство, заключающееся в том, что сравнения с ними всегда ложны. То есть для числа с плавающей запятой f
f != f
будет истинно, только если f равно NaN.Обратите внимание, что, как отмечалось в некоторых комментариях ниже, не все компиляторы учитывают это при оптимизации кода.
Для любого компилятора, который утверждает, что использует IEEE с плавающей запятой, этот прием должен работать. Но я не могу гарантировать, что это будет работать на практике. Проверьте с вашим компилятором, если сомневаетесь.
источник
-ffast-math
опции явно сказано, что это может привести к неправильному выводу для программ, которые зависят от точной реализации, если правила / спецификации IEEE или ISO для математических функций. Если эта опция не включена, использованиеx != x
является вполне допустимым и переносимым способом тестирования для NaN.x != x
действителен без этой опции, не следует логически. это может быть правдой для конкретной версии g ++ или нет. В любом случае, у вас нет возможности гарантировать, что опция fastmath не будет использоваться.-ffast-math
опции), тоx != x
это допустимое и переносимое решение. Вы даже можете проверить это-ffast-math
, протестировав__FAST_MATH__
макрос и переключившись на другую реализацию в этом случае (например, использовать объединения и битовое перемешивание).В
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.источник
std::isnan
теперь он является частью стандарта C ++ 11, и его поддержка уже распространена. std :: isnan был реализован в Visual Studio начиная с Visual Studio 2013. Возможно, @shuhalo стал ответственным за :-)Первое решение: если вы используете C ++ 11
После того, как об этом спросили, появилось немного новых разработок: важно знать, что
std::isnan()
это часть C ++ 11конспект
Определено в заголовке
<cmath>
Определяет, является ли данный аргумент числа с плавающей точкой 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
.Однако последний вариант может не работать с каждым компилятором и некоторыми настройками (в частности, настройками оптимизации), поэтому, в крайнем случае, вы всегда можете проверить битовую комбинацию ...
источник
std::isnan
по-прежнему плохая рекомендация по состоянию на февраль 2017 года, поскольку он не работает с оптимизацией с плавающей запятой g ++.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 ++ заявляет о соответствии, но не соответствует.В Boost также есть библиотека только для заголовков, в которой есть удобные инструменты для работы с типами данных с плавающей запятой.
Вы получаете следующие функции:
Если у вас есть время, взгляните на весь математический инструментарий от Boost, он имеет много полезных инструментов и быстро растет.
Также при работе с плавающими и не плавающими точками было бы неплохо взглянуть на Числовые преобразования .
источник
Существует три «официальных» способа:
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 ++ ... напортачить", рассмотрим
Компиляция с g ++ (TDM-2 mingw32) 4.4.1:
источник
-ffast-math
опции явно сказано, что она может привести к неправильному выводу для программ, которые зависят от точной реализации, если правила / спецификации IEEE или ISO для математических функций. Если эта опция не включена, использованиеx != x
является вполне допустимым и переносимым способом тестирования для NaN.gcc -ffast-math
это все еще соответствующая реализация C ++ (ну, если предположить, что этоnumeric_limits::is_iec559
правильно, то, хотя Alf предполагает, что это не так): код C ++, полагающийся на IEEE, не является переносимым C ++ и не имеет права ожидать реализации, чтобы обеспечить это.is_iec559
верно с-ffast-math
. Таким образом, проблема в том, что документы GCC-ffast-math
говорят только о том, что это не IEEE / ISO для математических функций, тогда как они должны сказать, что это не C ++, потому что его реализацияnumeric_limits
не работает. Я полагаю, что GCC не всегда может сказать, когда шаблон определен, имеет ли конечный бэкэнд соответствующие плавающие значения, и поэтому даже не пытается. IIRC есть похожие проблемы в списке выдающихся ошибок для соответствия GCC C99.is_iec559
если вам нужен IEEE, на практике это не работает в GCC. В C ++ 0x естьisnan
функция, но, поскольку GCCis_iec559
теперь не реализуется правильно , я полагаю, что в C ++ 0x ее тоже нет, и-ffast-math
вполне может нарушить ееisnan
.Существует std :: isnan, если ваш компилятор поддерживает расширения c99, но я не уверен, что mingw поддерживает.
Вот небольшая функция, которая должна работать, если ваш компилятор не имеет стандартной функции:
источник
isnan
выдаст неверный результат. Технически это правда, компилятор может содержать ошибки, но на практике это не так. То же самоеvar != var
. Это работает, потому что именно так определяются значения с плавающей точкой IEEE.Вы можете использовать
numeric_limits<float>::quiet_NaN( )
определенные вlimits
стандартной библиотеке для тестирования. Там определена отдельная константа дляdouble
.Я не знаю, работает ли это на всех платформах, так как я тестировал только с g ++ на Linux.
источник
Вы можете использовать
isnan()
функцию, но вам нужно включить математическую библиотеку C.Поскольку эта функция является частью C99, она доступна не везде. Если ваш поставщик не предоставляет эту функцию, вы также можете определить свой собственный вариант совместимости.
источник
isnan
в <math.h>В следующем коде используется определение 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
Нан профилактика
Мой ответ на этот вопрос - не используйте обратные проверки для
nan
. Вместо этого используйте превентивные проверки для разделения формы0.0/0.0
.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
вместо этого. Так,Таким образом, в приведенном выше случае, если бы x было
0.f
,inf
получилось бы (что имеет довольно хорошее / неразрушающее поведение, как упомянуто выше на самом деле).Помните, целочисленное деление на 0 вызывает исключение времени выполнения . Поэтому вы всегда должны проверять целочисленное деление на 0. То, что значение «
0.0/0.0
тихо» оцениваетnan
, не означает, что вы можете быть ленивым и не проверять,0.0/0.0
прежде чем это произойдет.1 Проверки на
nan
viax != x
иногда ненадежны (x != x
отбрасываются некоторыми оптимизирующими компиляторами, которые нарушают соответствие IEEE, особенно когда-ffast-math
коммутатор включен).источник
Начиная с 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-детектор.Результаты с g ++ (еще раз отметим, что стандартным поведением
(value == Fp_info::quiet_NaN())
является то, что он не работает как NaN-детектор, здесь просто очень большой практический интерес):Результаты с Visual C ++:
Подводя итог вышеприведенным результатам, только прямое тестирование представления на битовом уровне с использованием
is_ieee754_nan
функции, определенной в этой тестовой программе, надежно работало во всех случаях как с g ++, так и с Visual C ++.Приложение:
После публикации вышеизложенного я узнал о еще одном возможном тесте на NaN, упомянутом в другом ответе здесь, а именно
((value < 0) == (value >= 0))
. Это оказалось нормально работать с Visual C ++, но не с-ffast-math
опцией g ++ . Только прямое бит-тестирование работает надежно.источник
Это работает, если
sizeof(int)
4 иsizeof(long long)
8.Во время выполнения это только сравнение, кастинги не занимают время. Он просто изменяет конфигурацию флагов сравнения, чтобы проверить равенство.
источник
memcpy
байтовый массив, чтобы быть уверенным. Код для этого в моем ответе № 2 .Возможное решение, которое не будет зависеть от конкретного представления IEEE для используемого NaN, будет следующим:
источник
Учитывая, что (x! = X) не всегда гарантируется для NaN (например, при использовании опции -ffast-math), я использовал:
Числа не могут быть одновременно <0 и> = 0, поэтому на самом деле эта проверка проходит, только если число не меньше, не больше или равно нулю. Который в основном не число вообще или NaN.
Вы также можете использовать это, если вы предпочитаете:
Я не уверен, как на это влияет -ffast-math, поэтому ваш пробег может отличаться.
источник
f != f
и недостатки. Я видел, как llvm оптимизирует почти идентичный фрагмент кода. Оптимизатор может распространить информацию о первом сравнении и выяснить, что второе сравнение никогда не может быть истинным, если первое является. (если компилятор строго подчиняется правилам IEEE,f != f
то все равно намного проще)-ffast-math
опцией g ++ . Работает с Visual C ++. Смотрите ( stackoverflow.com/a/42138465/464581 ).Что касается меня, решение может быть макросом, чтобы сделать его явно встроенным и, следовательно, достаточно быстрым. Это также работает для любого типа поплавка. Он основан на том факте, что единственный случай, когда значение не равно себе, - это когда значение не является числом.
источник
Это работает:
вывод: isnan
источник
Мне кажется, что лучшим по-настоящему кроссплатформенным подходом было бы использовать объединение и проверить битовую комбинацию двойника для проверки на наличие NaN.
Я не полностью протестировал это решение, и может быть более эффективный способ работы с битовыми шаблонами, но я думаю, что оно должно работать.
источник
union
to type-pun между двумя типами может работать не так, как хотелось бы (: sad_panda :). Правильный (хотя на самом деле не настолько переносимый, как хотелось бы) способ состоит в том, чтобы полностью исключить объединение и создать memcpy изdouble
другойuint64_t
переменной, а затем выполнить тест с использованием этой вспомогательной переменной.На x86-64 у вас могут быть очень быстрые методы проверки NaN и бесконечности, которые работают независимо от
-ffast-math
опции компилятора. (f != f
,std::isnan
,std::isinf
Всегда даютfalse
с-ffast-math
).Тестирование на NaN, бесконечность и конечные числа может быть легко выполнено, проверяя максимальный показатель степени. бесконечность - максимальный показатель с нулевой мантиссой, NaN - максимальный показатель и ненулевая мантисса. Экспонента сохраняется в следующих битах после самого верхнего знакового бита, так что мы можем просто сдвинуть влево, чтобы избавиться от знакового бита и сделать экспоненту самыми старшими битами, маскирование (
operator&
) не требуется:В
std
версииisinf
иisfinite
нагрузка 2double/float
константы из.data
сегмента и в худшем случае они могут привести к 2 кэшу данных промаха. Вышеприведенные версии не загружают никаких данных,inf_double_shl1
иinf_float_shl1
константы кодируются как непосредственные операнды в инструкции по сборке.Быстрее
isnan2
всего 2 инструкции по сборке:Использует тот факт, что
ucomisd
инструкция устанавливает флаг четности, если любой аргумент равен NaN. Вот какstd::isnan
работает, когда не-ffast-math
указано никаких параметров.источник
Стандарт IEEE гласит, что когда показатель степени равен всем
1
s, а мантисса не равна нулю, число равно aNaN
. Double -1
знаковый бит,11
биты экспоненты и биты52
мантиссы. Сделайте немного проверки.источник
Как указано выше, a! = A не будет работать в g ++ и некоторых других компиляторах, но этот прием должен. Это может быть не так эффективно, но это все же способ:
По сути, в g ++ (хотя я не уверен в других) printf печатает 'nan' в форматах% d или% .f, если переменная не является допустимым целым числом / float. Поэтому этот код проверяет, является ли первый символ строки 'n' (как в "nan")
источник
340282346638528859811704183484516925440.000
если =FLT_MAX
. Он должен будет использоватьchar s[7]; sprintf(s, "%.0g", a);
, что будет 6 часов, еслиa=-FLT_MAX
, или-3e+38
Это обнаруживает бесконечность, а также NaN в Visual Studio, проверяя, что оно в двойных пределах:
источник
FLT_MIN
,DBL_MIN
иLDBL_MIN
более тщательно. Они определены как наименьшие нормализованные значения для каждого типа. Например, одинарная точность имеет более 8 миллионов допустимых значений в денормах, которые больше нуля и меньшеFLT_MIN
(и не являются NaN).