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

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);

如果不是错误,为什么?

有帮助吗?

解决方案

这是东西咬我了。

是,浮点数不应该,因为舍入误差的平等相比,你可能知道的。

但在这种情况下,你计算t1+t2,然后再计算它。 当然具有以产生相同的结果?

下面就是可能是怎么回事。我敢打赌,你在x86 CPU上运行此,正确吗?在x86 FPU使用80位为它的内部寄存器,但在存储器中的值被存储为64位双精度。

所以t1+t2首先计算与80个比特的精度,然后 - 我相信 - 存储出到存储器中sum_2与精度64位的 - 并且发生一些舍入。对于断言,它装回一个浮点寄存器,并且t1+t2与80个比特的精度再次计算,一次。所以,现在你要比较sum_2,这是以前四舍五入到64位浮点值,与t1+t2,其计算精度更高(80位) - 这就是为什么值不完全相同

修改的那么,为什么第一次测试通过?在这种情况下,编译器可能评估在编译的时候并将其存储为一个64位的量4.0+6.3 - 无论对于分配和断言。所以相同的值被比较,并且断言通过。

第二编辑以下是适用于代码(GCC,86)的第二部分生成的汇编代码,和评论 - 几乎如下上面概述的情形:

// 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

有意思的是:这没有优化的编译。当它与-O3编译,编译器优化的所有的代码了。

其他提示

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

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

这有什么好做的编译器和一切与浮点数的实现方式。这里是IEEE规格:

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

我重复我的英特尔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))

欢迎到浮点的奇妙世界!

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

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

这个想法是测量数字共有的前导有效数字的数量;如果您取 0.000195787019 的 -log10,您将得到 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/有效数字(尾数)
  • en.wikipedia.org/wiki/Rounding_error

可能的情况是在其中一种情况下,你最终进行比较的64位双到一个80位内部寄存器。这可能是有启发性看组装说明GCC发射的两个病例...

双精度数的比较是固有地不准确的。举例来说,你可以经常发现0.0 == 0.0返回的的。这是由于这样的方式存储FPU和轨道号。

维基说

  

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

您将需要使用的增量给予容忍你的比较,而不是精确值。

此“问题”可以通过使用这些选项“固定”:

-msse2 -mfpmath = SSE

,因为这页的说明:

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

在我使用这些选项,都断言通过。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top