在C++中检查double(或float)是否为NaN

417

有isnan()函数吗?

PS.:我在使用MinGW(如果有区别的话)。

我通过使用<math.h>中的isnan()解决了这个问题,但是在一开始我使用的<cmath>中并不存在该函数。


2
我不纯净,但你可以以可移植的方式完成它。谁说C++需要IEEE754? - David Heffernan
3
只是提醒一下,预防胜于治疗。换句话说,防止 0.f/0.f 的执行要比事后检查代码中的 nan 要好得多。如果允许 nan 不断蔓延,它会给你的程序带来严重破坏,并可能引入难以找到的 bug。这是因为 nan 是有毒的,(5*nan=nan), nan 不等于任何东西 (nan != nan),nan 不大于任何东西 (nan !> 0),nan 不小于任何东西 (nan !< 0)。 - bobobobo
1
@bobobobo:这是一项功能,允许集中式错误检查。就像异常与返回值一样。 - Ben Voigt
2
为什么<cmath>没有isnan()函数?它在std::中。 - frankliuao
21个回答

12
以下代码使用NAN的定义(所有指数位设置,至少一个分数位设置),并假定sizeof(int) = sizeof(float) = 4。可以在维基百科中查找NAN的详细信息。
``` bool IsNan( float value ) { return ((*(UINT*)&value) & 0x7fffffff) > 0x7f800000; } ```

我相信这也适用于大端平台。字面量0x7fffffff在内存中会被表示为ff ff ff 7fvalue的排序方式与0x7f800000相同,因此所有操作都是对齐的(没有字节交换)。如果有人能在大端平台上测试一下,我会很感兴趣。 - Bryan W. Wagner
0x7fff1234 is also a NaN. So is 0xffffffff - Steve Hollasch

7
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 754表示。 - Cheers and hth. - Alf
请注意,这个强制类型转换打破了g++的严格别名规则,并且当它检测到形式上的UB时,编译器已知会执行不可言说的操作™。与高效的类型转换不同,你需要使用memcpy来确保通过字节数组进行转换,具体代码请参见我的第二个答案(https://dev59.com/YHRB5IYBdhLWcg3wn4YL#42138465)。 - Cheers and hth. - Alf

4

不依赖于具体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) );
}

单精度浮点数有超过800万个合法且不同的NaN位表示,因此您需要添加更多的比较。 :) - Steve Hollasch

4

考虑到在某些情况下(例如使用-ffast-math选项时),(x != x)不能总是保证为NaN,因此我一直在使用:

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

数字不能同时小于0和大于等于0,因此仅当数字既不小于零也不大于或等于零时,此检查才能通过。 这基本上不是任何数字,或NaN。

如果您喜欢,也可以使用以下内容:

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

我不确定这是否会受到-ffast-math的影响,所以结果可能会有所不同。


这个问题和 f != f 一样有瑕疵。 我曾经看到 llvm 优化了几乎相同的代码。编译器可以传播第一个比较的信息,并找出如果第一个比较是真的,那么第二个比较可能永远不会成立。(如果编译器严格遵守 IEEE 规则,那么 f != f 也会简单得多) - Markus
不支持 g++ 的 -ffast-math 选项。可以在 Visual C++ 中使用。请参见(https://dev59.com/YHRB5IYBdhLWcg3wn4YL#42138465)。 - Cheers and hth. - Alf

3

对于我来说,解决方案可能是使用宏将其显式地内联,从而足够快。这也适用于任何浮点类型。它的基础是只有在值不是数字时才存在值不等于自身的情况。

#ifndef isnan
  #define isnan(a) (a != a)
#endif

这是对这个问题最好的答案之一!谢谢你的分享。 - Henri Menke
3
其他答案表明,在启用 -ffast-math 选项时,这可能会失败。 - Technophile

3

这是有效的:

#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


1

在我看来,最好的跨平台方法是使用联合并测试双精度浮点数的位模式以检查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;
}

请注意,“从未最近写入的联合成员读取是未定义行为”。 因此,使用union在两种类型之间进行类型转换可能无法按预期工作(:sad_panda:)。正确的方法(尽管实际上并不像期望的那样可移植)是完全避免使用联合,并从double执行memcpy到另一个uint64_t变量,然后使用该辅助变量进行测试。 - Eljay

1
在x86-64上,您可以拥有极快的方法来检查NaN和无穷大,而不受-ffast-math编译器选项的影响。 (使用-ffast-math时,f!= f,std :: isnan,std :: isinf始终产生false)。

测试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”从“.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;
}

利用 ucomisd 指令在任意参数为 NaN 时设置奇偶标志位。这就是在未指定 -ffast-math 选项时 std::isnan 的工作原理。

-1
IEEE标准规定, 当指数为所有1时, 且 尾数不为零, 该数是一个NaN。 Double类型有1个符号位,11个指数位和52个尾数位。 进行一次位检查。

-3

正如上面的评论所述,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在%d或%.f格式上打印“nan”。因此,此代码正在检查字符串的第一个字符是否为“n”(如“ nan”)。

2
如果a = 234324.0f,那么这不会导致缓冲区溢出吗? - Mazyod
如果a=FLT_MAX,则是340282346638528859811704183484516925440.000,否则就是t'will。他必须使用char s[7]; sprintf(s, "%.0g", a);,这将是6个字符,如果a=-FLT_MAX,则为-3e+38 - bobobobo
我使用的是和你相同的基本思路。但如果值为“-nan”,也会出现问题。不过,通过扩大缓冲区并将if(strchr(s,"nan")!=nullptr)用作判断条件应该可以解决这个问题。 - Jakub Homola

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