从 C++14 开始,有多种方法可以测试浮点数 value 是否为 NaN。
在这些方法中,只有检查数字表示的位,
正如我原来的答案所述,工作可靠。特别是std::isnan 和经常建议的检查v != v,不能可靠地工作并且不应该使用,以免当有人决定需要浮点优化时你的代码停止正常工作,并要求编译器这样做。这种情况可以改变,编译器可以变得更加符合,但是对于这个问题,自从最初的答案以来已经 6 年没有发生过。
大约 6 年来,我最初的答案是这个问题的选定解决方案,这没问题。但最近选择了一个高度赞成的答案,推荐不可靠的v != v 测试。因此,这个额外的更新答案(我们现在拥有 C++11 和 C++14 标准,以及即将推出的 C++17)。
从 C++14 开始,检查 NaN 的主要方法是:
std::isnan(value) )
是自 C++11 以来预期的标准库方式。 isnan 显然与
同名的 Posix 宏,但实际上这不是问题。主要问题是
当请求浮点算术优化时,至少使用一个主编译器,即 g++,std::isnan为 NaN 参数返回 false。
(fpclassify(value) == FP_NAN) )
遇到与std::isnan相同的问题,即不可靠。
(value != value) )
在许多 SO 答案中推荐。遇到与std::isnan 相同的问题,即
不可靠。
(value == Fp_info::quiet_NaN()) )
这是一个测试,标准行为不应该检测 NaN,但是
优化的行为也许可以检测到 NaN(由于优化的代码只是比较
位级表示),并且可能与另一种方法相结合
覆盖标准的未优化行为,可以可靠地检测到 NaN。很遗憾
结果证明它不能可靠地工作。
(ilogb(value) == FP_ILOGBNAN) )
遇到与std::isnan相同的问题,即不可靠。
isunordered(1.2345, value) )
遇到与std::isnan相同的问题,即不可靠。
is_ieee754_nan( value ) )
这不是标准功能。它根据 IEEE 754 检查位
标准。它完全可靠但代码在某种程度上依赖于系统。
在下面的完整测试代码中,“成功”是表达式是否报告值的 Nan-ness。对于大多数表达式,这种衡量成功的方法、检测 NaN 和仅检测 NaN 的目标对应于它们的标准语义。但是,对于 (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(检测 NaN)]
>
g++ --version |找到“++”
g++ (x86_64-win32-sjlj-rev1, 由 MinGW-W64 项目构建) 6.3.0
[C:\my\forums\so\282(检测 NaN)]
>
g++ foo.cpp && a
编译器声称 IEEE 754 = true
v = nan, (std::isnan(value)) = true 成功
u = 3.14, (std::isnan(value)) = false 成功
w = inf, (std::isnan(value)) = false 成功
v = nan, ((fpclassify(value) == 0x0100)) = true 成功
u = 3.14, ((fpclassify(value) == 0x0100)) = false 成功
w = inf, ((fpclassify(value) == 0x0100)) = false 成功
v = nan, ((value != value)) = true 成功
u = 3.14, ((value != value)) = false 成功
w = inf, ((value != value)) = false 成功
v = nan, ((value == Fp_info::quiet_NaN())) = false 失败
u = 3.14, ((value == Fp_info::quiet_NaN())) = false 成功
w = inf, ((value == Fp_info::quiet_NaN())) = false 成功
v = nan, ((ilogb(value) == ((int)0x80000000))) = true 成功
u = 3.14, ((ilogb(value) == ((int)0x80000000))) = false 成功
w = inf, ((ilogb(value) == ((int)0x80000000))) = false 成功
v = nan, (isunordered(1.2345, value)) = true 成功
u = 3.14, (isunordered(1.2345, value)) = false 成功
w = inf, (isunordered(1.2345, value)) = false 成功
v = nan, (is_ieee754_nan( value )) = true 成功
u = 3.14, (is_ieee754_nan( value )) = false 成功
w = inf, (is_ieee754_nan( value )) = false 成功
[C:\my\forums\so\282(检测 NaN)]
>
g++ foo.cpp -ffast-math && a
编译器声称 IEEE 754 = true
v = nan, (std::isnan(value)) = false 失败
u = 3.14, (std::isnan(value)) = false 成功
w = inf, (std::isnan(value)) = false 成功
v = nan, ((fpclassify(value) == 0x0100)) = false 失败
u = 3.14, ((fpclassify(value) == 0x0100)) = false 成功
w = inf, ((fpclassify(value) == 0x0100)) = false 成功
v = nan, ((value != value)) = false 失败
u = 3.14, ((value != value)) = false 成功
w = inf, ((value != value)) = false 成功
v = nan, ((value == Fp_info::quiet_NaN())) = true 成功
u = 3.14, ((value == Fp_info::quiet_NaN())) = true 失败
w = inf, ((value == Fp_info::quiet_NaN())) = true 失败
v = nan, ((ilogb(value) == ((int)0x80000000))) = true 成功
u = 3.14, ((ilogb(value) == ((int)0x80000000))) = false 成功
w = inf, ((ilogb(value) == ((int)0x80000000))) = false 成功
v = nan, (isunordered(1.2345, value)) = false 失败
u = 3.14, (isunordered(1.2345, value)) = false 成功
w = inf, (isunordered(1.2345, value)) = false 成功
v = nan, (is_ieee754_nan( value )) = true 成功
u = 3.14, (is_ieee754_nan( value )) = false 成功
w = inf, (is_ieee754_nan( value )) = false 成功
[C:\my\forums\so\282(检测 NaN)]
> _
Visual C++ 的结果:
[C:\my\forums\so\282(检测 NaN)]
>
cl /nologo- 2>&1 |找到“++”
Microsoft (R) C/C++ 优化编译器版本 19.00.23725 for x86
[C:\my\forums\so\282(检测 NaN)]
>
cl foo.cpp /Feb && b
foo.cpp
编译器声称 IEEE 754 = true
v = nan, (std::isnan(value)) = true 成功
u = 3.14, (std::isnan(value)) = false 成功
w = inf, (std::isnan(value)) = false 成功
v = nan, ((fpclassify(value) == 2)) = true 成功
u = 3.14, ((fpclassify(value) == 2)) = false 成功
w = inf, ((fpclassify(value) == 2)) = false 成功
v = nan, ((value != value)) = true 成功
u = 3.14, ((value != value)) = false 成功
w = inf, ((value != value)) = false 成功
v = nan, ((value == Fp_info::quiet_NaN())) = false 失败
u = 3.14, ((value == Fp_info::quiet_NaN())) = false 成功
w = inf, ((value == Fp_info::quiet_NaN())) = false 成功
v = nan, ((ilogb(value) == 0x7fffffff)) = true 成功
u = 3.14, ((ilogb(value) == 0x7fffffff)) = false 成功
w = inf, ((ilogb(value) == 0x7fffffff)) = true 失败
v = nan, (isunordered(1.2345, value)) = true 成功
u = 3.14, (isunordered(1.2345, value)) = false 成功
w = inf, (isunordered(1.2345, value)) = false 成功
v = nan, (is_ieee754_nan( value )) = true 成功
u = 3.14, (is_ieee754_nan( value )) = false 成功
w = inf, (is_ieee754_nan( value )) = false 成功
[C:\my\forums\so\282(检测 NaN)]
>
cl foo.cpp /Feb /fp:fast && b
foo.cpp
编译器声称 IEEE 754 = true
v = nan, (std::isnan(value)) = true 成功
u = 3.14, (std::isnan(value)) = false 成功
w = inf, (std::isnan(value)) = false 成功
v = nan, ((fpclassify(value) == 2)) = true 成功
u = 3.14, ((fpclassify(value) == 2)) = false 成功
w = inf, ((fpclassify(value) == 2)) = false 成功
v = nan, ((value != value)) = true 成功
u = 3.14, ((value != value)) = false 成功
w = inf, ((value != value)) = false 成功
v = nan, ((value == Fp_info::quiet_NaN())) = false 失败
u = 3.14, ((value == Fp_info::quiet_NaN())) = false 成功
w = inf, ((value == Fp_info::quiet_NaN())) = false 成功
v = nan, ((ilogb(value) == 0x7fffffff)) = true 成功
u = 3.14, ((ilogb(value) == 0x7fffffff)) = false 成功
w = inf, ((ilogb(value) == 0x7fffffff)) = true 失败
v = nan, (isunordered(1.2345, value)) = true 成功
u = 3.14, (isunordered(1.2345, value)) = false 成功
w = inf, (isunordered(1.2345, value)) = false 成功
v = nan, (is_ieee754_nan( value )) = true 成功
u = 3.14, (is_ieee754_nan( value )) = false 成功
w = inf, (is_ieee754_nan( value )) = false 成功
[C:\my\forums\so\282(检测 NaN)]
> _
总结以上结果,仅使用此测试程序中定义的is_ieee754_nan 函数直接测试位级表示,在所有情况下均能可靠地使用 g++ 和 Visual C++。
附录:
在发布上述内容后,我意识到另一种可能的 NaN 测试,在another answer 中提到,即((value < 0) == (value >= 0))。结果证明它在 Visual C++ 上运行良好,但在 g++ 的 -ffast-math 选项下失败了。只有直接位模式测试才能可靠地工作。