gcc精确错误?

时间:2009-08-26 23:03:25

标签: gcc precision

我只能假设这是一个错误。第一个断言通过而第二个失败:

double sum_1 =  4.0 + 6.3;
assert(sum_1 == 4.0 + 6.3);

double t1 = 4.0, t2 = 6.3;

double sum_2 =  t1 + t2;
assert(sum_2 == t1 + t2);

如果不是错误,为什么?

7 个答案:

答案 0 :(得分:13)

您正在比较浮点数。不要这样做,浮点数在某些情况下具有固有的精度误差。取而代之的是,取两个值的差值的绝对值,并断言该值小于某个小数字(epsilon)。

void CompareFloats( double d1, double d2, double epsilon )
{
    assert( abs( d1 - d2 ) < epsilon );
} 

这与编译器无关,而是与浮点数的实现方式有关。这是IEEE规范:

http://www.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF

答案 1 :(得分:12)

这也是困扰我的东西。

是的,由于舍入误差,永远不应该对浮点数进行相等比较,你可能知道这一点。

但在这种情况下,您正在计算t1+t2,然后再次计算它。 肯定必须产生相同的结果?

这是可能发生的事情。我敢打赌你在x86 CPU上运行它,对吗? x86 FPU的内部寄存器使用80位,但存储器中的值存储为64位双精度数。

所以t1+t2首先用80位精度计算,然后 - 我推测 - 以{64}的精度存储到sum_2的存储器中 - 并且发生一些舍入。对于断言,它被加载回浮点寄存器,再次以80位精度再次计算t1+t2。所以现在你将sum_2与先前舍入到64位浮点值的t1+t2进行比较,4.0+6.3是以更高的精度(80位)计算的 - 这就是为什么值不是完全相同。

编辑那么为什么第一次测试通过?在这种情况下,编译器可能在编译时评估// t1 = 4.0 fldl LC3 fstpl -16(%ebp) // t2 = 6.3 fldl LC4 fstpl -24(%ebp) // sum_2 = t1+t2 fldl -16(%ebp) faddl -24(%ebp) fstpl -32(%ebp) // Compute t1+t2 again fldl -16(%ebp) faddl -24(%ebp) // Load sum_2 from memory and compare fldl -32(%ebp) fxch %st(1) fucompp 并将其存储为64位数量 - 用于赋值和断言。所以正在比较相同的值,断言传递。

第二次编辑这是为代码的第二部分(gcc,x86)生成的汇编代码,带有注释 - 几乎遵循上面概述的场景:

-O3

有趣的附注:这是在没有优化的情况下编译的。当使用{{1}}编译时,编译器会优化所有代码。

答案 2 :(得分:3)

我在我的Intel Core 2 Duo上复制了你的问题,我查看了汇编代码。这是正在发生的事情:当您的编译器评估t1 + t2时,它会

load t1 into an 80-bit register
load t2 into an 80-bit register
compute the 80-bit sum

当它存储到sum_2时,它会

round the 80-bit sum to a 64-bit number and store it

然后==比较将80位和与64位和进行比较,它们是不同的,主要是因为小数部分0.3无法使用二进制浮点数精确表示,所以你正在比较已被截断的两个不同长度的'重复十进制'(实际重复二进制)。

如果您使用gcc -O1gcc -O2进行编译,那么令人恼火的是,gcc在编译时执行了错误的算法,问题就消失了。根据标准,这可能没问题,但这只是gcc不是我最喜欢的编译器的另一个原因。


P.S。当我说==将80位和与64位和进行比较时,当然我的意思是它比较了64位和的扩展版本。你可能会认真思考

sum_2 == t1 + t2

解析为

extend(sum_2) == extend(t1) + extend(t2)

sum_2 = t1 + t2

解析为

sum_2 = round(extend(t1) + extend(t2))

欢迎来到漂浮的精彩世界!

答案 3 :(得分:3)

当比较接近的浮点数时,通常要测量它们的相对差异,定义为

if (abs(x) != 0 || abs(y) != 0) 
   rel_diff (x, y) = abs((x - y) / max(abs(x),abs(y))
else
   rel_diff(x,y) = max(abs(x),abs(y))

例如,

rel_diff(1.12345, 1.12367)   = 0.000195787019
rel_diff(112345.0, 112367.0) = 0.000195787019
rel_diff(112345E100, 112367E100) = 0.000195787019

这个想法是衡量这些数字的共同点的有效数字;如果您使用-log10为0.000195787019,则会得到3.70821611,这大约是所有示例共有的前导基数10位数。

如果您需要确定两个浮点数是否相等,您应该执行类似

的操作
if (rel_diff(x,y) < error_factor * machine_epsilon()) then
  print "equal\n";

其中,机器epsilon是可以在所使用的浮点硬件的尾数中保存的最小数字。大多数计算机语言都有函数调用来获取此值。 error_factor应基于您认为在数字x和y的计算中舍入错误(和其他)所消耗的有效位数。例如,如果我知道x和y是大约1000个求和的结果,并且不知道求和的数字的任何界限,我会将error_factor设置为大约100。

尝试将这些添加为链接但不能,因为这是我的第一篇文章:

  • en.wikipedia.org/wiki/Relative_difference
  • en.wikipedia.org/wiki/Machine_epsilon
  • en.wikipedia.org/wiki/Significand(尾数)
  • en.wikipedia.org/wiki/Rounding_error

答案 4 :(得分:2)

在其中一种情况下,最终可能会将64位双精度值与80位内部寄存器进行比较。看看GCC为这两种情况发出的汇编指令可能很有启发......

答案 5 :(得分:1)

双精度数字的比较本质上是不准确的。例如,您经常可以找到0.0 == 0.0返回 false 。这是由于FPU存储和跟踪数字的方式。

Wikipedia says

  

测试平等是有问题的。两个在数学上相等的计算序列可能会产生不同的浮点值。

您需要使用增量来为比较提供容差,而不是精确值。

答案 6 :(得分:0)

使用以下选项可以“修复”此“问题”:

-msse2 -mfpmath = sse

如本页所述:

http://www.network-theory.co.uk/docs/gccintro/gccintro_70.html

一旦我使用了这些选项,两个断言都通过了。