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

Why does these two code variants produce different floating-point results?

本文关键字:结果 代码 两个 为什么      更新时间:2023-10-16

给出这个示例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默认打印没有小数部分的浮点数。EDIT:原因是cout的默认精度设置太低,需要cout <<设置精度(7)至少可以看到这个数量级的数字的小数点

但我更感兴趣的是为什么我得到不同的结果。我想它与舍入和一些微妙的int与所需的float输出类型的相互作用有关,但我不能指出它。哪个值是正确的?

我很惊讶,这么简单的一段代码竟然会搬起石头砸自己的脚。任何见解将非常感激!编译器为vc++ 2010。

EDIT2:我使用电子表格进行了更多的调查,以生成中间变量的"正确"值,并发现(通过跟踪)它们确实被修剪,导致最终结果的精度损失。我还发现了单个表达式的问题,因为我实际上使用了一个方便的函数来计算平方,而不是m * m:

template<typename T> inline T sqr(const T &arg) { return arg*arg; }

尽管我很好地问了,编译器显然没有内联这个,并且单独计算值,在将值返回给表达式之前修剪结果,再次扭曲结果。哎哟。

你应该看看我关于为什么同样的事情发生在c#中的长篇大论的回答:

(.1f+.2f==.3f) != (.1f+.2f). = (.3f)为什么?

总结一下:首先,使用float只能得到小数点后7位的精度。如果你在整个计算过程中进行精确的算术,正确的答案大约是439702.51239669……因此,考虑到浮点数的局限性,无论哪种情况,你都非常接近正确答案。

但这并不能解释为什么看起来完全相同的计算会得到不同的结果。答案是:编译器被允许在很大程度上使你的数学更精确,显然你遇到了两种情况,优化器采用逻辑上相同的表达式,但没有将它们优化到相同的代码。

无论如何,请仔细阅读我关于c#的回答;