NAN 差异 - std::nan vs quiet_NaN() vs Macro NAN

NAN Differences - std::nan vs quiet_NaN() vs Macro NAN

我想知道以下 nan 类型之间的区别是什么。除了 NAN_macro 的视觉差异(计算结果为 -nan(ind) 而不是 nan),它们的行为似乎都相同(根据下面的示例脚本)。

我看了一些其他答案,例如What is difference between quiet NaN and signaling NaN?。但我仍然不太明白为什么 NAN-nan(ind)std::numeric_limits<double>::quiet_NaN()nan,或者为什么我们有 std::nan("")std::nan("1") 如果在归根结底,它们似乎是同一个概念。

任何解释或澄清链接都会很棒。

#include <cmath>
#include <limits>

int main()
{
    using num_lim = std::numeric_limits<double>;

    const double NAN_macro   = static_cast<double>(NAN);  // -nan(ind)
    const double NAN_quiet   = num_lim::quiet_NaN();      // nan
    const double NAN_sig     = num_lim::signaling_NaN();  // nan
    const double NAN_str     = std::nan("");              // nan
    const double NAN_str1    = std::nan("1");             // nan
    const double NAN_strLong = std::nan("some string");   // nan

    const bool isnan_macro   = std::isnan(NAN_macro);    // true
    const bool isnan_quiet   = std::isnan(NAN_quiet);    // true
    const bool isnan_sig     = std::isnan(NAN_sig);      // true
    const bool isnan_str     = std::isnan(NAN_str);      // true
    const bool isnan_str1    = std::isnan(NAN_str1);     // true
    const bool isnan_strLong = std::isnan(NAN_strLong);  // true

    const bool not_equal_macro   = (NAN_macro   != NAN_macro);   // true
    const bool not_equal_quiet   = (NAN_quiet   != NAN_quiet);   // true
    const bool not_equal_sig     = (NAN_sig     != NAN_sig);     // true
    const bool not_equal_str     = (NAN_str     != NAN_str);     // true
    const bool not_equal_str1    = (NAN_str1    != NAN_str1);    // true
    const bool not_equal_strLong = (NAN_strLong != NAN_strLong); // true

    const double sum_macro   = 123.456 + NAN_macro;    // -nan(ind)
    const double sum_quiet   = 123.456 + NAN_quiet;    // nan
    const double sum_sig     = 123.456 + NAN_sig;      // nan
    const double sum_str     = 123.456 + NAN_str;      // nan
    const double sum_str1    = 123.456 + NAN_str1;     // nan
    const double sum_strLong = 123.456 + NAN_strLong;  // nan
}

IEEE 标准 754 表示 NaN 的位模式具有全 1 的指数和非零小数(请注意,所有浮点十进制值均由 "sign" 表示,一个"exponent" 和 a"fraction"),那么,您可以表示很多不同的 NaN,因为 "a non-zero fraction" 可能有很多不同的值。

要突出显示您的 NaN 表示,请使用以下内容扩展您的代码:

#include <cmath>
#include <limits>
#include <bitset>
#include <iostream>

union udouble {
  double d;
  unsigned long long u;
};

void Display(double doubleValue, char* what)
{
    udouble ud;
    ud.d = doubleValue;
    std::bitset<sizeof(double) * 8> b(ud.u);
    std::cout << "BitSet : " << b.to_string() << " for " << what << std::endl;
}

int main()
{
    using num_lim = std::numeric_limits<double>;

    const double NAN_macro   = static_cast<double>(NAN);  // -nan(ind)
    const double NAN_quiet   = num_lim::quiet_NaN();      // nan
    const double NAN_sig     = num_lim::signaling_NaN();  // nan
    const double NAN_str     = std::nan("");              // nan
    const double NAN_str1    = std::nan("1");             // nan
    const double NAN_strLong = std::nan("some string");   // nan

    ...

    Display( NAN_macro, "NAN_macro" );
    Display( NAN_quiet, "NAN_quiet" );
    Display( NAN_sig, "NAN_sig" );
    Display( NAN_str, "NAN_str" );
    Display( NAN_str1, "NAN_str1" );
    Display( NAN_strLong, "NAN_strLong" );
}

即使它有一些 UB(可以修复,请参阅 Ruslan 评论),它确实可以说明我在下面解释的内容),该程序输出:

BitSet : 0111111111111000000000000000000000000000000000000000000000000000 for NAN_macro
BitSet : 0111111111111000000000000000000000000000000000000000000000000000 for NAN_quiet
BitSet : 0111111111110100000000000000000000000000000000000000000000000000 for NAN_sig
BitSet : 0111111111111000000000000000000000000000000000000000000000000000 for NAN_str
BitSet : 0111111111111000000000000000000000000000000000000000000000000001 for NAN_str1
BitSet : 0111111111111000000000000000000000000000000000000000000000000000 for NAN_strLong

他们都有:

  • “0”作为符号(NAN_macro 可能会得到“1”,因为你将其报告为 -nan,我没有)。我使用了 g++,我敢打赌 NAN 宏可能被某些编译器定义不同,我怀疑这是 C++ 标准的一部分。
  • “11111111111”作为指数
  • 然后 "fraction" 的不同值...实际上是任何值,但绝不会只有零,否则它将不再是 NaN。

实际上,此 "fraction" 或 "payload"(请参阅 Ruslan 评论)值可用于存储您想要存储的任何信息(如 "why was there a nan here"?),这是叫,NaN-boxing

这就是为什么您在某些时候可能具有 "different" NaN 值的主要原因(quiet_NaN, signaling_NaN 或您创建的任何 NaN 符合 IEEE 标准 754...即使具有不同的内存表示,它们都是 NaN 值(x!=xstd::isnan(x)==true)。

所以,回答你的问题:

why NAN is -nan(ind) whereas std::numeric_limits<double>::quiet_NaN() is nan

可能是因为您的编译器这样定义了 NAN 宏,使用其他编译器可能会有所不同。顺便说一句,它就像 min/max 宏,即使定义它们的想法不好,也不要使用它们,更喜欢 std 函数,它们是标准的一部分,因此应该与任何您使用的编译器。

why we have std::nan("") and std::nan("1") if at the end of the day they both seem to be the same concept.

也许 "to help you play with NaN-boxing" 可能是一个答案,即使我怀疑这些函数是为特定目的而创建的。正确的答案可能只是“如果您需要不同于 std::quiet_NaNstd::signaling_NaN 的东西,让您决定要为 NaN 使用什么 "fraction" 值”

来源: https://steve.hollasch.net/cgindex/coding/ieeefloat.html

还使用输出NaN内存表示。