【问题标题】:Checking if a double (or float) is NaN in C++在 C++ 中检查双精度(或浮点数)是否为 NaN
【发布时间】:2010-10-08 21:53:40
【问题描述】:

有 isnan() 函数吗?

PS.:我在 MinGW(如果这有什么不同的话)。

我通过使用 <math.h> 中的 isnan() 解决了这个问题,<cmath> 中不存在这个问题,我最初是 #includeing。

【问题讨论】:

  • 我不纯你可以便携。谁说 C++ 需要 IEEE754?
  • 请注意,1 盎司的预防胜于 1 磅的治疗。换句话说,防止 0.f/0.f 被执行比在代码中追溯检查 nan 好得多。 nan 可能会对您的程序造成极大的破坏,如果允许扩散,它可能会引入难以发现的错误。这是因为nan 有毒,(5*nan=nan),nan 不等于任何东西(nan != nan),nan 不大于任何东西(@987654335 @ !> 0),nan 不小于任何东西 (nan!
  • @bobobobo:这是一个功能,允许集中错误检查。就像异常与返回值一样。
  • 为什么 没有 isnan()?它在 std::

标签: c++ double nan


【解决方案1】:

IEEE 标准说 当指数全部为1s 和 尾数不为零, 号码是NaN。 Double 是1 符号位、11 指数位和52 尾数位。 稍微检查一下。

【讨论】:

    【解决方案2】:

    这会在 Visual Studio 中检测无穷大和 NaN,方法是检查它是否在双重限制内:

    //#include <float.h>
    double x, y = -1.1; x = sqrt(y);
    if (x >= DBL_MIN && x <= DBL_MAX )
        cout << "DETECTOR-2 of errors FAILS" << endl;
    else
        cout << "DETECTOR-2 of errors OK" << endl;
    

    【讨论】:

    • 更仔细地检查FLT_MINDBL_MINLDBL_MIN的定义。这些被定义为每种类型的最小 标准化 值。例如,单精度具有超过 800 万个大于零且小于 FLT_MIN(并且不是 NaN)的合法 denorm 值。
    【解决方案3】:

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

    #include <cmath>
    

    由于此功能是 C99 的一部分,因此并非随处可用。如果您的供应商不提供该功能,您也可以定义自己的变体以实现兼容性。

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

    【讨论】:

    • 我用的是 里面没有 isnan!顺便我发现 is 中有一个 isnan
    • 正如我所说,这是 C99 的一部分。由于 C99 不是任何当前 C++ 标准的一部分,因此我提供了替代方案。但由于 isnan() 很可能会包含在即将发布的 C++ 标准中,因此我在其周围放置了一个 #ifndef 指令。
    【解决方案4】:

    第一个解决方案:如果您使用的是 C++11

    自从提出这个问题以来,有一些新的发展:重要的是要知道std::isnan() 是 C++11 的一部分

    概要

    在标题&lt;cmath&gt;中定义

    bool isnan( float arg ); (since C++11)
    bool isnan( double arg ); (since C++11)
    bool isnan( long double arg ); (since C++11)
    

    确定给定的浮点数 arg 是否不是数字 (NaN)。

    参数

    arg:浮点值

    返回值

    true 如果 arg 是 NaN,则 false 否则

    参考

    http://en.cppreference.com/w/cpp/numeric/math/isnan

    请注意,如果您使用 g++,这与 -fast-math 不兼容,请参阅下面的其他建议。


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

    对于 C99,在 C 中,这被实现为一个宏 isnan(c),它返回一个 int 值。 x 的类型应为 float、double 或 long double。

    各种供应商可能包含或不包含函数isnan()

    检查NaN 的所谓可移植方法是使用NaN 不等于自身的IEEE 754 属性:即x == x 将是错误的xNaN

    但是最后一个选项可能不适用于每个编译器和某些设置(特别是优化设置),所以在最后的选择中,您始终可以检查位模式...

    【讨论】:

    • 绝对值得被接受的答案,值得更多的支持。感谢您的提示
    • −1 std::isnan 截至 2017 年 2 月仍然是一个不好的建议,因为它不适用于 g++ 的浮点优化。
    • @Cheersandhth.-Alf:这个选项符合 IEEE 标准吗?答案已编辑
    • @BlueTrin:x != xisnan 都需要符合 IEEE 754 标准。关于后者,IEEE 754-2008 标准规定“实现应为所有支持的算术格式提供以下非计算操作”和“当且仅当 x 为 NaN 时 isNaN(x) 为真”。为了检查符合性,该标准需要is754version1985()is754version2008(),其中C++ 提供std::numeric_limits&lt;Fp&gt;::is_iec559()(IEC 559 是相同的标准)。不幸的是 -ffast-math 优化,例如g++ 声称符合但不符合要求。
    • 警告:isnan(x) 不适用于 gcc 和 clang 中的选项 -ffinite-math-only
    【解决方案5】:

    从 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 &lt; 0) == (value &gt;= 0))。结果证明它在 Visual C++ 上运行良好,但在 g++ 的 -ffast-math 选项下失败了。只有直接位模式测试才能可靠地工作。

    【讨论】:

      【解决方案6】:

      有三种“官方”方式:posixisnan、c++0xisnan函数模板、或者visual c++_isnan功能

      不幸的是,要检测使用哪些是相当不切实际的。

      不幸的是,没有可靠的方法来检测您是否具有带有 NaN 的 IEEE 754 表示。标准库提供了官方的这种方式(numeric_limits&lt;double&gt;::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> 键入“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> 一个 && 回声工作... ||回声!失败 作品... C:\test> gnuc x.cpp --fast-math C:\test> 一个 && 回声工作... ||回声!失败 断言失败:a != b,文件 x.cpp,第 6 行 此应用程序已请求运行时以不寻常的方式终止它。 请联系应用程序的支持团队以获取更多信息。 !失败的 C:\测试> _

      【讨论】:

      • @Alf:您的示例在 4.0 和 4.5 之间的各种 g++ 版本上的 Mac OS X 和 Linux 上都按预期工作。 -ffast-math 选项的文档明确指出,如果 IEEE 或 ISO 规则/规范用于数学函数,它可能会导致依赖于精确实现的程序输出不正确。如果不启用该选项,使用 x != x 是测试 NaN 的一种完全有效且可移植的方法。
      • @Adam:您缺少的是 C++ 标准不需要 IEEE 表示或浮点数的数学。至于手册页告诉你,gcc -ffast-math 仍然是一个符合 C++ 的实现(好吧,假设它得到 numeric_limits::is_iec559 正确,它是,尽管 Alf 在上面建议它没有):依赖 IEEE 的 C++ 代码是 不是可移植的 C++ 并且无权期望实现提供它。
      • 阿尔夫说得对,对 gcc 4.3.4 和 is_iec559 的快速测试在 -ffast-math 上是正确的。所以这里的问题是 GCC 的 -ffast-math 文档只说它是非 IEEE/ISO 的数学函数,而他们应该说它是非 C++,因为它的 numeric_limits 实现是无聊。我猜想 GCC 在定义模板时并不总是能判断出最终的后端是否真的有符合要求的浮点数,所以甚至没有尝试。 IIRC 在 GCC 的 C99 一致性的突出错误列表中有类似的问题。
      • @Alf,@Steve,我不知道 C++ 标准没有关于浮点值的规范。这对我来说非常令人震惊。将 IEEE 754 和 NaN 作为特定于平台的扩展而不是标准处理看起来更好。不是吗?我可以期待 C++0x 中添加的任何类型的 isnan() 或 IEEE754 吗?
      • @Eonil:C++0x 仍然有例如“浮点类型的值表示是实现定义的”。 C 和 C++ 都旨在支持在没有浮点硬件的机器上实现,并且正确的 IEEE 754 浮点数可能比合理准确的替代方案要慢得多。理论上,如果您需要 IEEE,您可以断言 is_iec559,但实际上这似乎不适用于 GCC。 C++0x 确实有一个 isnan 函数,但是由于 GCC 现在没有正确实现 is_iec559,我想它也不会在 C++0x 中,而且 -ffast-math 很可能会破坏它的 isnan
      【解决方案7】:

      在我看来,最好的真正跨平台方法是使用联合并测试双精度的位模式以检查 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 变量,然后使用该辅助变量进行测试。
      【解决方案8】:

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

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

      数字不能同时为 = 0,因此实际上只有当数字既不小于、也不大于或等于零时,此检查才会通过。基本上根本就不是数字,也就是 NaN。

      如果您愿意,也可以使用它:

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

      我不确定这如何受 -ffast-math 影响,因此您的里程可能会有所不同。

      【讨论】:

      • 这实际上是有缺陷的,就像f != f 也有缺陷一样。我已经看到 llvm 优化了几乎相同的一段代码。优化器可以传播有关第一次比较的信息,并确定如果第一次比较是真的,那么第二次比较可能永远不会为真。 (如果编译器严格遵守 IEEE 规则,f != f 无论如何都会简单得多)
      • 不适用于 g++ 的 -ffast-math 选项。适用于 Visual C++。请参阅 (stackoverflow.com/a/42138465/464581)。
      【解决方案9】:
      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 表示。
      • 请注意,此转换违反了 g++ 的严格别名规则,并且已知编译器在检测到正式 UB 时会执行 Unmentionable Things™。使用 g++ 而不是有效的强制转换,您需要使用memcpy,通过一个字节数组来确定。 Code for that in my #2 answer.
      【解决方案10】:

      这行得通:

      #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

      【讨论】:

        【解决方案11】:

        纳米预防

        我对这个问题的回答是不要对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 是代码稳定性的可怕克星,必须非常小心地检测和预防1nan与普通数字不同的属性:

        • nan 有毒,(5*nan=nan)
        • nan 不等于任何东西,甚至不等于它自己 (nan != nan)
        • nan 不大于任何东西 (nan !> 0)
        • nan 不比什么都少 (nan!

        列出的最后 2 个属性是反逻辑的,并且会导致依赖于与 nan 数字比较的代码的奇怪行为(倒数第三个属性也是奇数,但您可能永远不会看到 @987654341 @ 在您的代码中(除非您正在检查 nan(不可靠))。

        在我自己的代码中,我注意到nan 值往往会产生难以发现的错误。 (注意inf-inf 的情况不是。(-inf TRUE,(0 inf) 返回 TRUE,甚至 (@ 987654348@ inf) 返回 TRUE。因此,根据我的经验,代码的行为通常仍如所愿)。

        nan下做什么

        您希望在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(实际上具有非常好的/非破坏性行为,如上所述)。

        记住,integer division by 0 causes a runtime exception。因此,您必须始终检查整数除以 0。仅仅因为 0.0/0.0 悄悄地计算为 nan 并不意味着您可以偷懒而不在发生之前检查 0.0/0.0

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

        【讨论】:

        • 感谢您指出这一点;像这样的编程肯定会帮助解决这样的问题。但下一次,请尽量不要滥用文本格式功能太多。像这样切换字体大小、粗细和样式真的很难阅读。
        • 请注意,0.0/0.0 并不是唯一可能导致 NaN 的操作。负数的平方根返回 NaN。 +infinity 的余弦也返回 NaN。 x 不在 [0,pi] 范围内的操作 acos(x) 也可能导致 NaN。简而言之,必须格外小心地查看这些潜在风险操作,而不仅仅是 0.0/0.0。
        • 完全同意鲍里斯的观点。以我的经验,NaN 实际上总是来自类似 sqrt(-1.302e-53) 之类的东西,即接近于零的中间计算结果被输入 sqrt 而不检查负数。
        • “防止 NaNs”意味着您需要了解所有基本的算术运算,而不仅仅是除法。您需要注意 ∞/∞、0 * ∞、∞ % x、x % 0、∞ - ∞、0^0、∞^0 等等。使用此类基本算术运算“预防性”意味着您将完全降低您的性能(并且可能会错过您没有想到的其他情况)。
        【解决方案12】:

        上面的 cmets 声明 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”)

        【讨论】:

        • 如果 a = 234324.0f 会不会导致缓冲区溢出?
        • 是的,如果 a=FLT_MAX,则为 340282346638528859811704183484516925440.000。他必须使用char s[7]; sprintf(s, "%.0g", a);,如果a=-FLT_MAX-3e+38,则为6 个字符
        【解决方案13】:

        对我来说,解决方案可能是一个宏,使其显式内联,从而足够快。 它也适用于任何浮点类型。它基于这样一个事实,即值不等于自身的唯一情况是该值不是数字。

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

        【讨论】:

        • 这是这个问题的最佳答案之一!谢谢分享。
        • 其他答案表明,如果设置了 -ffast-math 选项,这可能会失败。
        【解决方案14】:

        根据 IEEE 标准,NaN 值具有奇怪的属性,即涉及它们的比较总是为假。也就是说,对于浮点数 f,如果 f 为 NaN,f != f 为真。

        请注意,正如下面的一些 cmets 所指出的,并非所有编译器在优化代码时都尊重这一点。

        对于任何声称使用 IEEE 浮点的编译器,这个技巧应该有效。但我不能保证它在实践中发挥作用。如有疑问,请咨询您的编译器。

        【讨论】:

        • 如果在 IEEE 模式下运行,编译器最好不要删除它。当然,请检查编译器的文档...
        • -1 仅在理论上有效,在实践中无效:诸如 g++(带有 -fastmath)之类的编译器搞砸了。在 c++0x 之前,唯一通用的方法是测试 bitpattern。
        • @Alf:-ffast-math 选项的文档明确指出,如果 IEEE 或 ISO 规则/规范用于数学函数,它可能会导致依赖于精确实现的程序输出不正确。如果不启用该选项,使用 x != x 是测试 NaN 的一种完全有效且可移植的方法。
        • @Adam:文档确实公开声明它不合格,是的。是的,我之前也遇到过这个论点,并与 Gabriel Dos Reis 详细讨论过这个问题。它通常用于在循环论证中为设计辩护(我不知道您是否打算与之相关,但值得了解——这是火焰战争的东西)。您认为x != x 在没有该选项的情况下有效的结论不符合逻辑。对于特定版本的 g++ 可能是真的,也可能不是。无论如何,您通常无法保证不会使用 fastmath 选项。
        • @Alf:不,我不知道您与 Gabriel Dos Reis 的讨论。 Steve Jessop 在另一个关于假设 IEEE 代表的问题中提出了很好的观点。如果您假设 IEEE 754 并且编译器以一致的方式运行(即没有 -ffast-math 选项),那么 x != x 是一个有效且可移植的解决方案。您甚至可以通过测试__FAST_MATH__ 宏来测试-ffast-math,并在这种情况下切换到不同的实现(例如,使用联合和位旋转)。
        【解决方案15】:

        不依赖于特定 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 位表示,因此您需要添加更多比较。 :)
        【解决方案16】:

        以下代码使用 NAN 的定义(所有指数位设置,至少一个小数位设置)并假设 sizeof(int) = sizeof(float) = 4。您可以在 Wikipedia 中查找 NAN 以了解详细信息。

        bool IsNan( float value ) { return ((*(UINT*)&value) & 0x7fffffff) > 0x7f800000; }

        【讨论】:

        • 我相信这也适用于大端平台。文字0x7fffffff 将简单地作为ff ff ff 7f 存在于内存中。 value0x7f800000 具有相同的顺序,因此所有操作都排成一行(没有交换字节)。如果有人可以在大端平台上进行测试,我会很感兴趣。
        • 0x7fff1234 也是一个 NaN。 0xffffffff 也是如此
        【解决方案17】:

        Boost 中还有一个 header-only library,它拥有处理浮点数据类型的简洁工具

        #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 发行版上编译)。
        • 如果您使用选项 --fast-math 进行编译,则此功能将无法按预期工作。
        【解决方案18】:

        当前 C++ 标准库中没有可用的 isnan() 函数。它是在C99 中引入的,并被定义为macro 而不是函数。 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:: 命名空间中,尽管有许多实现(即 GCC 4 + 在 Linux 上或在 Mac OS X 10.5+ 上的 XCode 中)将它们直接注入到 std::,因此 std::isnan 定义明确。

        此外,C++ 的某些实现仍然使 C99 isnan() 宏可用于 C++(包括在 cmathmath.h 中),这可能会引起更多的混淆,开发人员可能会认为这是一种标准行为。

        关于 Viusal C++ 的说明,如上所述,它既不提供 std::isnan 也不提供 std::tr1::isnan,但它提供了定义为 _isnan() 的扩展函数,该函数自 Visual C++ 6.0 以来一直可用

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

        【讨论】:

        • 每个人都想要这些函数,比如 isNan 或 isInfinity。为什么负责人不简单地列入他们的标准???? - 我将尝试找出如何负责并为此投票。说真的。
        • @shuhalo 负责了吗?
        • 这个答案应该更新,因为std::isnan 现在是 C++11 标准的一部分并且支持已经扩展。 std::isnan 从 Visual Studio 2013 开始在 Visual Studio 中实现。也许@shuhalo 负责 :-)
        【解决方案19】:

        您可以使用limits 标准库中定义的numeric_limits&lt;float&gt;::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++ 进行了测试。

        【讨论】:

        • 但请注意——GCC 3.2.3 版中的 numeric_limits 似乎存在错误,因为它为 quiet_NaN 返回 0.0。根据我的经验,更高版本的 GCC 还可以。
        • @Nathan:很高兴知道。我使用的是 4.3.2 版本,所以我已经走出了困境。
        【解决方案20】:

        如果您的编译器支持 c99 扩展,则有一个 std::isnan,但我不确定 mingw 是否支持。

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

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

        【讨论】:

        • 这样做时,编译器有机会优化比较,始终返回 true。
        • 不,没有。执行此操作的编译器已损坏。您不妨说标准库isnan 有可能返回错误的结果。从技术上讲,编译器可能有问题,但在实践中,不会发生。与var != var 相同。它之所以有效,是因为 IEEE 浮点值是这样定义的。
        • 如果设置了 -ffast-math,isnan() 将无法为 gcc 返回正确的结果。当然,这种优化被记录为破坏了 IEEE 语义......
        • 如果设置了 -ffast-math,则编译器有问题。或者更确切地说,如果设置了 -ffast-math,所有的赌注都没有了,你无论如何都不能依赖 NaN。
        猜你喜欢
        • 2021-07-02
        • 2011-11-17
        • 2021-10-27
        • 1970-01-01
        • 2011-04-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多