f != f
才会为真。-ffast-math
选项的文档明确说明,如果数学函数遵循IEEE或ISO的规则/规范,它可能会导致对依赖于精确实现的程序产生错误的输出。如果未启用该选项,则使用x != x
可以是一种完全有效和可移植的测试NaN的方法。 - Adam Rosenfieldx != x
是有效的,这是没有逻辑关系的。这可能对于某个版本的g++是正确的,也可能不是。无论如何,通常情况下你无法保证 fastmath
选项不会被使用。 - Cheers and hth. - Alf-ffast-math
选项),那么x != x
是一种有效且可移植的解决方案。您甚至可以通过测试__FAST_MATH__
宏并在这种情况下切换到不同的实现(例如使用联合和位操作)来测试-ffast-math
。 - Adam Rosenfield自从提出此问题以来,有了一些新的发展:重要的是要知道std::isnan()
是C++11的一部分。
在头文件<cmath>
中定义。
bool isnan( float arg ); (since C++11)
bool isnan( double arg ); (since C++11)
bool isnan( long double arg ); (since C++11)
确定给定的浮点数参数是否为非数字(NaN
)。
参数:
arg
:浮点值
返回值:
如果arg
是NaN
,则返回true
,否则返回false
。
参考资料:
http://en.cppreference.com/w/cpp/numeric/math/isnan
请注意,如果你使用g++,这与-fast-math不兼容。以下是其他建议。
isnan(c)
。变量x
的类型应为float、double或long double。isnan()
。NaN
的可移植方式是使用IEEE 754属性,即NaN
不等于自身:即对于x
为NaN
,x == x
将返回false。std::isnan
仍然不是一个好的建议,因为它无法与g ++的浮点优化一起使用。 - Cheers and hth. - Alf当前C++标准库中没有可用的isnan()
函数。它是在C99中引入的,并被定义为宏而不是函数。由C99定义的标准库元素不是当前C++标准ISO/IEC 14882:1998或其更新版本ISO/IEC 14882:2003的一部分。
在2005年,提出了技术报告1。TR1将C99与C++兼容。尽管它从未被正式采用成为C++标准,但许多C++实现(如GCC 4.0+或Visual C++ 9.0+)提供TR1功能,其中所有或仅某些(Visual C++ 9.0不提供C99数学函数)。
如果有TR1,则cmath
包含C99元素,例如isnan()
、isfinite()
等,但它们被定义为函数,而不是宏,通常在std::tr1::
命名空间中,尽管许多实现(例如Linux上的GCC 4+或Mac OS X 10.5+中的XCode)直接将它们注入到std::
中,因此std::isnan
是被定义好的。
此外,一些C++实现仍然使C99 isnan()
宏可用于C++(通过cmath
或math.h
包含),这可能会导致更多混淆,并使开发人员认为它是一种标准行为。
std::isnan
和 std::tr1::isnan
,但它提供了一个扩展函数 _isnan()
,该函数自 Visual C++ 6.0 版本就已经可用。std::isnan
。对于旧版本的编译器和库,似乎(这里有一个 相关讨论,我还没有检查过),定义了两个函数,__inline_isnand()
在Intel上,__isnand()
在Power PC上。Boost中还有一个仅包含头文件的库,其中有处理浮点数数据类型的方便工具。
#include <boost/math/special_functions/fpclassify.hpp>
template <class T> bool isfinite(T z);
template <class T> bool isinf(T t);
template <class T> bool isnan(T t);
template <class T> bool isnormal(T t);
isnan
宏、c++0x 的 isnan
函数模板,或者 visual c++ 的 _isnan
函数。numeric_limits<double>::is_iec559
)。但在实践中,如 g++ 等编译器会出现问题。x != x
,但是像 g++ 和 visual c++ 这样的编译器会出错。#include <limits>
#include <assert.h>
void foo( double a, double b )
{
assert( a != b );
}
int main()
{
typedef std::numeric_limits<double> Info;
double const nan1 = Info::quiet_NaN();
double const nan2 = Info::quiet_NaN();
foo( nan1, nan2 );
}
使用g++(TDM-2 mingw32)4.4.1编译:
C:\test> type "C:\Program Files\@commands\gnuc.bat" @rem -finput-charset=windows-1252 @g++ -O -pedantic -std=c++98 -Wall -Wwrite-strings %* -Wno-long-long
C:\test> gnuc x.cpp C:\test> a && echo works... || echo !failed works...
C:\test> gnuc x.cpp --fast-math
C:\test> a && echo works... || echo !failed 断言失败:a!= b,文件x.cpp,第6行
此应用程序已请求运行时以不寻常的方式终止。有关更多信息,请联系应用程序的支持团队。 !failed
C:\test> _
-ffast-math
选项的文档明确说明,如果IEEE或ISO数学函数规则/规范对程序依赖于精确实现,则可能导致输出不正确。如果未启用该选项,则使用x!= x
是一种完全有效且可移植的测试NaN的方法。 - Adam Rosenfield-ffast-math
可以使 is_iec559
成立。问题在于 GCC 的文档中只提到 -ffast-math
对数学函数不遵守 IEEE/ISO 标准,而没有提到它对 C++ 不遵守标准,因为其实现的 numeric_limits
有问题。我猜测,在模板定义时,GCC 无法确定最终后端是否具有符合标准的浮点数,所以甚至不尝试。据我所知,GCC 的 C99 符合性的未解决错误列表中也存在类似问题。 - Steve Jessopis_iec559
,但实际上在GCC上似乎不起作用。C++0x确实有一个isnan
函数,但由于GCC现在没有正确实现is_iec559
,我猜它在C++0x中也不会有,而-ffast-math
可能会破坏它的isnan
函数。 - Steve Jessop如果你的编译器支持C99扩展,就可以使用std::isnan。但是我不确定mingw是否支持。
这里有一个小函数,如果你的编译器没有标准函数,它应该可以工作:
bool custom_isnan(double var)
{
volatile double d = var;
return d != d;
}
isnan
函数会返回错误结果,虽然从技术上讲可能存在这种情况,但在实践中不可能发生。与 var != var
一样,它之所以有效是因为 IEEE 浮点数值是这样定义的。 - jalf你可以使用在标准库limits
中定义的numeric_limits<float>::quiet_NaN( )
来进行测试。对于double
,有一个单独的常量被定义。
#include <iostream>
#include <math.h>
#include <limits>
using namespace std;
int main( )
{
cout << "The quiet NaN for type float is: "
<< numeric_limits<float>::quiet_NaN( )
<< endl;
float f_nan = numeric_limits<float>::quiet_NaN();
if( isnan(f_nan) )
{
cout << "Float was Not a Number: " << f_nan << endl;
}
return 0;
}
我不知道这个在所有平台上是否都有效,因为我只在 Linux 上使用 g++ 进行了测试。
您可以使用isnan()
函数,但需要包含C数学库。
#include <cmath>
由于这个函数是C99的一部分,它并不在所有地方都可用。如果您的供应商没有提供该函数,您还可以定义自己的变体以实现兼容性。
inline bool isnan(double x) {
return x != x;
}
isnan
函数。 - hasenvalue
是否为NaN。std::isnan
和经常提出的检查v != v
不可靠,不应使用,以防止当某人决定需要浮点优化,并要求编译器进行优化时,您的代码停止正确工作。这种情况可能会发生变化,编译器可能会更符合规范,但对于这个问题,在原始答案发布6年后尚未发生。v != v
检查被选中。因此,现在有了这个额外的更新答案(我们现在拥有C++11和C++14标准以及即将发布的C++17)。
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())
这个测试不应该检测到NaNs,因为其符合标准行为。但是,在优化行为下,可能会检测到NaNs(由于优化代码直接比较位级表示),并且结合另一种方式来覆盖标准未经优化的行为,可以可靠地检测到NaN。不幸的是,它被证明不可靠。(ilogb(value) == FP_ILOGBNAN)
与std::isnan
存在相同的问题,即不可靠。isunordered(1.2345, value)
与std::isnan
存在相同的问题,即不可靠。is_ieee754_nan(value)
不是标准函数。它根据IEEE 754标准检查位。它是完全可靠的,但代码有点依赖于系统。在以下完整的测试代码中,“success”表示表达式是否报告值的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 | find "++" g++ (x86_64-win32-sjlj-rev1, Built by MinGW-W64 project) 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)0x使用Visual C++的结果:
[C:\my\forums\so\282 (检测 NaN)] > cl /nologo- 2>&1 | find "++" 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
总结以上结果,只有直接测试位级表示,使用在此测试程序中定义的is_ieee754_nan
函数,在所有情况下都可靠地与g++和Visual C++一起使用。
补充:
在发布上述内容后,我注意到另一种可能的NaN测试方法,提到了这里的另一个答案,即((value < 0) == (value >= 0))
。这在Visual C++中表现良好,但在g++的-ffast-math
选项中失败。只有直接的位模式测试是可靠的。
__fpclassify
获得。在简单的测试中,__fpclassify(x) == FP_NAN
在快速数学运算下是有效的。 - Mark Dewing我的答案是不要使用反向检查来检测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)nan
数进行比较的代码出现异常行为(第三个属性也很奇怪,但你可能永远不会在你的代码中看到x != x ?
(除非你正在检查nan(不可靠)))。nan
值往往会产生难以发现的错误。(请注意,这并不适用于inf
或-inf
。(-inf
< 0)返回TRUE
,(0 < inf
)返回TRUE,甚至(-inf
< inf
)也返回TRUE。因此,根据我的经验,代码的行为通常仍然是所期望的)。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.
0.f
,则会得到inf
的结果(实际上,如上所述,这种行为相当好/非破坏性)。0.0 / 0.0
静默地计算为nan
,也不能懒惰地不检查0.0 / 0.0
之前是否发生。x!= x
检查nan
有时是不可靠的(一些优化编译器会删除x!= x
,从而破坏IEEE合规性,特别是启用-ffast-math
开关时)。
0.f/0.f
的执行要比事后检查代码中的nan
要好得多。如果允许nan
不断蔓延,它会给你的程序带来严重破坏,并可能引入难以找到的 bug。这是因为nan
是有毒的,(5*nan
=nan
),nan
不等于任何东西 (nan
!=nan
),nan
不大于任何东西 (nan
!> 0),nan
不小于任何东西 (nan
!< 0)。 - bobobobo