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


300

众所周知,NaN是以算术方式传播的,但是我找不到任何演示,因此我写了一个小测试:

#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;
}

该示例(在此处实时运行)基本上产生了我所期望的(否定有点怪异,但有点道理):

neg: -nan
sub: nan nan nan
add: nan nan
div: nan nan nan
mul: nan nan

MSVC 2015产生类似的结果。但是,英特尔C ++ 15会产生:

neg: -nan(ind)
sub: nan nan 0.000000
add: nan nan
div: nan nan nan
mul: nan nan

具体来说,qNaN - qNaN == 0.0

这个...不可能是吧?有关标准(ISO C,ISO C ++,IEEE 754)对此有何评论?为什么编译器之间的行为有所不同?


18
Javascript和Python(numpy)没有这种行为。 Nan-NaNNaN。Perl和Scala的行为也类似。
保罗

33
也许您启用了不安全的数学优化(相当于-ffast-mathgcc)?
Matteo Italia

5
@nm:不是。附件F支持的时候,也有必要拥有指定浮点行为,这是可选的,但规范性可言,基本上是采用了IEEE 754到C.
R.,GitHub上停止帮助ICE

5
如果您想询问有关IEEE 754标准的问题,请在问题中的某处提及。
n。代词

68
确定这个问题与标题中的JavaScript有关。
MikeTheLiar 2015年

Answers:


300

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


15
我只是自己尝试一下。确实,指定精确或严格即可解决此问题。
imallett,2015年

67
我想支持Intel决定默认为/fp:fast:如果您想要安全的东西,则最好避免NaN首先出现,并且通常不使用==浮点数。依靠IEEE754分配给NaN的怪异语义要求麻烦。
大约

10
@leftaroundabout:除了恕我直言,做出NaN!= NaN返回true的可怕决定之外,您对NaN有什么奇怪的发现?
2015年

21
NaN具有重要用途-在每次计算后无需进行测试即可检测异常情况。并非每个浮点开发人员都需要它们,但不要解雇它们。
布鲁斯·道森

6
@supercat出于好奇,您是否同意NaN==NaN退货的决定false
Kyle Strand

53

这个 。。。不能对吧?我的问题是:有关标准(ISO C,ISO C ++,IEEE 754)对此有何评论?

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

这是IEEE-754:2008所说的:

(6.2使用NaN的操作)“ [...]对于使用静默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”)


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

—但是如果您以牺牲正确性为代价要求速度,那么您会得到所要求的—

$ 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

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


请注意,使用-ffast-mathgcc关于浮点运算不再符合ISO 9899:2011。
2015年

1
@FUZxxl是的,关键是两个编译器都具有不兼容的浮点模式,只是icc默认为该模式,而gcc没有。
zwol

4
仅仅为了加油,我真的很喜欢英特尔默认启用快速数学的选择。使用浮点数的全部目的是获得高吞吐量。
纳文
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.