为什么NaN - NaN == 0.0与英特尔C++编译器?

ima*_*ett 299 c c++ floating-point icc ieee-754

众所周知,NaNs在算术中传播,但我找不到任何演示,所以我写了一个小测试:

#include <limits>
#include <cstdio>

int main(int argc, char* argv[]) {
    float qNaN = std::numeric_limits<float>::quiet_NaN();

    float neg = -qNaN;

    float sub1 = 6.0f - qNaN;
    float sub2 = qNaN - 6.0f;
    float sub3 = qNaN - qNaN;

    float add1 = 6.0f + qNaN;
    float add2 = qNaN + qNaN;

    float div1 = 6.0f / qNaN;
    float div2 = qNaN / 6.0f;
    float div3 = qNaN / qNaN;

    float mul1 = 6.0f * qNaN;
    float mul2 = qNaN * qNaN;

    printf(
        "neg: %f\nsub: %f %f %f\nadd: %f %f\ndiv: %f %f %f\nmul: %f %f\n",
        neg, sub1,sub2,sub3, add1,add2, div1,div2,div3, mul1,mul2
    );

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这个例子(在这里运行)基本上产生了我所期望的(负面有点奇怪,但它有点意义):

neg: -nan
sub: nan nan nan
add: nan nan
div: nan nan nan
mul: nan nan
Run Code Online (Sandbox Code Playgroud)

MSVC 2015产生类似的东西.但是,英特尔C++ 15产生:

neg: -nan(ind)
sub: nan nan 0.000000
add: nan nan
div: nan nan nan
mul: nan nan
Run Code Online (Sandbox Code Playgroud)

具体来说,qNaN - qNaN == 0.0.

这......不可能是对的,对吗?相关标准(ISO C,ISO C++,IEEE 754)对此有何评论,为什么编译器之间的行为存在差异?

Pet*_*lin 299

在英特尔C++编译器是默认的浮点处理/fp:fast,它处理NaN的不安全(这也导致在NaN == NaNtrue例如).尝试指定/fp:strict/fp:precise查看是否有帮助.

  • 我想支持英特尔决定默认为`/ fp:fast`:如果你想要_safe_,你应该更好地避免NaN首先出现,并且通常不要使用`==`浮动 - 点数.依靠IEEE754分配给NaN的奇怪语义要求麻烦. (67认同)
  • NaN具有重要用途 - 它们可以检测异常情况,而无需在每次计算后进行测试.并非每个浮点开发人员都需要它们,但不要忽略它们. (21认同)
  • 我自己就是这样做的.实际上,指定精确或严格修复问题. (15认同)
  • @leftaroundabout:你对NaN感到奇怪,除了恕我直言,有可能让NaN做出决定!= NaN回归真实? (10认同)
  • @supercat出于好奇,你是否同意让'NaN == NaN`返回'false`的决定? (6认同)
  • @KyleStrand:不,但如果NaN!= NaN返回true,代码至少可以使用倒置逻辑和!=来防范某些问题.真正需要的是(并且由于一些奇怪的原因*仍然*通常不可用)是执行完全有序的关系比较的标准化有效方法.虽然有时NaN无序是有用的,但还有其他时候,例如在尝试对数字列表进行排序时,会对所有内容造成严重破坏.如果我正在设计一种语言,我会有多种类型的float和double,它们是可以转换的... (6认同)
  • @leftaroundabout:你有没有写过数字代码?花式类型系统不会为您节省开支.处理NaNs的方式经过精心设计,因此您仍然可以检查错误,但不必在任何地方使用明确的,很少采用的条件来污染您的代码. (5认同)
  • @supercat NaNs是无序的,在科学计算中很重要,以保留不确定形式的正确语义.我猜这个用例比包含NaN的排序列表的效率更常见/更重要. (4认同)
  • @ user168715:BTW,我也认为应该有四个零值:零(*任何*加或减零自身产生),正和负无穷小(乘法或除法下溢的结果)和无符号无穷小(结果)减法产生零结果).由有符号的无穷小除法应给出+/- inf,但除以零或无符号无穷小应该得到NaN.这样的安排将使所有操作在正数和负数之间适当对称. (4认同)
  • @leftaroundabout我同意tmyklebu这种类型推断在这里没用.(几乎)没有人在编译时编写NaN:它们是由于复杂计算的多次迭代过程中的下溢/溢出而产生的,并且预测它们比运行程序本身更容易.例如,考虑三体问题的模拟:如果三个物体中的任何两个物体彼此太靠近,则会出现NaN(由于力的溢出与$ 1/r ^ 2 $成比例)但当然是三个-body问题是混沌系统的典型例子,无法在编译时进行分析. (3认同)
  • ...但是使用了不同的NaN比较语义(只允许在具有相同语义的值之间进行操作,除非非NaN常量可以自由转换为任何其他形式); 我有"通用"类型使用完全排名的运算符. (2认同)
  • @supercat:IEEE 754-2008(或者你标点它)确实建议实现提供总顺序和通常的浮点比较.不幸的是,他们无法为C和C++委员会规定语法.我相信它也会推荐`sinpi`和`cospi`和朋友为你的第二次投诉.但我同意; 可惜它一开始并不存在. (2认同)
  • @leftaroundabout对,但不幸的是NaNs在实践中如何出现.人们可能会想到一个代码,为三体问题(为了效率)需要大量时间步骤进行数值积分,并定期检查NaNs和其他问题,如果检测到,则重新计算时间(杰斐逊的时间扭曲)并重新运行那些处理身体碰撞的特殊(更昂贵)处理的迭代. (2认同)

oua*_*uah 53

这个 ...不能对,对吧?我的问题:相关标准(ISO C,ISO C++,IEEE 754)对此有何看法?

Petr Abdulin已经回答了编译器给出0.0答案的原因.

以下是IEEE-754:2008所说的内容:

(6.2使用NaN的操作)"[...]对于具有安静NaN输入的操作,除了最大和最小操作之外,如果要传递浮点结果,结果应该是一个安静的NaN,它应该是一个输入NaN."

所以减去两个安静的NaN操作数的唯一有效结果是一个安静的NaN; 任何其他结果无效.

C标准说:

(C11,F.9.2表达式转换p1)"[...]

x - x→0.0"如果x是NaN或无穷大,则表达式x - x和0 0不相等"

(其中NaN表示根据F.2.1p1的安静NaN"此规范没有定义信号NaN的行为.它通常使用术语NaN来表示安静的NaN")


zwo*_*wol 20

由于我看到一个答案违反了英特尔编译器的标准符合性,并且没有其他人提到这一点,我将指出GCC和Clang都有一种模式,他们做了类似的事情.它们的默认行为符合IEEE标准 -

$ g++ -O2 test.cc && ./a.out 
neg: -nan
sub: nan nan nan
add: nan nan
div: nan nan nan
mul: nan nan

$ clang++ -O2 test.cc && ./a.out 
neg: -nan
sub: -nan nan nan
add: nan nan
div: nan nan nan
mul: nan nan
Run Code Online (Sandbox Code Playgroud)

- 但如果你以牺牲正确性为代价来要求速度,那么你得到你所要求的 -

$ g++ -O2 -ffast-math test.cc && ./a.out 
neg: -nan
sub: nan nan 0.000000
add: nan nan
div: nan nan 1.000000
mul: nan nan

$ clang++ -O2 -ffast-math test.cc && ./a.out 
neg: -nan
sub: -nan nan 0.000000
add: nan nan
div: nan nan nan
mul: nan nan
Run Code Online (Sandbox Code Playgroud)

我认为批评ICC的默认选择是完全公平的,但我不会将整个Unix战争重新纳入该决定.

  • 只是为了在火中投入燃料,我非常喜欢英特尔的选择,默认情况下启用快速数学运算.使用浮动的全部意义是获得高吞吐量. (4认同)