Как сделать переносную функцию isnan/isinf
Я использую isinf
, isnan
работает на платформах Linux, которые работали отлично. Но это не сработало на OS-X, поэтому я решил использовать std::isinf
std::isnan
который работает на Linux и OS-X.
Но компилятор Intel не распознает его, и я думаю, что это ошибка компилятора Intel в соответствии с http://software.intel.com/en-us/forums/showthread.php?t=64188
Так что теперь я просто хочу избежать хлопот и определить свой собственный isinf
, isnan
реализация.
Кто-нибудь знает, как это можно сделать?
редактировать:
Я сделал это в моем исходном коде для создания isinf
/isnan
за работой
#include <iostream>
#include <cmath>
#ifdef __INTEL_COMPILER
#include <mathimf.h>
#endif
int isnan_local(double x) {
#ifdef __INTEL_COMPILER
return isnan(x);
#else
return std::isnan(x);
#endif
}
int isinf_local(double x) {
#ifdef __INTEL_COMPILER
return isinf(x);
#else
return std::isinf(x);
#endif
}
int myChk(double a){
std::cerr<<"val is: "<<a <<"\t";
if(isnan_local(a))
std::cerr<<"program says isnan";
if(isinf_local(a))
std::cerr<<"program says isinf";
std::cerr<<"\n";
return 0;
}
int main(){
double a = 0;
myChk(a);
myChk(log(a));
myChk(-log(a));
myChk(0/log(a));
myChk(log(a)/log(a));
return 0;
}
11 ответов
Вы также можете использовать boost для этой задачи:
#include <boost/math/special_functions/fpclassify.hpp> // isnan
if( boost::math::isnan( ... ) .... )
Я не пробовал это, но я бы подумал
int isnan(double x) { return x != x; }
int isinf(double x) { return !isnan(x) && isnan(x - x); }
должно сработать. Такое ощущение, что должен быть лучший способ для isinf, но это должно сработать.
Согласно этому, бесконечность легко проверить:
- знак = 0 или 1 бит, указывающий положительную / отрицательную бесконечность.
- экспонента = все 1 бит.
- мантисса = все 0 бит
NaN немного сложнее, потому что не имеет уникального представления:
- знак = 0 или 1.
- экспонента = все 1 бит.
- mantissa = все, кроме всех 0 битов (поскольку все 0 битов представляют бесконечность).
Ниже приведен код для случая с плавающей запятой двойной точности. Одинарная точность может быть записана аналогичным образом (напомним, что показатель степени составляет 11 бит для двойных и 8 бит для одиночных):
int isinf(double x)
{
union { uint64 u; double f; } ieee754;
ieee754.f = x;
return ( (unsigned)(ieee754.u >> 32) & 0x7fffffff ) == 0x7ff00000 &&
( (unsigned)ieee754.u == 0 );
}
int isnan(double x)
{
union { uint64 u; double f; } ieee754;
ieee754.f = x;
return ( (unsigned)(ieee754.u >> 32) & 0x7fffffff ) +
( (unsigned)ieee754.u != 0 ) > 0x7ff00000;
}
Реализация довольно проста (я взял их из заголовочных файлов OpenCV). Он использует объединение 64-разрядного целого числа без знака равного размера, которое может потребоваться для правильного объявления:
#if defined _MSC_VER
typedef unsigned __int64 uint64;
#else
typedef uint64_t uint64;
#endif
Это работает под Visual Studio 2008:
#include <math.h>
#define isnan(x) _isnan(x)
#define isinf(x) (!_finite(x))
#define fpu_error(x) (isinf(x) || isnan(x))
В целях безопасности я рекомендую использовать fpu_error(). Я считаю, что некоторые числа выбираются с помощью isnan(), а некоторые с помощью isinf(), и вам нужно, чтобы оба были в безопасности.
Вот некоторый тестовый код:
double zero=0;
double infinite=1/zero;
double proper_number=4;
printf("isinf(infinite)=%d.\n",isinf(infinite));
printf("isinf(proper_number)=%d.\n",isinf(proper_number));
printf("isnan(infinite)=%d.\n",isnan(infinite));
printf("isnan(proper_number)=%d.\n",isnan(proper_number));
double num=-4;
double neg_square_root=sqrt(num);
printf("isinf(neg_square_root)=%d.\n",isinf(neg_square_root));
printf("isinf(proper_number)=%d.\n",isinf(proper_number));
printf("isnan(neg_square_root)=%d.\n",isnan(neg_square_root));
printf("isnan(proper_number)=%d.\n",isnan(proper_number));
Вот вывод:
isinf(infinite)=1.
isinf(proper_number)=0.
isnan(infinite)=0.
isnan(proper_number)=0.
isinf(neg_square_root)=1.
isinf(proper_number)=0.
isnan(neg_square_root)=1.
isnan(proper_number)=0.
isnan
теперь является частью C++11, включенной в GCC++ и Apple LLVM.
Теперь MSVC++ имеет _isnan
функция в <float.h>
,
Подходящее #define
с и #include
ы должны сделать подходящий обходной путь.
Тем не менее, я рекомендую не допустить появления nan, вместо обнаружения nan.
Ну, в идеале, вы должны подождать, пока Intel исправит ошибку или предоставит обходной путь:-)
Но если вы хотите обнаружить NaN
а также Inf
из значений IEEE754 отобразите его в целое число (32 или 64 бита в зависимости от того, имеет ли он одинарную или двойную точность) и проверьте, равны ли все биты экспоненты 1. Это указывает на эти два случая.
Вы можете различить NaN
а также Inf
проверяя бит старшего разряда мантиссы. Если это 1, это NaN
иначе Inf
,
+/-Inf
продиктовано знаком бит.
Для одинарной точности (32-битные значения) знак является старшим битом (b31), экспонента - следующие восемь бит (плюс 23-битная мантисса). Для двойной точности знак по-прежнему является старшим битом, но показатель степени составляет одиннадцать бит (плюс 52 бита для мантиссы).
В Википедии есть все кровавые подробности.
Следующий код показывает вам, как работает кодировка.
#include <stdio.h>
static void decode (char *s, double x) {
long y = *(((long*)(&x))+1);
printf("%08x ",y);
if ((y & 0x7ff80000L) == 0x7ff80000L) {
printf ("NaN (%s)\n", s);
return;
}
if ((y & 0xfff10000L) == 0x7ff00000L) {
printf ("+Inf (%s)\n", s);
return;
}
if ((y & 0xfff10000L) == 0xfff00000L) {
printf ("-Inf (%s)\n", s);
return;
}
printf ("%e (%s)\n", x, s);
}
int main (int argc, char *argv[]) {
double dvar;
printf ("sizeof double = %d\n", sizeof(double));
printf ("sizeof long = %d\n", sizeof(long));
dvar = 1.79e308; dvar = dvar * 10000;
decode ("too big", dvar);
dvar = -1.79e308; dvar = dvar * 10000;
decode ("too big and negative", dvar);
dvar = -1.0; dvar = sqrt(dvar);
decode ("imaginary", dvar);
dvar = -1.79e308;
decode ("normal", dvar);
return 0;
}
и это выводит:
sizeof double = 8
sizeof long = 4
7ff00000 +Inf (too big)
fff00000 -Inf (too big and negative)
fff80000 NaN (imaginary)
ffefdcf1 -1.790000e+308 (normal)
Просто помните, что этот код (но не метод) во многом зависит от размеров ваших длинных, которые не слишком переносимы. Но, если вам нужно немного покопаться, чтобы получить информацию, вы уже въехали на эту территорию:-)
Кроме того, я всегда находил преобразователь IEEE754 Харальда Шмидта очень полезным для анализа с плавающей запятой.
Просто используйте этот супер простой IEEE 754-1985-совместимый код:
static inline bool ISINFINITE( float a ) { return (((U32&) a) & 0x7FFFFFFFU) == 0x7F800000U; }
static inline bool ISINFINITEPOSITIVE( float a ) { return (((U32&) a) & 0xFFFFFFFFU) == 0x7F800000U; }
static inline bool ISINFINITENEGATIVE( float a ) { return (((U32&) a) & 0xFFFFFFFFU) == 0xFF800000U; }
static inline bool ISNAN( float a ) { return !ISINFINITE( a ) && (((U32&) a) & 0x7F800000U) == 0x7F800000U; }
static inline bool ISVALID( float a ) { return (((U32&) a) & 0x7F800000U) != 0x7F800000U; }
Как сказал brubelsabs, Boost предлагает эту функцию, но, как сообщается здесь, вместо использования
if (boost::math::isnan(number))
Это должно быть использовано:
if ((boost::math::isnan)(number))
Кажется, никто не упомянул функцию C99 fpclassify, которая возвращает:
Один из типов FP_INFINITE, FP_NAN, FP_NORMAL, FP_SUBNORMAL, FP_ZERO или типа, определенного реализацией, указывающий категорию аргумента.
Это работает с visual studio, но я не знаю насчет OS-X.
В следующей статье есть несколько интересных трюков для isnan и isinf: http://jacksondunstan.com/articles/983
Это работает на OSX
#include <math.h>
также это может быть портативным,
int isinf( double x ) { return x == x - 1; }
редактировать:
как указывал Крис, вышеописанное может потерпеть неудачу с большим х
int isinf( double x ) { return x == x * 2; }