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);
如果不是一个 bug,那是什么原因呢?
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);
你正在比较浮点数。不要这样做,因为浮点数在某些情况下存在固有的精度误差。相反,获取两个值的差的绝对值,并断言该值小于一些小数字(epsilon)。
void CompareFloats( double d1, double d2, double epsilon )
{
assert( abs( d1 - d2 ) < epsilon );
}
这与编译器无关,而与浮点数实现方式有关。以下是IEEE规范:
http://www.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF
// 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
进行编译时,编译器会优化所有代码。
4.0 + 6.3
是一个表达式,由编译器常量折叠为10.3。因此,第一个assert等同于assert(10.3 == 10.3)
,它会轻松通过。在第二个测试中,它实际上将4.0放入double中,将6.3放入double中(失去了一点精度),将它们相加,并将其与常量10.3进行比较,由于它们之间的差异约为2^-70,所以失败了。 :) - hobbs我已经在我的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 -O1
或gcc -O2
编译器编译,gcc在编译时会出错,但问题会消失。也许这符合标准,但这只是gcc不是我最喜欢的编译器的又一个原因。 ==
将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";
也许在其中一个情况下,您会将64位双精度浮点数与80位内部寄存器进行比较。查看GCC为这两种情况发出的汇编指令可能很有启发性...
双精度数的比较本质上是不准确的。例如,你经常会发现0.0 == 0.0
返回false。这是由于FPU存储和跟踪数字的方式。
测试相等性是有问题的。两个在数学上相等的计算序列可能会产生不同的浮点值。
您需要使用一个delta来给出比较的容差,而不是一个精确的值。
这个“问题”可以通过使用以下选项来“修复”:
-msse2 -mfpmath=sse
如此页面所解释的那样:
http://www.network-theory.co.uk/docs/gccintro/gccintro_70.html
一旦我使用了这些选项,两个断言都通过了。