为什么这两个代码变体产生不同的浮点结果? [英] Why does these two code variants produce different floating-point results?

查看:199
本文介绍了为什么这两个代码变体产生不同的浮点结果?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给出这个例子的C ++代码片段:

  void floatSurprise()
{
//来自某种计算
int a = 18680,b = 3323524,c = 121;
float m = float(a)/ c;

//变体1:计算单个表达式的结果
float r1 = b - (2.0f * m * a)+(m * m * c);
cout<< r1 =<< r1<< ENDL;

//变体2:将表达式分解为中间部分,
///然后计算
float
r2_p1 = 2.0f * m * a,
r2_p2 = m * m * c,
r2 = b - r2_p1 + r2_p2;

cout<< r2 =<< r2< ENDL;



$ b $ p
$ b $输出是:


= 439703

dev2 = 439702

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



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

我很惊讶,用这么简单的一段代码就可以轻松地拍摄自己的脚了。任何有识之士将不胜感激!编译器是VC ++ 2010。

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

 模板< typename T> inline T sqr(const T& arg){return arg * arg;即使我问得很好,编译器显然没有内联,并分别计算出这个值,在将值返回给表达式之前修剪结果,再次歪曲结果。 

解决方案

您应该阅读我长久以来对C#中相同事情发生的回答:
$ b

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



总结:首先,只用浮点数就可以精确到小数点后七位。在整个计算过程中,对于439702.51239669的确切算术,正确的答案就是439702.51239669 ...所以在这两种情况下,考虑到浮点数的限制,您都会接近正确答案。



但是,这并不能解释为什么你得到不同的结果,看起来像完全相同的计算。答案是:编译器可以广泛地使你的数学更精确,显然你已经遇到了两种情况:优化器在逻辑上使用相同的表达式,而不是优化它们到相同的代码。

无论如何,请仔细阅读我对C#的回答。那里的一切都适用于C ++。


Given this example C++ code snippet:

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

The output is:

dev1 = 439703
dev2 = 439702

When viewed in the debugger, the values are actually 439702.50 and 439702.25, respectively, which is interesting in itself - not sure why iostream prints floats without the fractional part by default. EDIT: The reason for this was that the default precision setting for cout was too low, needed cout << setprecision(7) at least to see the decimal point for numbers of this magnitude.

But I'm even more interested in why am I getting different results. I suppose it has to do with rounding and some subtle interplay of ints with the required float output type, but I can't put my finger on it. Which value is the correct one?

I was amazed that it was so easy to shoot myself in the foot with such a simple piece of code. Any insight will be greatly appreciated! The compiler was VC++2010.

EDIT2: I did some more investigating using a spreadsheet to generate "correct" values for the intermediate variables and found (via tracing) that indeed they were being trimmed, contributing to the precision loss in the ultimate result. I also found a problem with the single expression, because I actually used a handy function for calculating squares instead of m * m there:

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

Even though I asked nicely, the compiler apparently didn't inline this, and calculated the value separately, trimming the result before returning the value to the expression, skewing the result yet again. Ouch.

解决方案

You should read my long, long answer about why the same thing happens in C#:

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

Summing up: first of all, you only get about seven decimal places of accuracy with float. The correct answer were you do to it with exact arithmetic throughout the entire calculation is about 439702.51239669... so you are getting darn close to the correct answer considering the limitations of a float, in either case.

But that doesn't explain why you are getting different results with what looks like exactly the same calculations. The answer is: the compiler is permitted wide lattitude to make your math more accurate, and apparently you have hit upon two cases where the optimizer takes what is logically the same expression and does not optimize them down to the same code.

Anyway, read my answer regarding C# carefully; everything in there applies to C++ just as well.

这篇关于为什么这两个代码变体产生不同的浮点结果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆