为什么这两个代码变量会产生不同的浮点数结果?

9

考虑下面这段C++的示例代码:

void floatSurprise()
{
    // these come from some sort of calculation
    int a = 18680, b = 3323524, c = 121;
    float m = float(a) / c;

    // variant 1: calculate result from single expression
    float r1 = b - (2.0f * m * a) + (m * m * c);
    cout << "r1 = " << r1 << endl;

    // variant 2: break up the expression into intermediate parts, 
    /// then calculate 
    float
        r2_p1 = 2.0f * m * a,
        r2_p2 = m * m * c,
        r2 = b - r2_p1 + r2_p2;

    cout << "r2 = " << r2 << endl;
}

输出结果为:

dev1 = 439703
dev2 = 439702

在调试器中查看时,实际值分别为439702.50和439702.25,这本身很有趣 - 不确定为什么iostream默认情况下打印浮点数时不带小数部分。编辑:原因是cout的默认精度设置太低,需要至少使用cout<<setprecision(7)来看到此数量级的小数点。
但我更想知道为什么会得到不同的结果。我想这与舍入以及整型与所需浮点输出类型的微妙相互作用有关,但我无法明确指出问题所在。哪个值是正确的?
我很惊讶一个简单的代码片段竟然可以让我弄巧成拙。任何见解都将不胜感激!编译器是VC++2010。 编辑2:我使用电子表格生成了中间变量的“正确”值进行了更多调查,并通过跟踪发现它们的确被削减了,从而导致最终结果失去精度。我还发现了单个表达式的问题,因为我实际上在那里使用了一个方便的用于计算平方的函数,而不是m * m
template<typename T> inline T sqr(const T &arg) { return arg*arg; }

尽管我已经礼貌地请求了编译器,但它显然没有内联处理这个操作,而是单独计算了值,然后在将值返回表达式之前修剪了结果,再次扭曲了结果。哎呀。

1
不是对你问题的答案,但是你应该优先选择使用“double”而不是“float”,因为精度通常更好。你总会遇到像这样的特殊情况(这就是浮点运算的痛苦之处),但误差会小得多。 - syam
1
@neuviemeporte:修剪发生在最后,这就解释了不同的结果:在第一种情况下,您一次性以本地大小进行所有计算,并仅修剪一次,但在第二种情况下,您修剪中间值,那些修剪值被重复使用于最终计算,因此会产生额外的误差。 - syam
@syam,Eric:哇,我没有想到这个角度。可惜从我的角度来看,0.25是正确的,因为它与其他计算值混合后可以得到良好的结果。看起来我在别处有一个bug... :/ - neuviemeporte
只是一个小修正:FP寄存器的宽度为80位,而不是84位。 :) - neuviemeporte
@EricPostpischil:啊,我不知道那个。好知道。CLR只在将赋值分配给静态字段、实例字段或数组元素时需要修剪;对本地变量的赋值可以保留更高的精度。而*C#*从不需要修剪;规范指出,计算可以随时以任何原因使用更高的精度进行。 - Eric Lippert
显示剩余4条评论
1个回答

11

你应该阅读我关于为什么在 C# 中发生同样情况的长篇解释:

(.1f+.2f==.3f) != (.1f+.2f).Equals(.3f) Why?

总结一下:首先,使用 float 类型只能获得大约七个小数位的精度。如果您在整个计算过程中使用精确的算术运算,则正确答案大约为 439702.51239669... 因此,在限制 float 的情况下,无论哪种情况,您都已经非常接近正确答案。

但这并不能解释为什么您在看起来完全相同的计算中获得不同的结果。答案是:编译器允许进行广泛的优化,以使您的数学运算更加准确,显然您已经碰到了两种情况,其中优化器将逻辑上相同的表达式不优化为相同的代码。

总之,请仔细阅读我关于 C# 的回答;里面的所有内容同样适用于 C++。


谢谢你提供的0.512...值 - 我变得有点偏执,甚至不相信Windows计算器。想想我是否应该相信你.... ;) - neuviemeporte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接