【问题标题】:gcc rounding difference between versionsgcc 版本之间的舍入差异
【发布时间】:2012-01-10 11:50:56
【问题描述】:

我正在调查why a test case is failing

有问题的测试可以简化为执行(4.0/9.0) ** (1.0/2.6),将其四舍五入为 6 位并检查已知值(作为字符串):

#include<stdio.h>
#include<math.h>
int main(){
    printf("%.06f\n", powf(4.0/9.0, (1.0/2.6)));
}

如果我在 Linux 上的 gcc 4.1.2 中编译并运行它,我会得到:

0.732057

Python 同意,Wolfram|Alpha 也同意:

$ python2.7 -c 'print "%.06f" % (4.0/9.0)**(1/2.6)'
0.732057

但是,我在 Linux 上的 gcc 4.4.0 和 OS X 上的 4.2.1 上得到以下结果:

0.732058

double 行为相同(尽管我没有对此进行广泛测试)

我不确定如何进一步缩小范围。这是 gcc 回归吗?舍入算法的变化?我做了什么傻事?

编辑:将结果打印为 12 位,第 7 位的数字是 4 vs 5,这解释了舍入差异,但不是值差异:

gcc 4.1.2:

0.732057452202

gcc 4.4.0:

0.732057511806

这是两个版本的gcc -S 输出:https://gist.github.com/1588729

【问题讨论】:

  • 要删除行为不同的 printf() 作为变量,打印或检查保存该值的内存位,因此您删除了故事的“转换为字符串”部分。
  • 这两者之间的区别正是 32 位处理器的 machine epsilon。而且gcc4.4的结果更接近表达式的实际值。

标签: c++ c gcc floating-point precision


【解决方案1】:

最近的 gcc 版本能够使用 mfpr 进行编译时浮点计算。我的猜测是您最近的 gcc 会这样做,并为编译时版本使用更高的精度。这是至少 C99 标准所允许的(如果它被修改,我没有查看其他标准)

C99 中的 6.3.1.8/2

浮动操作数的值和浮动表达式的结果可能是 以比类型要求更高的精度和范围表示;类型不是 从而改变了。

编辑:您的 gcc -S 结果证实了这一点。我还没有检查计算,但旧的有(在用内存代替它的常量内容之后)

movss 1053092943, %xmm1
movss 1055100473, %xmm0
call powf

使用 4/9.0 和 1/2.6 的预计算值调用 powf,然后在提升为 double 后打印结果,而新的只是打印提升为 double 的浮点 0x3f3b681f。

【讨论】:

  • 有趣,这听起来像是原因!您能否详细说明反汇编中的内容? (一件事 - 较旧的 gcc4.1.2 可能使用了 mfpr - 它的结果与其他来源相匹配,包括丹尼尔的回答)
【解决方案2】:

我认为旧的 gcc 在引擎盖下使用了 double。在 Haskell 中进行计算并以全精度打印结果,我得到了

Prelude Text.FShow.RealFloat> FD ((4/9) ** (1/2.6))
0.73205748476369969512944635425810702145099639892578125
Prelude Text.FShow.RealFloat> FF ((4/9) ** (1/2.6))
0.732057511806488037109375

所以double 结果与 gcc-4.1.2 产生的结果一致,float 结果与 gcc-4.4.0 的结果一致。结果 gcc-4.5.1 在此处为 float resp 生成。 double 同意 Haskell 的结果。

正如 A Programmer 所引用的,允许编译器使用更高的精度,旧的 gcc 可以,新的显然没有。

【讨论】:

    【解决方案3】:

    这里有很多玩家。 Gcc 很可能只是将计算转发到您的浮点处理器;你可以检查反汇编。

    您可以使用二进制表示检查二进制结果(来自同一 wolfram/alpha):

    float q=powf(4.0/9.0, (1.0/2.6));
    unsigned long long hex=*reinterpret_cast<unsigned long long*>(&q);
    unsigned long long reference=0x1f683b3f;
    assert( hex==reference );
    

    printf 也可能是罪魁祸首:该数字的十进制表示也可能是问题所在。你可以尝试写printf("%0.06f", 0.73205748 ); 来测试一下。

    【讨论】:

      【解决方案4】:

      您应该能够通过打印更多(所有)有效数字来区分舍入不同的格式和给出不同答案的数学。

      如果不进行舍入时看起​​来相同,则printf("%0.6f" 只是舍入不同。


      好的,用我手头的旧Linux+python环境,我明白了:

      Python 2.4.3 (#1, Jun 11 2009, 14:09:37)
      [GCC 4.1.2 20080704 (Red Hat 4.1.2-44)] on linux2
      Type "help", "copyright", "credits" or "license" for more information.
      >>> (4.0/9.0)**(1.0/2.6)
      0.7320574847636997
      

      这又不一样了。

      也许更简单地问一下,有多少有效数字对于这个单元测试是真正重要的?

      【讨论】:

      • 更新了答案,将值打印为 12 位数字 - 第 7 位的值在 4 和 5 之间变化,因此 printf 始终四舍五入
      • 您的结果四舍五入到 gcc 4.1.2 结果,round(0.7320574847636997, 6) == 0.732057。理想情况下,单元测试将精确到 6 位数,因为默认情况下以 LUT 格式存储多少位数,但我会看看是否可以将其减少到 5
      猜你喜欢
      • 2015-12-13
      • 2012-12-07
      • 2011-06-24
      • 1970-01-01
      • 2015-08-08
      • 2012-11-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多