Есть ли функция isnan()?
PS: Я в MinGW (если это имеет значение).
Я решил это с помощью isnan() из <math.h>
, которого нет в <cmath>
, который был сначала #include
ing.
Есть ли функция isnan()?
PS: Я в MinGW (если это имеет значение).
Я решил это с помощью isnan() из <math.h>
, которого нет в <cmath>
, который был сначала #include
ing.
В соответствии со стандартом IEEE значения NaN имеют нечетное свойство, при котором сравнения с ними всегда ложны. То есть, для float f, f != f
будет истинным, только если f является NaN.
Обратите внимание, что, как указывали некоторые комментарии ниже, не все компиляторы соблюдают это при оптимизации кода.
Для любого компилятора, претендующего на использование плавающей запятой IEEE, этот трюк должен работать. Но я не могу гарантировать, что он будет работать на практике. Если у вас есть сомнения, обратитесь к своему компилятору.
В текущей стандартной библиотеке С++ нет функции isnan()
. Он был введен в C99 и определен как макрос не является функцией. Элементы стандартной библиотеки, определенные C99, не являются частью нынешнего стандарта С++ ISO/IEC 14882: 1998 ни его обновления ISO/IEC 14882: 2003.
В 2005 году был предложен Технический отчет 1. TR1 обеспечивает совместимость с C99 до С++. Несмотря на то, что он никогда официально не принимался, чтобы стать стандартом С++, многие (GCC 4.0+ или Visual С++ 9.0+ Реализации С++ предоставляют функции TR1, все они или только некоторые (Visual С++ 9.0 не предоставляет математические функции C99).
Если TR1 доступен, то cmath
включает элементы C99, такие как isnan()
, isfinite()
и т.д., но они определяются как функции, а не макросы, обычно в пространстве имен std::tr1::
, хотя многие реализации (то есть GCC 4 + в Linux или в XCode в Mac OS X 10.5+) вставляйте их непосредственно в std::
, поэтому std::isnan
четко определен.
Кроме того, некоторые реализации С++ по-прежнему делают C99 isnan()
макрос доступным для С++ (включенным через cmath
или math.h
), что может вызвать больше путаницы, а разработчики могут считать это стандартным поведением.
Заметка о Viusal С++, как упоминалось выше, не предоставляет std::isnan
ни std::tr1::isnan
, но предоставляет функцию расширения, определенную как _isnan()
, которая была доступна с Visual С++ 6.0
На XCode есть еще больше удовольствия. Как уже упоминалось, GCC 4+ определяет std::isnan
. Для старых версий компилятора и библиотеки XCode кажется (здесь соответствующее обсуждение), у меня не было возможности проверить себя) две функции:, __inline_isnand()
для Intel и __isnand()
на ПК с питанием.
Поскольку это было задано, было несколько новых событий: важно знать, что std::isnan()
является частью С++ 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
, если arg NaN
, false
иначе
Ссылка
http://en.cppreference.com/w/cpp/numeric/math/isnan
Обратите внимание, что это несовместимо с -fast-math, если вы используете g++, см. ниже для других предложений.
Для C99 в C это реализовано как макрос isnan(c)
, который возвращает значение int. Тип x
должен быть плавающим, двойным или длинным.
Различные поставщики могут включать или не включать функцию isnan()
.
Предполагаемый переносимый способ проверки NaN
заключается в использовании свойства IEEE 754, что NaN
не равно самому себе: i.e. x == x
будет false для x
, являющегося NaN
.
Однако последняя опция может не работать с каждым компилятором и некоторыми настройками (особенно с настройками оптимизации), поэтому в последнем случае вы всегда можете проверить шаблон бита...
Существует также библиотека только для заголовка, присутствующая в 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, у него много полезных инструментов и быстро растет.
Также, имея дело с плавающими и неплавающими точками, может быть хорошей идеей посмотреть числовые преобразования.
Существует три "официальных" способа: posix isnan
макрос, С++ 0x isnan
шаблон функции или визуальный С++ _isnan
функция.
К сожалению, довольно сложно определить, какие из них использовать.
И, к сожалению, нет надежного способа определить, есть ли у вас представление IEEE 754 с NaN. Стандартная библиотека предлагает такой официальный способ (numeric_limits<double>::is_iec559
). Но на практике компиляторы, такие как g++, закручивают.
В теории можно было бы просто просто x != x
, но компиляторы, такие как g++ и visual С++, закручивали это.
Итак, в конце концов, протестируйте конкретные NaN-битпаттеры, предполагая (и, надеясь, в какой-то момент применить) конкретное представление, такое как IEEE 754.
EDIT: в качестве примера "компиляторы, такие как g++ и hellip; завинтите это", рассмотрите
#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> type "C:\Program Files\@commands\gnuc.bat" @rem -finput-charset=windows-1252 @g++ -O -pedantic -std=c++98 -Wall -Wwrite-strings %* -Wno-long-long C:\test> gnuc x.cpp C:\test> a && echo works... || echo !failed works... C:\test> gnuc x.cpp --fast-math C:\test> a && echo works... || echo !failed Assertion failed: a != b, file x.cpp, line 6 This application has requested the Runtime to terminate it in an unusual way. Please contact the application support team for more information. !failed 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;
}
Мой ответ на этот вопрос не использует ретроактивные проверки для 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.
Итак, в приведенном выше случае, если x были 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;
}
В С++ 14 существует несколько способов проверить, является ли число с плавающей запятой value
NaN.
Из этих способов проверяется только проверка битов числа,
работает надежно, как отмечено в моем первоначальном ответе. В частности, std::isnan
и часто предлагаемая проверка v != v
не работают надежно и не должны использоваться, чтобы ваш код не работал корректно, когда кто-то решил, что оптимизация с плавающей точкой необходима, и просит компилятор сделать это. Эта ситуация может измениться, компиляторы могут получить больше соответствия, но для этой проблемы, которая не произошла за 6 лет с момента первоначального ответа.
В течение примерно 6 лет мой первоначальный ответ был выбранным решением для этого вопроса, который был в порядке. Но недавно был выбран очень высокий ответ, рекомендующий ненадежный тест v != v
. Следовательно, этот дополнительный более современный ответ (теперь у нас есть стандарты С++ 11 и С++ 14, а С++ 17 - на горизонте).
Основными способами проверки для NaN-ness, начиная с С++ 14, являются:
std::isnan(value) )
является стандартным библиотечным способом с С++ 11. isnan
, по-видимому, конфликтует с
Посикс-макрос с тем же именем, но на практике это не проблема. Основная проблема
что, когда запрашивается арифметическая оптимизация с плавающей запятой, по крайней мере с одним основным компилятором, а именно g++, std::isnan
возвращает false
для аргумента NaN.
(fpclassify(value) == FP_NAN) )
Страдает от той же проблемы, что и std::isnan
, т.е. Не является надежной.
(value != value) )
Рекомендуется во многих ответах SO. Страдает от той же проблемы, что и std::isnan
, т.
не является надежным.
(value == Fp_info::quiet_NaN()) )
Это тест, который при стандартном поведении не должен определять NaN, но с
оптимизированное поведение, возможно, может обнаружить NaN (из-за оптимизированного кода, просто сравнивающего
bitlevel) и, возможно, в сочетании с другим способом
охватывают стандартное не оптимизированное поведение, могут надежно обнаруживать NaN. К сожалению
оказалось, что он не работает надежно.
(ilogb(value) == FP_ILOGBNAN) )
Страдает от той же проблемы, что и std::isnan
, т.е. Не является надежной.
isunordered(1.2345, value) )
Страдает от той же проблемы, что и std::isnan
, т.е. Не является надежной.
is_ieee754_nan( value ) )
Это не стандартная функция. Проверка бит в соответствии с IEEE 754
стандарт. Он полностью надежный, но код несколько зависит от системы.
В следующем полном тестовом коде "успех" заключается в том, указывает ли выражение Nan-ness значение. Для большинства выражений эта мера успеха, цель обнаружения NaNs и только NaNs, соответствует их стандартной семантике. Однако для выражения (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 (detect NaN)] > g++ --version | find "++" g++ (x86_64-win32-sjlj-rev1, Built by MinGW-W64 project) 6.3.0 [C:\my\forums\so\282 (detect NaN)] > g++ foo.cpp && a Compiler claims IEEE 754 = true v = nan, (std::isnan(value)) = true Success u = 3.14, (std::isnan(value)) = false Success w = inf, (std::isnan(value)) = false Success v = nan, ((fpclassify(value) == 0x0100)) = true Success u = 3.14, ((fpclassify(value) == 0x0100)) = false Success w = inf, ((fpclassify(value) == 0x0100)) = false Success v = nan, ((value != value)) = true Success u = 3.14, ((value != value)) = false Success w = inf, ((value != value)) = false Success v = nan, ((value == Fp_info::quiet_NaN())) = false FAILED u = 3.14, ((value == Fp_info::quiet_NaN())) = false Success w = inf, ((value == Fp_info::quiet_NaN())) = false Success v = nan, ((ilogb(value) == ((int)0x80000000))) = true Success u = 3.14, ((ilogb(value) == ((int)0x80000000))) = false Success w = inf, ((ilogb(value) == ((int)0x80000000))) = false Success v = nan, (isunordered(1.2345, value)) = true Success u = 3.14, (isunordered(1.2345, value)) = false Success w = inf, (isunordered(1.2345, value)) = false Success v = nan, (is_ieee754_nan( value )) = true Success u = 3.14, (is_ieee754_nan( value )) = false Success w = inf, (is_ieee754_nan( value )) = false Success [C:\my\forums\so\282 (detect NaN)] > g++ foo.cpp -ffast-math && a Compiler claims IEEE 754 = true v = nan, (std::isnan(value)) = false FAILED u = 3.14, (std::isnan(value)) = false Success w = inf, (std::isnan(value)) = false Success v = nan, ((fpclassify(value) == 0x0100)) = false FAILED u = 3.14, ((fpclassify(value) == 0x0100)) = false Success w = inf, ((fpclassify(value) == 0x0100)) = false Success v = nan, ((value != value)) = false FAILED u = 3.14, ((value != value)) = false Success w = inf, ((value != value)) = false Success v = nan, ((value == Fp_info::quiet_NaN())) = true Success u = 3.14, ((value == Fp_info::quiet_NaN())) = true FAILED w = inf, ((value == Fp_info::quiet_NaN())) = true FAILED v = nan, ((ilogb(value) == ((int)0x80000000))) = true Success u = 3.14, ((ilogb(value) == ((int)0x80000000))) = false Success w = inf, ((ilogb(value) == ((int)0x80000000))) = false Success v = nan, (isunordered(1.2345, value)) = false FAILED u = 3.14, (isunordered(1.2345, value)) = false Success w = inf, (isunordered(1.2345, value)) = false Success v = nan, (is_ieee754_nan( value )) = true Success u = 3.14, (is_ieee754_nan( value )) = false Success w = inf, (is_ieee754_nan( value )) = false Success [C:\my\forums\so\282 (detect NaN)] > _
Результаты с Visual С++:
[C:\my\forums\so\282 (detect NaN)] > cl /nologo- 2>&1 | find "++" Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23725 for x86 [C:\my\forums\so\282 (detect NaN)] > cl foo.cpp /Feb && b foo.cpp Compiler claims IEEE 754 = true v = nan, (std::isnan(value)) = true Success u = 3.14, (std::isnan(value)) = false Success w = inf, (std::isnan(value)) = false Success v = nan, ((fpclassify(value) == 2)) = true Success u = 3.14, ((fpclassify(value) == 2)) = false Success w = inf, ((fpclassify(value) == 2)) = false Success v = nan, ((value != value)) = true Success u = 3.14, ((value != value)) = false Success w = inf, ((value != value)) = false Success v = nan, ((value == Fp_info::quiet_NaN())) = false FAILED u = 3.14, ((value == Fp_info::quiet_NaN())) = false Success w = inf, ((value == Fp_info::quiet_NaN())) = false Success v = nan, ((ilogb(value) == 0x7fffffff)) = true Success u = 3.14, ((ilogb(value) == 0x7fffffff)) = false Success w = inf, ((ilogb(value) == 0x7fffffff)) = true FAILED v = nan, (isunordered(1.2345, value)) = true Success u = 3.14, (isunordered(1.2345, value)) = false Success w = inf, (isunordered(1.2345, value)) = false Success v = nan, (is_ieee754_nan( value )) = true Success u = 3.14, (is_ieee754_nan( value )) = false Success w = inf, (is_ieee754_nan( value )) = false Success [C:\my\forums\so\282 (detect NaN)] > cl foo.cpp /Feb /fp:fast && b foo.cpp Compiler claims IEEE 754 = true v = nan, (std::isnan(value)) = true Success u = 3.14, (std::isnan(value)) = false Success w = inf, (std::isnan(value)) = false Success v = nan, ((fpclassify(value) == 2)) = true Success u = 3.14, ((fpclassify(value) == 2)) = false Success w = inf, ((fpclassify(value) == 2)) = false Success v = nan, ((value != value)) = true Success u = 3.14, ((value != value)) = false Success w = inf, ((value != value)) = false Success v = nan, ((value == Fp_info::quiet_NaN())) = false FAILED u = 3.14, ((value == Fp_info::quiet_NaN())) = false Success w = inf, ((value == Fp_info::quiet_NaN())) = false Success v = nan, ((ilogb(value) == 0x7fffffff)) = true Success u = 3.14, ((ilogb(value) == 0x7fffffff)) = false Success w = inf, ((ilogb(value) == 0x7fffffff)) = true FAILED v = nan, (isunordered(1.2345, value)) = true Success u = 3.14, (isunordered(1.2345, value)) = false Success w = inf, (isunordered(1.2345, value)) = false Success v = nan, (is_ieee754_nan( value )) = true Success u = 3.14, (is_ieee754_nan( value )) = false Success w = inf, (is_ieee754_nan( value )) = false Success [C:\my\forums\so\282 (detect NaN)] > _
Подводя итог приведенным выше результатам, только прямое тестирование представления уровня бит с использованием функции is_ieee754_nan
, определенной в этой тестовой программе, надежно выполнялось во всех случаях как с g++, так и с Visual С++.
Приложение:
После публикации выше я узнал о еще одном возможном испытании для NaN, упомянутого в другом ответе, а именно ((value < 0) == (value >= 0))
. Это оказалось хорошо работать с Visual С++, но не с g++ -ffast-math
. Только прямое тестирование битпатента работает надежно.
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 для используемого 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, поэтому ваш пробег может измениться.
Для меня решение может быть макросом, чтобы сделать его явно встроенным и, таким образом, достаточно быстрым. Он также работает для любого типа float. Он основывается на том, что единственный случай, когда значение не равно самому себе, - это когда значение не является числом.
#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
Прочитав другие ответы, я хотел что-то, что пройдет через предупреждение о переносе с плавающей точкой и не сломается под быстрой математикой. Появится следующий код:
/*
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;
}
Мне кажется, что лучшим по-настоящему кросс-платформенным подходом было бы использование союза и проверка битовой диаграммы двойника для проверки 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
битами мантиссы. Сделайте немного проверки.
На 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 в 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;
Как указано выше, состояние 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" )