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

时间:2013-03-19 01:39:01

标签: c++ floating-point

给出这个示例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&lt;&lt; setprecision(7)至少可以看到这个数量的数字的小数点。

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

我很惊讶用这么简单的代码拍摄自己很容易。任何见解将不胜感激!编译器是VC ++ 2010。

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

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

即使我很好地问,编译器显然没有内联这个,并分别计算了值,在将值返回到表达式之前修剪结果,再次偏移结果。哎哟。

1 个答案:

答案 0 :(得分:11)

你应该阅读我长期以来对C#中发生同样事情的答案:

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

总结:首先,使用float只能获得大约7位小数的​​精度。正确的答案是你在整个计算过程中使用精确算术进行的是大约439702.51239669 ...因此,考虑到浮点数的限制,在任何一种情况下,你都会接近正确的答案。

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

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