如何制作一个便携式的isnan/isinf函数

36
我一直在Linux平台上使用isinfisnan函数,这些函数运行得很完美。但是在OS-X上它们不起作用,所以我决定使用std::isinfstd::isnan,这两个函数在Linux和OS-X上都能正常工作。
但是Intel编译器不能识别它们,根据http://software.intel.com/en-us/forums/showthread.php?t=64188的说法,我猜测这是Intel编译器的一个bug。
现在,我只想避免麻烦,自己定义isinfisnan的实现方式。请问有人知道如何做到这一点吗?
编辑:
最终,我在我的源代码中实现了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个回答

25

你也可以使用boost来完成这个任务:

#include <boost/math/special_functions/fpclassify.hpp> // isnan

if( boost::math::isnan( ... ) .... )

50
是的,拿出大约7000个头文件来解决只需要2或3行代码就能解决的问题。 - Eric
34
如果有人仍然使用boost,那么您不必使用它,但这是一个非常便携和简短的解决方案。没有 #IFDEFs 的话,是吗? - math
3
楼主的问题是“如何制作一个便携式isnan/isinf函数”。很明显,楼主想要自己实现它。这个回答应该作为评论发布,而不是答案。它没有回答问题,更糟糕的是建议使用一个巨大的库。 - plasmacel
5
幸运的是,Boost 是开源的,因此他可以阅读实现以猜测如何解决这个问题。由于 OP 提到他之前使用了预定义的 isnan 和 isinf 函数,他可能不需要重新发明轮子。如果库的大小确实是一个问题,你正在假设!那么这里发布的其他解决方案或 Boost 可能会有所帮助。除非他不把尺寸作为要求,否则最好不要优化。顺便说一句,他提到的错误已经被修复了。 - math

23

我没有尝试过这个,但我认为

int isnan(double x) { return x != x; }
int isinf(double x) { return !isnan(x) && isnan(x - x); }

应该是可行的。感觉使用 isinf 应该有更好的方法,但那样也可以。


我做了类似于你的isnan函数的东西,它可以在Windows、Linux和OS X上运行。 - John D. Cook
18
这个表达式 (x != x) 在使用快速数学标志的 MSVC 或 gcc 中将无法工作(即如果浮点实现不符合 IEEE 标准)。请参阅 http://msdn.microsoft.com/en-us/library/e7s85ffb.aspx。 - Emil Styrke
4
同时,“int isinf(double x) { return fabs(x) > DBL_MAX; }”意思是判断一个双精度浮点数是否为无穷大(正无穷或负无穷),如果是则返回1,否则返回0。 - Pascal Cuoq
1
-ffast-math 会破坏这个,使用 std::isnan - Maxim Egorushkin

18
根据这里的说明,正无穷大很容易检查:
  • 符号 = 0或1表示正/负无穷大。
  • 指数 = 所有位都为1。
  • 尾数 = 所有位都为0。

NaN更加复杂一些,因为它没有唯一的表示:

  • 符号 = 0或1。
  • 指数 = 所有位都为1。
  • 尾数 = 除了所有位都为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

4
如果有人感兴趣,OpenCV中的那个定义已经移动到了“hal”模块中。这是一个更加永久的链接:https://github.com/Itseez/opencv/blob/3.0.0/modules/hal/include/opencv2/hal/defs.h#L447 - Amro
2
+1 这显然是最独立于平台的方法,可以保证得到正确的答案,而无需包含库并且不考虑编译器设置。 - Sheldon Juncker
我听说在C++(或者可能只是C语言)中将一个类型的值放入联合体中,然后以不同类型读取它会导致未定义的行为。你能详细解释一下吗? - Niko O
@NikoO 我也不确定,快速搜索得到以下结果:https://dev59.com/h1QK5IYBdhLWcg3wMNEV、https://dev59.com/Yl8e5IYBdhLWcg3whqo4、https://dev59.com/9mgu5IYBdhLWcg3wYF-3、https://en.wikipedia.org/wiki/Type_punning#Use_of_union。另外一个看起来也被接受的替代方法是使用 memcpy :https://dev59.com/tGMm5IYBdhLWcg3wMs3R、https://dev59.com/VlUK5IYBdhLWcg3wwiWW,或者使用 C++20 的 std::bit_cast - Amro

8

这在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.

4
finite函数对于inf和nan都返回false,因此你的isinf实现是错误的——事实上,你自己的演示输出就显示了这一点 :-) - Eamon Nerbonne

7

2
有时候你无法控制数据中是否存在NaN值,特别是如果这些数据不是由你创建的。但是输出结果仍然需要知道这些NaN值的存在。 - pattivacek
1
有时候你会使用NaN来表示“无效”,类似于null_ptr。 - Pablo H

6
理想情况下,您应该等待 Intel 修复错误或提供解决方法 :-)
但是如果您想要从 IEEE754 值中检测 NaNInf,则将其映射到整数(32位或64位,具体取决于其单精度还是双精度),并检查指数位是否都为1。这表示这两种情况。
您可以通过检查尾数的高位来区分 NaNInf。如果是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)

请记住,这段代码(但不是方法)在很大程度上取决于您的long类型的大小,这并不是非常便携。但是,如果您必须进行位操作以获取信息,那么您已经进入了该领域 :-)

另外,我一直觉得Harald Schmidt的IEEE754转换器非常有用,可以用于浮点数分析。


2
很不幸,表达式 *(((long*)(&x))+1) 会引发未定义的行为:在将指针转换为 long* 后,编译器可以推断出结果指针与原始指针不是别名,因为一个指向 long,而另一个指向 double,并对其进行优化。当编译器决定内联 decode() 时,这可能会成为问题,因为它会允许编译器将整数读取移到浮点数写入之前,这显然会产生垃圾。为了安全起见,请使用 memcpy() 而不是转换。 - cmaster - reinstate monica

3

只需使用符合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; }

2
如brubelsabs所说,Boost库提供了这个功能,但正如这里所报道的那样,它不是使用。
if (boost::math::isnan(number))

应该使用这个:

if ((boost::math::isnan)(number))

1

似乎没有人提到C99函数fpclassify,它返回以下之一:

FP_INFINITE,FP_NAN,FP_NORMAL,FP_SUBNORMAL,FP_ZERO或实现定义的类型,指定arg的类别。

这在Visual Studio中可以使用,但我不知道在OS-X中是否可用。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接