Проверка, является ли double (или float) NaN в C++
Есть ли функция isnan()?
PS: я нахожусь в MinGW (если это имеет значение).
Я решил это с помощью isnan() из <math.h>
, которого нет в <cmath>
который я был #include
сначала.
21 ответ
Согласно стандарту IEEE, значения NaN имеют странное свойство, заключающееся в том, что сравнивающие их всегда ложны. То есть для поплавка е, f != f
будет истинным, только если f является NaN.
Обратите внимание, что, как отмечалось в некоторых комментариях ниже, не все компиляторы учитывают это при оптимизации кода.
Для любого компилятора, который утверждает, что использует IEEE с плавающей запятой, этот прием должен работать. Но я не могу гарантировать, что это будет работать на практике. Проверьте с вашим компилятором, если сомневаетесь.
Первое решение: если вы используете C++11
После того, как об этом спросили, появилось немного новых разработок: важно знать, что std::isnan()
является частью C++11
конспект
Определено в заголовке <cmath>
bool isnan( float arg ); (since C++11)
bool isnan( double arg ); (since C++11)
bool isnan( long double arg ); (since C++11)
Определяет, является ли данный аргумент числа с плавающей точкой не-число (NaN
).
параметры
arg
: значение с плавающей точкой
Возвращаемое значение
true
если аргумент NaN
, false
иначе
Ссылка
http://en.cppreference.com/w/cpp/numeric/math/isnan
Обратите внимание, что это несовместимо с -fast-math, если вы используете g++, другие предложения приведены ниже.
Другие решения: если вы используете инструменты, не совместимые с C++11
Для C99, в C это реализовано как макрос isnan(c)
который возвращает значение int. Тип x
должен быть плавающим, двойным или длинным двойным.
Различные поставщики могут включать или не включать функцию isnan()
,
Предположительно портативный способ проверки NaN
использовать свойство IEEE 754, которое NaN
не равно себе: т.е. x == x
будет ложным для x
являющийся NaN
,
Однако последний вариант может не работать с каждым компилятором и некоторыми настройками (в частности, настройками оптимизации), поэтому, в крайнем случае, вы всегда можете проверить битовую комбинацию...
Здесь нет isnan()
функция доступна в текущей стандартной библиотеке C++. Он был введен в C99 и определен как макрос, а не функция. Элементы стандартной библиотеки, определенные C99, не являются частью текущего стандарта C++ ISO/IEC 14882:1998 и не являются его обновлением ISO/IEC 14882:2003.
В 2005 году был предложен Технический отчет 1. TR1 обеспечивает совместимость с C99 для C++. Несмотря на то, что он никогда официально не принимался стать стандартом C++, многие (реализации GCC 4.0+ или Visual C++ 9.0+ C++ предоставляют функции TR1, все они или только некоторые (Visual C++ 9.0 не предоставляет математические функции C99),
Если TR1 доступен, то cmath
включает в себя элементы C99, такие как isnan()
, isfinite()
и т. д., но они определяются как функции, а не макросы, обычно в std::tr1::
пространства имен, хотя многие реализации (например, GCC 4+ в Linux или в XCode в Mac OS X 10.5+) внедряют их непосредственно в std::
, так std::isnan
хорошо определено.
Более того, некоторые реализации C++ все еще делают C99 isnan()
макрос, доступный для C++ (включен через cmath
или же math.h
), что может вызвать больше недоразумений, и разработчики могут предположить, что это стандартное поведение.
Замечание о Viusal C++, как уже упоминалось выше, в нем не предусмотрено std::isnan
ни std::tr1::isnan
, но он предоставляет функцию расширения, определенную как _isnan()
который был доступен начиная с Visual C++ 6.0
На XCode еще больше веселья. Как уже упоминалось, GCC 4+ определяет std::isnan
, Для более старых версий компилятора и библиотеки XCode, кажется (здесь уместно обсуждение), не было возможности проверить себя) определены две функции, __inline_isnand()
на Intel и __isnand()
на Power PC.
В Boost также есть библиотека только для заголовков, в которой есть удобные инструменты для работы с типами данных с плавающей запятой.
#include <boost/math/special_functions/fpclassify.hpp>
Вы получаете следующие функции:
template <class T> bool isfinite(T z);
template <class T> bool isinf(T t);
template <class T> bool isnan(T t);
template <class T> bool isnormal(T t);
Если у вас есть время, взгляните на весь математический инструментарий от Boost, он имеет много полезных инструментов и быстро растет.
Также при работе с плавающими и не плавающими точками было бы неплохо взглянуть на числовые преобразования.
Есть три "официальных" способа: posix isnan
макрос, с ++0x isnan
шаблон функции или Visual C++ _isnan
функция
К сожалению, определить, какой из них использовать, довольно непрактично.
И, к сожалению, нет надежного способа определить, есть ли у вас представление IEEE 754 с NaN. Стандартная библиотека предлагает официальный способ (numeric_limits<double>::is_iec559
). Но на практике компиляторы, такие как g ++, облажались.
В теории можно было бы просто использовать x != x
, но компиляторы, такие как g ++ и visual C++, все испортили.
Итак, в конце, протестируйте для конкретных битовых шаблонов NaN, предполагая (и, надеюсь, принудительно применяя, в какой-то момент!) Конкретное представление, такое как IEEE 754.
РЕДАКТИРОВАТЬ: в качестве примера "компиляторы, такие как g ++... напортачить", рассмотрим
#include <limits>
#include <assert.h>
void foo( double a, double b )
{
assert( a != b );
}
int main()
{
typedef std::numeric_limits<double> Info;
double const nan1 = Info::quiet_NaN();
double const nan2 = Info::quiet_NaN();
foo( nan1, nan2 );
}
Компиляция с g ++ (TDM-2 mingw32) 4.4.1:
C: \ test> введите "C:\Program Files\@commands\gnuc.bat" @rem -finput-charset=windows-1252 @g++ -O -pedantic -std= C++98 -Wall -Write-strings% * -Wno-long-long C: \ test> gnuc x.cpp C: \ test> && echo работает... || эхо! не удалось работает... C: \ test> gnuc x.cpp - fast-math C: \ test> && echo работает... || эхо! не удалось Ошибка подтверждения: a!= B, файл x.cpp, строка 6 Это приложение запросило среду выполнения прекратить его необычным способом. Пожалуйста, обратитесь в службу поддержки приложения для получения дополнительной информации.!не удалось C:\test> _
Существует std::isnan, если ваш компилятор поддерживает расширения c99, но я не уверен, что mingw поддерживает.
Вот небольшая функция, которая должна работать, если ваш компилятор не имеет стандартной функции:
bool custom_isnan(double var)
{
volatile double d = var;
return d != d;
}
Ты можешь использовать numeric_limits<float>::quiet_NaN( )
определены в limits
стандартная библиотека для тестирования. Там есть отдельная константа, определенная для double
,
#include <iostream>
#include <math.h>
#include <limits>
using namespace std;
int main( )
{
cout << "The quiet NaN for type float is: "
<< numeric_limits<float>::quiet_NaN( )
<< endl;
float f_nan = numeric_limits<float>::quiet_NaN();
if( isnan(f_nan) )
{
cout << "Float was Not a Number: " << f_nan << endl;
}
return 0;
}
Я не знаю, работает ли это на всех платформах, так как я тестировал только с g++ на Linux.
Вы можете использовать isnan()
функция, но вам нужно включить математическую библиотеку C.
#include <cmath>
Поскольку эта функция является частью C99, она доступна не везде. Если ваш поставщик не предоставляет функцию, вы также можете определить свой собственный вариант совместимости.
inline bool isnan(double x) {
return x != x;
}
Начиная с C++14 существует несколько способов проверить, является ли число с плавающей запятой value
это NaN.
Из этих способов надежно работает только проверка битов представления числа, как отмечено в моем первоначальном ответе. Особенно, std::isnan
и часто предлагаемая проверка v != v
не работайте надежно и не должны использоваться, иначе ваш код перестанет работать правильно, когда кто-то решит, что необходима оптимизация с плавающей запятой, и попросит компилятор сделать это. Эта ситуация может измениться, компиляторы могут получить больше соответствия, но для этой проблемы, которая не возникала в течение 6 лет с момента первоначального ответа.
В течение примерно 6 лет моим первоначальным ответом было выбранное решение для этого вопроса, которое было в порядке. Но в последнее время высоко одобренный ответ, рекомендующий ненадежный v != v
тест был выбран. Отсюда и этот дополнительный более актуальный ответ (теперь у нас есть стандарты C++11 и C++14 и C++17 на горизонте).
Основными способами проверки на NaN-ность, начиная с C++14, являются:
std::isnan(value) )
это стандартный библиотечный путь начиная с C++11.isnan
очевидно, конфликтует с макросом Posix с тем же именем, но на практике это не проблема. Основная проблема заключается в том, что когда запрашивается арифметическая оптимизация с плавающей запятой, то по крайней мере с одним основным компилятором, а именно g++,std::isnan
возвращаетсяfalse
для аргумента NaN.(fpclassify(value) == FP_NAN) )
Страдает от той же проблемы, что иstd::isnan
не надежен.(value != value) )
Рекомендуется во многих SO ответах. Страдает от той же проблемы, что иstd::isnan
не надежен.(value == Fp_info::quiet_NaN()) )
Это тест, который при стандартном поведении не должен обнаруживать NaN, но при оптимизированном поведении может обнаруживать NaN (благодаря оптимизированному коду, просто сравнивающему представления уровня битов напрямую) и, возможно, объединяется с другим способом охвата стандартного неоптимизированного поведения., мог надежно обнаружить NaN. К сожалению, оказалось, что не работает надежно.(ilogb(value) == FP_ILOGBNAN) )
Страдает от той же проблемы, что иstd::isnan
не надежен.isunordered(1.2345, value) )
Страдает от той же проблемы, что иstd::isnan
не надежен.is_ieee754_nan( value ) )
Это не стандартная функция. Это проверка битов в соответствии со стандартом IEEE 754. Это абсолютно надежно, но код в некоторой степени зависит от системы.
В следующем полном тестовом коде "успех" означает, что выражение сообщает о значении значения. Для большинства выражений эта мера успеха, цель обнаружения NaN и только NaN, соответствует их стандартной семантике. Для (value == Fp_info::quiet_NaN()) )
Выражение, однако, стандартное поведение заключается в том, что он не работает как NaN-детектор.
#include <cmath> // std::isnan, std::fpclassify
#include <iostream>
#include <iomanip> // std::setw
#include <limits>
#include <limits.h> // CHAR_BIT
#include <sstream>
#include <stdint.h> // uint64_t
using namespace std;
#define TEST( x, expr, expected ) \
[&](){ \
const auto value = x; \
const bool result = expr; \
ostringstream stream; \
stream << boolalpha << #x " = " << x << ", (" #expr ") = " << result; \
cout \
<< setw( 60 ) << stream.str() << " " \
<< (result == expected? "Success" : "FAILED") \
<< endl; \
}()
#define TEST_ALL_VARIABLES( expression ) \
TEST( v, expression, true ); \
TEST( u, expression, false ); \
TEST( w, expression, false )
using Fp_info = numeric_limits<double>;
inline auto is_ieee754_nan( double const x )
-> bool
{
static constexpr bool is_claimed_ieee754 = Fp_info::is_iec559;
static constexpr int n_bits_per_byte = CHAR_BIT;
using Byte = unsigned char;
static_assert( is_claimed_ieee754, "!" );
static_assert( n_bits_per_byte == 8, "!" );
static_assert( sizeof( x ) == sizeof( uint64_t ), "!" );
#ifdef _MSC_VER
uint64_t const bits = reinterpret_cast<uint64_t const&>( x );
#else
Byte bytes[sizeof(x)];
memcpy( bytes, &x, sizeof( x ) );
uint64_t int_value;
memcpy( &int_value, bytes, sizeof( x ) );
uint64_t const& bits = int_value;
#endif
static constexpr uint64_t sign_mask = 0x8000000000000000;
static constexpr uint64_t exp_mask = 0x7FF0000000000000;
static constexpr uint64_t mantissa_mask = 0x000FFFFFFFFFFFFF;
(void) sign_mask;
return (bits & exp_mask) == exp_mask and (bits & mantissa_mask) != 0;
}
auto main()
-> int
{
double const v = Fp_info::quiet_NaN();
double const u = 3.14;
double const w = Fp_info::infinity();
cout << boolalpha << left;
cout << "Compiler claims IEEE 754 = " << Fp_info::is_iec559 << endl;
cout << endl;;
TEST_ALL_VARIABLES( std::isnan(value) ); cout << endl;
TEST_ALL_VARIABLES( (fpclassify(value) == FP_NAN) ); cout << endl;
TEST_ALL_VARIABLES( (value != value) ); cout << endl;
TEST_ALL_VARIABLES( (value == Fp_info::quiet_NaN()) ); cout << endl;
TEST_ALL_VARIABLES( (ilogb(value) == FP_ILOGBNAN) ); cout << endl;
TEST_ALL_VARIABLES( isunordered(1.2345, value) ); cout << endl;
TEST_ALL_VARIABLES( is_ieee754_nan( value ) );
}
Результаты с g++ (еще раз обратите внимание, что стандартное поведение (value == Fp_info::quiet_NaN())
в том, что он не работает как NaN-детектор, он просто очень интересен здесь):
[C:\my\forums\so\282 (обнаружение NaN)] > g ++ - версия | найти "++" g++ (x86_64-win32-sjlj-rev1, созданный проектом MinGW-W64) 6.3.0 [C:\my\forums\so\282 (обнаружение NaN)] > g ++ foo.cpp && a Заявление компилятора IEEE 754 = true v = nan, (std::isnan(value)) = истинный успех u = 3.14, (std::isnan(значение)) = ложь Успех w = inf, (std::isnan(value)) = false Успех v = nan, ((fpclassify(value) == 0x0100)) = true Успех u = 3.14, ((fpclassify(value) == 0x0100)) = false Успех w = inf, ((fpclassify(value) == 0x0100)) = false Успех v = nan, ((значение!= значение)) = истинный успех и = 3.14, ((значение!= значение)) = ложь Успех w = inf, ((значение!= значение)) = ложь Успех v = nan, ((значение == Fp_info::quiet_NaN())) = false FAILED u = 3.14, ((значение == Fp_info:: quiet_NaN ())) = false Успешно w = inf, ((значение == Fp_info:: quiet_NaN ())) = false Успех v = nan, ((ilogb(значение) == ((int)0x80000000))) = true Успех u = 3.14, ((ilogb(значение) == ((int)0x80000000))) = false Успех w = inf, ((ilogb(значение) == ((int)0x80000000))) = false Успех v = nan, (isorordered(1.2345, value)) = истинный успех u = 3,14, (неупорядочено (1,2345, значение)) = ложь Успех w = inf, (isorordered(1.2345, value)) = false Успех v = nan, (is_ieee754_nan( value)) = истинный успех u = 3,14, (is_ieee754_nan(значение)) = ложь Успех w = inf, (is_ieee754_nan( value)) = false Успех [C:\my\forums\so\282 (обнаружение NaN)] > g ++ foo.cpp -ffast-math && a Заявление компилятора IEEE 754 = true v = nan, (std::isnan(value)) = false FAILED u = 3.14, (std::isnan(значение)) = ложь Успех w = inf, (std::isnan(value)) = false Успех v = nan, ((fpclassify(value) == 0x0100)) = false FAILED u = 3.14, ((fpclassify(value) == 0x0100)) = false Успех w = inf, ((fpclassify(value) == 0x0100)) = false Успех v = nan, ((value!= value)) = false FAILED и = 3.14, ((значение!= значение)) = ложь Успех w = inf, ((значение!= значение)) = ложь Успех v = nan, ((value == Fp_info::quiet_NaN())) = истинный успех u = 3.14, ((значение == Fp_info::quiet_NaN())) = true FAILED w = inf, ((значение == Fp_info::quiet_NaN())) = true FAILED v = nan, ((ilogb(значение) == ((int)0x80000000))) = true Успех u = 3.14, ((ilogb(значение) == ((int)0x80000000))) = false Успех w = inf, ((ilogb(значение) == ((int)0x80000000))) = false Успех v = nan, (isorordered(1.2345, value)) = false FAILED u = 3,14, (неупорядочено (1,2345, значение)) = ложь Успех w = inf, (isorordered(1.2345, value)) = false Успех v = nan, (is_ieee754_nan( value)) = истинный успех u = 3,14, (is_ieee754_nan(значение)) = ложь Успех w = inf, (is_ieee754_nan( value)) = false Успех [C:\my\forums\so\282 (обнаружение NaN)] > _
Результаты с Visual C++:
[C:\my\forums\so\282 (обнаружение NaN)]> cl / nologo- 2> & 1 | find "++" Оптимизирующий компилятор Microsoft (R) C/C++ версии 19.00.23725 для x86 [C:\my\forums\so\282 (обнаружение NaN)]> cl foo.cpp / Feb && b foo.cpp Заявления компилятора IEEE 754 = true v = nan, (std:: isnan (значение)) = true Успех u = 3.14, (std::isnan(значение)) = false Успех w = inf, (std:: isnan (значение)) = false Успех v = nan, ((fpclassify(value) == 2)) = true Успех u = 3.14, ((fpclassify(value) == 2)) = false Успех w = inf, ((fpclassify(value) == 2)) = false Успех v = nan, ((значение!= Значение)) = true Успех u = 3.14, ((значение!= Значение)) = false Успех w = inf, ((значение!= Значение)) = false Success v = nan, ((значение == Fp_info::quiet_NaN())) = false FAILED u = 3.14, ((value == Fp_info::quiet_NaN())) = false Success w = inf, ((value == Fp_info::quiet_NaN())) = false Успех v = nan, ((ilogb(значение) == 0x7fffffff)) = true Успех u = 3.14, ((ilogb(значение) == 0x7fffffff)) = false Успех w = inf, ((ilogb(значение) == 0x7fffffff)) = истинно НЕ УДАЛЕН v = nan, (неупорядочено (1.2345, значение)) = верно Успех u = 3.14, (isunordered(1.2345, значение)) = false Успех w = inf, (isorordered(1.2345, value)) = false Успех v = nan, (is_ieee754_nan( value)) = true Успех u = 3.14, (is_ieee754_nan(значение)) = false Успех w = inf, (is_ieee754_nan (значение)) = false Успех [C:\my\forums\so\282 (обнаружение NaN)]> cl foo.cpp / Feb / fp: fast && b foo.cpp Заявления компилятора IEEE 754 = true v = nan, (std:: isnan (значение)) = true Успех u = 3.14, (std::isnan(значение)) = false Успех w = inf, (std:: isnan (значение)) = false Успех v = nan, ((fpclassify(value) == 2)) = true Успех u = 3.14, ((fpclassify(value) == 2)) = false Успех w = inf, ((fpclassify(value) == 2)) = false Успех v = nan, ((значение!= Значение)) = true Успех u = 3.14, ((значение!= Значение)) = false Успех w = inf, ((значение!= Значение)) = ложный успех v = nan, ((значение == Fp_info:: quiet_NaN ())) = ложный СБОЙ u = 3.14, ((значение == Fp_info:: quiet_NaN ())) = ложный успех w = inf, ((значение == Fp_info:: quiet_NaN ())) = false Успех v = nan, ((ilogb(значение) == 0x7ffffff f)) = true Успех u = 3.14, ((ilogb(значение) == 0x7fffffff)) = false Успех w = inf, ((ilogb(значение) == 0x7fffffff)) = true FAILED v = nan, (isorordered (1.2345, значение)) = true Успех u = 3.14, (isorordered(1.2345, value)) = false Успех w = inf, (isorordered(1.2345, value)) = false Успех v = nan, (is_ieee754_nan( value)) = true Успех u = 3.14, (is_ieee754_nan(значение)) = false Успех w = inf, (is_ieee754_nan (значение)) = false Успех [C:\my\forums\so\282 (обнаружение NaN)]> _ _
Подводя итог вышеприведенным результатам, только прямое тестирование представления на битовом уровне, используя is_ieee754_nan
Функция, определенная в этой тестовой программе, надежно работала во всех случаях как с g++, так и с Visual C++.
Приложение:
После публикации вышеизложенного мне стало известно о еще одном возможном тесте на NaN, упомянутом в другом ответе здесь, а именно ((value < 0) == (value >= 0))
, Это оказалось нормально работать с Visual C++, но не с g ++ -ffast-math
вариант. Надежно работает только прямое тестирование бит-паттернов.
Нан профилактика
Мой ответ на этот вопрос: не используйте обратные проверки для nan
, Используйте профилактические проверки для подразделений формы 0.0/0.0
вместо.
#include <float.h>
float x=0.f ; // I'm gonna divide by x!
if( !x ) // Wait! Let me check if x is 0
x = FLT_MIN ; // oh, since x was 0, i'll just make it really small instead.
float y = 0.f / x ; // whew, `nan` didn't appear.
nan
результаты операции 0.f/0.f
, или же 0.0/0.0
, nan
это ужасная неприятность для стабильности вашего кода, которая должна быть обнаружена и предотвращена очень осторожно 1. Свойства nan
которые отличаются от нормальных чисел:
nan
токсичен, (5*nan
знак равноnan
)nan
не равно ни чему, даже самому себе (nan
знак равноnan
)nan
не больше чем что-либо (nan
!> 0)nan
не меньше чем ничего (nan
! <0)
Последние 2 перечисленных свойства противоречивы и приведут к странному поведению кода, который основан на сравнении с nan
число (третье последнее свойство тоже странно, но вы, вероятно, никогда не увидите x != x ?
в вашем коде (если вы не проверяете NAN (ненадежно))).
В своем собственном коде я заметил, что nan
значения имеют тенденцию приводить к трудностям поиска ошибок. (Обратите внимание, что это не так для inf
или же -inf
, (-inf
<0) возвращает TRUE
, (0 < inf
) возвращает TRUE и даже (-inf
< inf
) возвращает TRUE. Таким образом, по моему опыту, поведение кода часто остается желаемым.
что делать под нан
То, что вы хотите случиться под 0.0/0.0
должен обрабатываться как особый случай, но то, что вы делаете, должно зависеть от чисел, которые вы ожидаете получить из кода.
В приведенном выше примере результат (0.f/FLT_MIN
) будет 0
, в принципе. Вы можете хотеть 0.0/0.0
чтобы генерировать HUGE
вместо. Так,
float x=0.f, y=0.f, z;
if( !x && !y ) // 0.f/0.f case
z = FLT_MAX ; // biggest float possible
else
z = y/x ; // regular division.
Таким образом, в приведенном выше, если х были 0.f
, inf
результат (который имеет довольно хорошее / неразрушающее поведение, как упомянуто выше на самом деле).
Помните, целочисленное деление на 0 вызывает исключение времени выполнения. Поэтому вы всегда должны проверять целочисленное деление на 0. Просто потому, что 0.0/0.0
спокойно оценивает nan
не означает, что вы можете быть ленивым и не проверять 0.0/0.0
прежде чем это произойдет.
1 Проверки для nan
с помощью x != x
иногда ненадежны ( x != x
удаляются некоторыми оптимизирующими компиляторами, которые нарушают соответствие IEEE, особенно когда -ffast-math
переключатель включен).
В следующем коде используется определение NAN (все установленные биты экспоненты, как минимум один набор дробных битов) и предполагается, что sizeof(int) = sizeof(float) = 4. Вы можете найти NAN в Википедии для получения подробной информации.
bool IsNan( float value )
{
return ((*(UINT*)&value) & 0x7fffffff) > 0x7f800000;
}
inline bool IsNan(float f)
{
const uint32 u = *(uint32*)&f;
return (u&0x7F800000) == 0x7F800000 && (u&0x7FFFFF); // Both NaN and qNan.
}
inline bool IsNan(double d)
{
const uint64 u = *(uint64*)&d;
return (u&0x7FF0000000000000ULL) == 0x7FF0000000000000ULL && (u&0xFFFFFFFFFFFFFULL);
}
Это работает, если sizeof(int)
4 и sizeof(long long)
это 8.
Во время выполнения это только сравнение, кастинги не занимают время. Он просто изменяет конфигурацию флагов сравнения, чтобы проверить равенство.
После прочтения других ответов я хотел что-то, что прошло бы через предупреждение сравнения с плавающей запятой и не сломалось бы при быстрой математике. Следующий код работает:
/*
Portable warning-free NaN test:
* Does not emit warning with -Wfloat-equal (does not use float comparisons)
* Works with -O3 -ffast-math (floating-point optimization)
* Only call to standard library is memset and memcmp via <cstring>
* Works for IEEE 754 compliant floating-point representations
* Also works for extended precision long double
*/
#include <cstring>
template <class T> bool isNaN(T x)
{
/*Initialize all bits including those used for alignment to zero. This sets
all the values to positive zero but does not clue fast math optimizations as
to the value of the variables.*/
T z[4];
memset(z, 0, sizeof(z));
z[1] = -z[0];
z[2] = x;
z[3] = z[0] / z[2];
/*Rationale for following test:
* x is 0 or -0 --> z[2] = 0, z[3] = NaN
* x is a negative or positive number --> z[3] = 0
* x is a negative or positive denormal number --> z[3] = 0
* x is negative or positive infinity --> z[3] = 0
(IEEE 754 guarantees that 0 / inf is zero)
* x is a NaN --> z[3] = NaN != 0.
*/
//Do a bitwise comparison test for positive and negative zero.
bool z2IsZero = memcmp(&z[2], &z[0], sizeof(T)) == 0 ||
memcmp(&z[2], &z[1], sizeof(T)) == 0;
bool z3IsZero = memcmp(&z[3], &z[0], sizeof(T)) == 0 ||
memcmp(&z[3], &z[1], sizeof(T)) == 0;
//If the input is bitwise zero or negative zero, then it is not NaN.
return !z2IsZero && !z3IsZero;
}
//NaN test suite
#include <iostream>
/*If printNaN is true then only expressions that are detected as NaN print and
vice versa.*/
template <class T> void test(bool printNaN)
{
T v[10] = {-0.0, 0.0, -1.0, 1.0,
std::numeric_limits<T>::infinity(),
-std::numeric_limits<T>::infinity(),
std::numeric_limits<T>::denorm_min(),
-std::numeric_limits<T>::denorm_min(),
std::numeric_limits<T>::quiet_NaN(),
std::numeric_limits<T>::signaling_NaN()};
for(int i = 0; i < 10; i++)
{
for(int j = 0; j < 10; j++)
{
if(isNaN(v[i] + v[j]) == printNaN)
std::cout << v[i] << "+" << v[j] << " = " << v[i] + v[j] << std::endl;
if(isNaN(v[i] - v[j]) == printNaN)
std::cout << v[i] << "-" << v[j] << " = " << v[i] - v[j] << std::endl;
if(isNaN(v[i] * v[j]) == printNaN)
std::cout << v[i] << "*" << v[j] << " = " << v[i] * v[j] << std::endl;
if(isNaN(v[i] / v[j]) == printNaN)
std::cout << v[i] << "/" << v[j] << " = " << v[i] / v[j] << std::endl;
}
}
}
//Test each floating-point type.
int main()
{
std::cout << "NaNs:" << std::endl;
test<float>(true);
test<double>(true);
test<long double>(true);
std::cout << std::endl << "Not NaNs:" << std::endl;
test<float>(false);
test<double>(false);
test<long double>(false);
return 0;
}
Возможное решение, которое не будет зависеть от конкретного представления IEEE для используемого NaN, будет следующим:
template<class T>
bool isnan( T f ) {
T _nan = (T)0.0/(T)0.0;
return 0 == memcmp( (void*)&f, (void*)&_nan, sizeof(T) );
}
Учитывая, что (x!= X) не всегда гарантируется для NaN (например, при использовании опции -ffast-math), я использовал:
#define IS_NAN(x) (((x) < 0) == ((x) >= 0))
Числа не могут быть одновременно < 0 и>= 0, поэтому в действительности эта проверка проходит только в том случае, если число не меньше, не больше или равно нулю. Который в основном не число вообще или NaN.
Вы также можете использовать это, если вы предпочитаете:
#define IS_NAN(x) (!((x)<0) && !((x)>=0)
Я не уверен, как на это влияет -ffast-math, поэтому ваш пробег может отличаться.
Что касается меня, решение может быть макросом, чтобы сделать его явно встроенным и, следовательно, достаточно быстрым. Это также работает для любого типа поплавка. Он основан на том факте, что единственный случай, когда значение не равно себе, - это когда значение не является числом.
#ifndef isnan
#define isnan(a) (a != a)
#endif
Это работает:
#include <iostream>
#include <math.h>
using namespace std;
int main ()
{
char ch='a';
double val = nan(&ch);
if(isnan(val))
cout << "isnan" << endl;
return 0;
}
вывод: isnan
На x86-64 у вас могут быть чрезвычайно быстрые методы проверки NaN и бесконечности, которые работают независимо от -ffast-math
вариант компилятора. (f != f
, std::isnan
, std::isinf
всегда уступать false
с -ffast-math
).
Проверка на NaN, бесконечность и конечные числа может быть легко выполнена путем проверки максимального показателя степени. бесконечность - максимальный показатель степени с нулевой мантиссой, NaN - максимальный показатель степени и ненулевой мантиссой. Показатель экспоненты сохраняется в следующих битах после самого верхнего знакового бита, поэтому мы можем просто сдвиг влево, чтобы избавиться от знакового бита и сделать экспоненту самыми верхними битами, без маскировки (operator&
) это необходимо:
static inline uint64_t load_ieee754_rep(double a) {
uint64_t r;
static_assert(sizeof r == sizeof a, "Unexpected sizes.");
std::memcpy(&r, &a, sizeof a); // Generates movq instruction.
return r;
}
static inline uint32_t load_ieee754_rep(float a) {
uint32_t r;
static_assert(sizeof r == sizeof a, "Unexpected sizes.");
std::memcpy(&r, &a, sizeof a); // Generates movd instruction.
return r;
}
constexpr uint64_t inf_double_shl1 = UINT64_C(0xffe0000000000000);
constexpr uint32_t inf_float_shl1 = UINT32_C(0xff000000);
// The shift left removes the sign bit. The exponent moves into the topmost bits,
// so that plain unsigned comparison is enough.
static inline bool isnan2(double a) { return load_ieee754_rep(a) << 1 > inf_double_shl1; }
static inline bool isinf2(double a) { return load_ieee754_rep(a) << 1 == inf_double_shl1; }
static inline bool isfinite2(double a) { return load_ieee754_rep(a) << 1 < inf_double_shl1; }
static inline bool isnan2(float a) { return load_ieee754_rep(a) << 1 > inf_float_shl1; }
static inline bool isinf2(float a) { return load_ieee754_rep(a) << 1 == inf_float_shl1; }
static inline bool isfinite2(float a) { return load_ieee754_rep(a) << 1 < inf_float_shl1; }
В std
версии isinf
а также isfinite
нагрузка 2 double/float
константы из .data
сегмент и в худшем случае они могут вызвать 2 промаха кэша данных. Вышеупомянутые версии не загружают никаких данных,inf_double_shl1
а также inf_float_shl1
константы кодируются как непосредственные операнды в инструкции сборки.
Быстрее isnan2
всего 2 инструкции по сборке:
bool isnan2(double a) {
bool r;
asm(".intel_syntax noprefix"
"\n\t ucomisd %1, %1"
"\n\t setp %b0"
"\n\t .att_syntax prefix"
: "=g" (r)
: "x" (a)
: "cc"
);
return r;
}
Использует тот факт, что ucomisd
инструкция устанавливает флаг четности, если какой-либо аргумент равен NaN. Вот какstd::isnan
работает когда нет -ffast-math
параметры указаны.
Мне кажется, что лучший действительно кроссплатформенный подход - это использовать объединение и тестировать битовую комбинацию двойника для проверки на наличие NaN.
Я не полностью протестировал это решение, и может быть более эффективный способ работы с битовыми шаблонами, но я думаю, что оно должно работать.
#include <stdint.h>
#include <stdio.h>
union NaN
{
uint64_t bits;
double num;
};
int main()
{
//Test if a double is NaN
double d = 0.0 / 0.0;
union NaN n;
n.num = d;
if((n.bits | 0x800FFFFFFFFFFFFF) == 0xFFFFFFFFFFFFFFFF)
{
printf("NaN: %f", d);
}
return 0;
}
Стандарт IEEE гласит, что когда показатель степени равен всем 1, а мантисса не равна нулю, число равно NaN. Двойной является 1 знаковым битом, 11 экспонентными битами и 52 битами мантиссы. Сделайте немного проверки.
Как указано выше, a!= A не будет работать в g++ и некоторых других компиляторах, но этот трюк должен. Это может быть не так эффективно, но это все же способ:
bool IsNan(float a)
{
char s[4];
sprintf(s, "%.3f", a);
if (s[0]=='n') return true;
else return false;
}
По сути, в g++ (хотя я не уверен в других) printf печатает 'nan' в форматах%d или%.f, если переменная не является допустимым целым числом /float. Поэтому этот код проверяет, является ли первый символ строки 'n' (как в "nan")
Это обнаруживает бесконечность, а также NaN в Visual Studio, проверяя, что оно в двойных пределах:
//#include <float.h>
double x, y = -1.1; x = sqrt(y);
if (x >= DBL_MIN && x <= DBL_MAX )
cout << "DETECTOR-2 of errors FAILS" << endl;
else
cout << "DETECTOR-2 of errors OK" << endl;