在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个回答

401
根据IEEE标准,NaN值的一个奇怪属性是涉及它们的比较结果总是为假。也就是说,对于一个浮点数f,只有在f是NaN时,f != f才会为真。
请注意,正如下面一些评论所指出的那样,并非所有编译器在优化代码时都遵循这一点。
对于任何声称使用IEEE浮点数的编译器,这个技巧应该有效。但我不能保证在实践中会有效。如果有疑问,请与您的编译器进行核实。

4
如果以IEEE模式运行,编译器最好不要删除此内容。当然,需要查看您编译器的文档资料... - dmckee --- ex-moderator kitten
42
只有在理论上才能起作用,实际上并不行:像g ++(使用-fastmath)这样的编译器会搞砸它。 直到C ++ 0x出现之前,唯一通用的方法是测试位模式。 - Cheers and hth. - Alf
80
@Alf: -ffast-math选项的文档明确说明,如果数学函数遵循IEEE或ISO的规则/规范,它可能会导致对依赖于精确实现的程序产生错误的输出。如果未启用该选项,则使用x != x可以是一种完全有效和可移植的测试NaN的方法。 - Adam Rosenfield
7
@Adam:文档明确说明它不符合标准,是的。我曾经与Gabriel Dos Reis长时间讨论过这个问题,并遇到过这个论点。它通常被用来为设计辩护,形成一个循环论证的论点(我不知道你是否打算与此有关联,但值得了解 - 这是火药味)。你得出的结论是在没有这个选项的情况下 x != x 是有效的,这是没有逻辑关系的。这可能对于某个版本的g++是正确的,也可能不是。无论如何,通常情况下你无法保证 fastmath 选项不会被使用。 - Cheers and hth. - Alf
9
@Alf:我不知道你与Gabriel Dos Reis的讨论。在另一个问题中,Steve Jessop提出了一个很好的观点,即假设IEEE表示法。如果您假设IEEE 754,并且编译器是符合标准的(即没有使用-ffast-math选项),那么x != x是一种有效且可移植的解决方案。您甚至可以通过测试__FAST_MATH__宏并在这种情况下切换到不同的实现(例如使用联合和位操作)来测试-ffast-math - Adam Rosenfield
显示剩余9条评论

232

第一种解决方案:如果您正在使用C++11

自从提出此问题以来,有了一些新的发展:重要的是要知道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:浮点值 返回值: 如果argNaN,则返回true,否则返回false参考资料: http://en.cppreference.com/w/cpp/numeric/math/isnan 请注意,如果你使用g++,这与-fast-math不兼容。以下是其他建议。

其他解决方案:如果您使用不符合C++11标准的工具

在C99中,在C语言中,这被实现为一个返回int值的宏isnan(c)。变量x的类型应为float、double或long double。
各种供应商可能包含或不包含函数isnan()
检查NaN的可移植方式是使用IEEE 754属性,即NaN不等于自身:即对于xNaNx == x将返回false。
但是,最后一种选择可能不能在每个编译器和某些设置(特别是优化设置)下工作,因此最后一招是始终检查位模式...

9
绝对值得成为被采纳的答案并且应该获得更多点赞。感谢您的建议。 - LBes
4
截至2017年2月,std::isnan 仍然不是一个好的建议,因为它无法与g ++的浮点优化一起使用。 - Cheers and hth. - Alf
@Cheersandhth.-Alf:这个选项符合IEEE标准吗?答案已经被编辑过了。 - BlueTrin
0 取消我的踩,因为添加的评论使问题对读者更加清晰。 :) - Cheers and hth. - Alf
1
警告:在gcc和clang中,isnan(x)与选项-ffinite-math-only不兼容。 - A Fog
显示剩余5条评论

231

当前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++(通过cmathmath.h包含),这可能会导致更多混淆,并使开发人员认为它是一种标准行为。

关于Visual C++的一点说明,如上所述,它不提供 std::isnanstd::tr1::isnan,但它提供了一个扩展函数 _isnan(),该函数自 Visual C++ 6.0 版本就已经可用。
在XCode中,更有趣的事情还在后面。如上所述,GCC 4+ 定义了 std::isnan。对于旧版本的编译器和库,似乎(这里有一个 相关讨论,我还没有检查过),定义了两个函数,__inline_isnand() 在Intel上,__isnand() 在Power PC上。

32
每个人都希望有像 isNan 或 isInfinity 这样的函数。为什么负责人不直接将它们包含在标准中呢?我会试着找出如何获得控制权并投票支持这一点。说真的。 - shuhalo
14
你好,我可以进行翻译。这句话的意思是“你负责了吗?” - Tomáš Zato
24
自C++11标准推出以来,支持std::isnan函数。从Visual Studio 2013开始,该函数已被实现。也许@shuhalo负责了此项工作 :-) - aberaud

83

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

如果你有时间的话,可以看一下Boost的整个数学工具包,它有许多有用的工具,并且正在快速发展。
另外,在处理浮点数和非浮点数时,查看Numeric Conversions可能是一个好主意。

它是在Boost 1.35中添加的(我刚发现我的程序在旧的Linux发行版上无法编译)。 - marcin
2
如果您使用 --fast-math 选项进行编译,那么此函数将无法按预期工作。 - Gaetano Mendola

44
有三种“官方”方法:posix 的 isnan、c++0x 的 isnan 函数模板,或者 visual c++ 的 _isnan 函数
不幸的是,很难检测出使用哪种方法。
而且,没有可靠的方法来检测是否具有 IEEE 754 表示的 NaN。标准库提供了官方的方法(numeric_limits<double>::is_iec559)。但在实践中,如 g++ 等编译器会出现问题。
理论上可以使用简单的 x != x,但是像 g++ 和 visual c++ 这样的编译器会出错。
因此,在最后,测试特定的 NaN 位模式,假设(并希望在某些时候强制执行!)特定的表示,如 IEEE 754。
编辑:作为“编译器如g++……搞砸”的示例,请参考
#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> _

4
@Alf:您的例子在我使用的多个版本的g ++(4.0至4.5)上的Mac OS X和Linux上都按预期工作。 -ffast-math选项的文档明确说明,如果IEEE或ISO数学函数规则/规范对程序依赖于精确实现,则可能导致输出不正确。如果未启用该选项,则使用x!= x是一种完全有效且可移植的测试NaN的方法。 - Adam Rosenfield
6
你所忽略的是,C++标准并不要求浮点数使用IEEE表示或数学方法。就man页面而言,“gcc -ffast-math”仍然是符合C++标准的实现(假设它能正确地获取“numeric_limits :: is_iec559”的值,尽管Alf在上面暗示它可能无法实现这一点):依赖于IEEE的C++代码具备可移植性,也没有权利期望实现提供它。 - Steve Jessop
5
阿尔夫说得没错,在 gcc 4.3.4 上进行快速测试时,使用 -ffast-math 可以使 is_iec559 成立。问题在于 GCC 的文档中只提到 -ffast-math 对数学函数不遵守 IEEE/ISO 标准,而没有提到它对 C++ 不遵守标准,因为其实现的 numeric_limits 有问题。我猜测,在模板定义时,GCC 无法确定最终后端是否具有符合标准的浮点数,所以甚至不尝试。据我所知,GCC 的 C99 符合性的未解决错误列表中也存在类似问题。 - Steve Jessop
1
@Alf,@Steve,我不知道C++标准没有关于浮点数值的规范。这对我来说相当震惊。它似乎更好地处理IEEE 754和NaN作为平台特定扩展而不是标准。不是吗?我能期望在C++0x中添加任何类型的isnan()或IEEE754吗? - eonil
3
@Eonil提到,C++0x仍然有一些像"浮点数类型的值表示是实现定义的"这样的规定。C和C++都旨在支持在没有浮点数硬件的机器上进行实现,而正确的IEEE 754浮点数在模拟时可能会比相当准确的替代方案慢得多。理论上,如果你需要IEEE,你可以断言is_iec559,但实际上在GCC上似乎不起作用。C++0x确实有一个isnan函数,但由于GCC现在没有正确实现is_iec559,我猜它在C++0x中也不会有,而-ffast-math可能会破坏它的isnan函数。 - Steve Jessop

39

如果你的编译器支持C99扩展,就可以使用std::isnan。但是我不确定mingw是否支持。

这里有一个小函数,如果你的编译器没有标准函数,它应该可以工作:

bool custom_isnan(double var)
{
    volatile double d = var;
    return d != d;
}

8
在这样做时,编译器可能会优化比较操作,始终返回 true。 - CTT
24
没有这样的情况。能够做到那种编译器是有问题的。这就好比说标准库中的 isnan 函数会返回错误结果,虽然从技术上讲可能存在这种情况,但在实践中不可能发生。与 var != var 一样,它之所以有效是因为 IEEE 浮点数值是这样定义的。 - jalf
30
如果设置了 -ffast-math 选项,isnan() 函数将无法在 gcc 中返回正确的结果。当然,这种优化被记录为破坏 IEEE 语义... - Matthew Herrmann
如果设置了-ffast-math,则编译器存在缺陷。或者更确切地说,如果设置了-ffast-math,则所有的赌注都是无效的,你也不能依赖NaN。 - Adrian Ratnapala

25

你可以使用在标准库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++ 进行了测试。


2
请注意,目前在GCC版本3.2.3中numeric_limits似乎存在一个错误,因为它返回quiet_NaN的值为0.0。根据我的经验,更高版本的GCC是没有问题的。 - Nathan Kitchen
@Nathan:好知道。我正在使用4.3.2版本,所以我已经很安全了。 - Bill the Lizard

21

您可以使用isnan()函数,但需要包含C数学库。

#include <cmath>

由于这个函数是C99的一部分,它并不在所有地方都可用。如果您的供应商没有提供该函数,您还可以定义自己的变体以实现兼容性。

inline bool isnan(double x) {
    return x != x;
}

我正在使用 <cmath>,但它里面没有isnan!偶然发现在 <math.h> 中确实有一个 isnan 函数。 - hasen
2
正如我所说,这是 C99 的一部分。由于 C99 不是当前任何 C++ 标准的一部分,我提供了替代方案。但由于很可能 isnan() 将包含在即将到来的 C++ 标准中,我在其周围放置了 #ifndef 指令。 - raimue

17
截至C++14,有几种方法来测试浮点数value是否为NaN。
这些方法中,仅检查数字表示的位的检查方法是可靠的,如我的原始答案所述。特别是,std::isnan和经常提出的检查v != v不可靠,不应使用,以防止当某人决定需要浮点优化,并要求编译器进行优化时,您的代码停止正确工作。这种情况可能会发生变化,编译器可能会更符合规范,但对于这个问题,在原始答案发布6年后尚未发生。
大约6年来,我的原始答案一直是该问题的选定解决方案,这还可以。但最近一个高度赞成的答案建议使用不可靠的v != v检查被选中。因此,现在有了这个额外的更新答案(我们现在拥有C++11和C++14标准以及即将发布的C++17)。
截至C++14,检查NaN-ness的主要方法包括:
  • 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选项中失败。只有直接的位模式测试是可靠的。

1
我进一步研究了fpclassify函数。glibc实现确实会检查位模式,并且应该能够抵御快速数学运算的影响。然而,包含文件(/usr/include/math.h和cmath)将fpclassify指向了一个编译器内置函数。至少在clang中,该内置函数使用了自我比较,在快速数学运算下会失败。glibc版本似乎可以用__fpclassify获得。在简单的测试中,__fpclassify(x) == FP_NAN在快速数学运算下是有效的。 - Mark Dewing
std::isnan是非常可靠的。gcc只是不可靠,如果你愿意启用一个选项,让它抛弃规则。简单的解决方案:不要这样做。 - undefined

12

防止NaN

我的答案是不要使用反向检查来检测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.f0.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。因此,根据我的经验,代码的行为通常仍然是所期望的)。
应对nan时,你想要发生的情况必须被作为特殊情况处理,但你所做的必须取决于你期望从代码中得到的数字。
在上面的示例中,(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之前是否发生。
1 通过使用x!= x检查nan有时是不可靠的(一些优化编译器会删除x!= x,从而破坏IEEE合规性,特别是启用-ffast-math开关时)。

谢谢您指出这一点;像这样编程肯定会有助于解决问题。但下次请尽量不要滥用文本格式功能。频繁切换字体大小、粗细和样式会让阅读变得非常困难。 - Magnus
4
注意,0.0/0.0并不是唯一可能导致NaN的操作。负数的平方根也会返回NaN。正无穷数的余弦值也会返回NaN。当x不在范围[0,pi]内时,acos(x)操作也会返回NaN。简言之,我们必须特别小心地注意这些潜在风险操作,而不仅仅关注于0.0/0.0。 - Boris Dalstein
完全同意Boris的观点。根据我的经验,NaN几乎总是来自于类似sqrt(-1.302e-53)这样的情况,即将接近零的中间计算结果输入到sqrt中而没有检查是否为负数。 - hans_meine
2
“防止NaN”意味着您需要进入所有基本算术运算,而不仅仅是除法。您需要注意∞/∞、0 * ∞、∞%x、x%0、∞-∞、0^0、∞^0等许多情况。对于这些基本算术运算采取“预防性”措施意味着您将完全破坏性能(并且可能会错过您没有考虑到的其他情况)。 - Steve Hollasch

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