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

输出是:

开发1 = 439703
开发2 = 439702

在调试器中查看时,这些值实际上分别是 439702.50 和 439702.25,这本身就很有趣——不知道为什么 iostream 默认打印没有小数部分的浮点数。编辑:原因是 cout 的默认精度设置太低,至少需要 cout << setprecision(7) 才能看到这个数量级的小数点。

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

我很惊讶,用这么简单的一段代码就可以轻易地击中自己的脚。任何见解将不胜感激!编译器是 VC++2010。

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

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

即使我问得很好,编译器显然没有内联它,而是单独计算值,在将值返回给表达式之前修剪结果,再次扭曲结果。哎哟。

4

1 回答 1

11

您应该阅读我关于为什么在 C# 中发生同样事情的长长的回答:

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

总结:首先,浮点数只能精确到小数点后七位。正确的答案是你在整个计算过程中用精确的算术来做大约 439702.51239669 ......所以考虑到浮点数的限制,无论哪种情况,你都非常接近正确的答案。

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

无论如何,请仔细阅读我关于 C# 的回答;那里的所有内容也适用于 C++。

于 2013-03-19T01:49:54.613 回答