是否存在isnan()函数?

注:我在MinGW(如果这有区别的话)。

我使用isnan()从<math.h>解决了这个问题,这在<cmath>中不存在,我一开始是#包括在内的。


当前回答

南预防

我对这个问题的回答是不要对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数比较的代码的奇怪行为(最后3个属性也很奇怪,但您可能永远不会看到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.

在上面,如果x = 0。F, inf会导致(实际上如上所述,它具有相当好的/非破坏性行为)。

记住,整数除以0会导致运行时异常。所以你必须总是检查整数除以0。仅仅因为0.0/0.0悄悄地计算为nan并不意味着您可以偷懒,在它发生之前不检查0.0/0.0。

通过x != x检查nan有时是不可靠的(x != x被一些破坏IEEE遵从性的优化编译器剥离,特别是当- fast-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数比较的代码的奇怪行为(最后3个属性也很奇怪,但您可能永远不会看到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.

在上面,如果x = 0。F, inf会导致(实际上如上所述,它具有相当好的/非破坏性行为)。

记住,整数除以0会导致运行时异常。所以你必须总是检查整数除以0。仅仅因为0.0/0.0悄悄地计算为nan并不意味着您可以偷懒,在它发生之前不检查0.0/0.0。

通过x != x检查nan有时是不可靠的(x != x被一些破坏IEEE遵从性的优化编译器剥离,特别是当- fast-math开关启用时)。

一个可能的解决方案,不依赖于特定的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) );
}

当前c++标准库中没有可用的isnan()函数。它是在C99中引入的,并被定义为宏而不是函数。C99定义的标准库元素既不是当前c++标准ISO/IEC 14882:1998的一部分,也不是更新版ISO/IEC 14882:2003的一部分。

2005年提出了技术报告1。TR1为c++带来了与C99的兼容性。尽管它从未被正式采用成为c++标准,但许多实现(GCC 4.0+或Visual c++ 9.0+ c++实现)确实提供了TR1特性,全部或仅部分(Visual c++ 9.0不提供C99数学函数)。

如果TR1可用,那么cmath包含C99元素,如isnan(), isfinite()等,但它们被定义为函数,而不是宏,通常在std:: TR1:: namespace中,尽管许多实现(例如Linux上的GCC 4+或Mac OS X 10.5+上的XCode)将它们直接注入std::,因此std::isnan定义良好。

此外,c++的一些实现仍然使C99 isnan()宏对c++可用(通过cmath或math.h包含),这可能会引起更多的混淆,开发人员可能认为这是一种标准行为。

关于visualc++的一个注意事项,如上所述,它不提供std::isnan,也不提供std::tr1::isnan,但它提供了一个定义为_isnan()的扩展函数,该扩展函数自visualc++ 6.0以来一直可用

在XCode中,有更多的乐趣。如前所述,GCC 4+定义了std::isnan。对于旧版本的编译器和库形式的XCode,似乎(这里是相关的讨论),还没有机会检查自己)定义了两个函数,Intel上的__inline_isnand()和Power PC上的__isnand()。

考虑到(x != x)对于NaN并不总是保证的(比如如果使用- fast-math选项),我一直在使用:

#define IS_NAN(x) (((x) < 0) == ((x) >= 0))

数字不能同时< 0和>= 0,所以实际上只有当数字既不小于也不大于或等于0时,这个检查才会通过。基本上没有数字,或者NaN。

如果你喜欢,你也可以使用这个:

#define IS_NAN(x) (!((x)<0) && !((x)>=0)

我不确定这是如何受到快速数学的影响,所以你的里程可能会有所不同。

在x86-64上,您可以使用非常快速的方法来检查NaN和无穷大,不管- fast-math编译器选项如何,这些方法都可以正常工作。(f != f, std::isnan, std::isinf使用- fast-math总是产生false)。


NaN、无穷大和有限数的测试可以通过检查最大指数轻松完成。无穷大是最大指数和零尾数,NaN是最大指数和非零尾数。指数存储在最上面的符号位之后的下一位,这样我们就可以左移来去掉符号位,让指数成为最上面的位,不需要屏蔽(操作符&):

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; }

isinf和isfinite的std版本从.data段加载2个double/float常量,在最坏的情况下,它们会导致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;
}

如果任何参数为NaN,则使用ucomisd指令设置奇偶校验标志的事实。这就是在没有指定- fast-math选项时std::isnan的工作方式。