clang和gcc之间的浮点运算结果不一致

Far*_*ley 0 c floating-point gcc clang

在OSX 10.10和ubuntu 14.04上单独编译和运行.

#include<float.h>
#include<math.h>
#include<stdio.h>

void testAtan() {
  float temp1 = 62981764.0000000000000000f;
  float temp2 = (2.14859168E8f  *  atanf(temp1));
  printf("temp2: %.16f\n", temp2);
}

int main() {
  printf("FLT_EVAL_METHOD=%d\n", FLT_EVAL_METHOD);
  testAtan();
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

在OS X上,它会打印出来

FLT_EVAL_METHOD=0
temp2: 337499968.0000000000000000
Run Code Online (Sandbox Code Playgroud)

在ubuntu上,它打印出来

FLT_EVAL_METHOD=0
temp2: 337500000.0000000000000000
Run Code Online (Sandbox Code Playgroud)

任何想法证明这一点以及如何使结果保持一致?

Pas*_*uoq 8

您正在调用一个库函数,atanfIEEE 754标准不要求它实现如此准确,以至于它将为所有实现产生相同的结果.

大多数实现具有高于0.5 ULP的分数的准确度,但是对于难以舍入的结果(实际结果接近两个浮点之间的中点的结果)而言,这仍然是足够的.例如,如果实际结果是浮点f1在f2方向上的0.4 ULP,则实现可以返回f1而另一个返回f2,它们仍然可以精确到0.6 ULP(这是非常好但不常见).

如果您希望在任何地方获得相同的结果,您应该合并自己的实现atanf,仅由基本的IEEE 754操作组成.然后,它将在为基本操作(即大多数编译平台)提供IEEE 754语义的所有编译平台上产生相同的结果.这就是Java所做的使浮点基本函数结果可重现的原因:它在"netlib"实现上进行标准化.如果您设法在您希望定位的其他平台上编译它,您可以使用Stephen Canon指出的Apple实现:与OS X数学库中的许多其他功能一样,它提供了出色的标准兼容性,并且非常好的交易 -在准确性和速度之间.

您还必须使用任何"正确舍入"的数学库,然后结果将与任何其他正确舍入的数学库相同,因为对于任何参数的基本函数的任何应用,只有一个正确舍入的结果.一个正确舍入的库是CRlibm,但关键是你可以使用任何其他库并获得与CRlibm相同的结果.CRlibm只提供双精度函数,但是如果任何单精度标准函数的任何参数在正确舍入到单精度时产生不同的结果而不是正确舍入到双精度然后舍入到单精度,我会非常惊讶-精确.

编辑:

在传递给单精度反正切函数的大参数的特定情况下,还有一个原因可以解释为什么实现可以自愿选择除计算得到的最精确结果之外的结果:实现可能认为最好使函数始终返回-π/ 2和π/ 2之间的结果.对于非常大的参数,实际结果接近π/ 2,并且最接近π/ 2的单精度浮点近似恰好高于π/ 2.atanf在这些情况下,选择在π/ 2下面立即返回浮点数的一些实现,而其他实现可以选择返回上面(并且最接近)π/ 2的浮点数.我在一篇博客文章中对此进行了讨论(但是我的观点很简单:我不太使用浮点数,所以我的意见无关紧要).博客文章是在双精度的背景下构建的,但实际上,在双精度的情况下,我们很幸运(对于函数的特殊情况atan):最接近doubleπ/ 2的近似值恰好低于它,因此没有选择实际上是必要的.

  • 这通常是一个很好的答案,但是对于"atanf(巨大)"来说,这是一个迷人的特殊情况,值得一些额外的细节.数学上精确的结果是(pi/2 - tiny),它由两个单精度值括起来,L <atan(巨大)<U.U(由glibc产生的结果)稍微接近数学上精确的结果,所以它似乎最初是两者中更正确的.但是,U> pi/2,因此返回U不满足结果在区间[-pi/2,pi/2]中的约束.这就是我们选择在OS X数学库中返回L的原因. (4认同)
  • @StephenCanon啊是的,我没有注意到这个问题.我很久以前写过这篇文章,其优点是可以指向其他文档片段:http://blog.frama-c.com/index.php?post/2013/03/03/correct-rounding-or-数学上正确的舍入 (2认同)