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

我的问题不是浮动精度。 这是关于Equals()==不同的原因。

我明白为什么.1f + .2f == .3ffalse (而.1m + .2m == .3mtrue )。
我得到的==是引用和.Equals()是价值比较。 (编辑:我知道还有更多。)

但为什么(.1f + .2f).Equals(.3f) true ,而(.1d+.2d).Equals(.3d)仍然是false

 .1f + .2f == .3f;              // false
(.1f + .2f).Equals(.3f);        // true
(.1d + .2d).Equals(.3d);        // false

问题是措词混乱。 让我们把它分解成许多更小的问题:

为什么在浮点算术中,十分之十十分之十并不总是等于十分之三?

让我给你一个比喻。 假设我们有一个数学系统,所有的数字四舍五入到小数点后五位。 假设你说:

x = 1.00000 / 3.00000;

你会期望x是0.33333,对吧? 因为这是我们系统中最接近实际答案的数字。 现在假设你说过

y = 2.00000 / 3.00000;

你会希望y是0.66667,对吧? 因为再次,这是我们系统中最接近实际答案的数字。 0.66666比0.66667更远离三分之二。

请注意,在第一种情况下,我们向下取整,在第二种情况下,我们取整。

现在当我们说

q = x + x + x + x;
r = y + x + x;
s = y + y;

我们得到什么? 如果我们做了精确的算术,那么每一个都显然是四分之三,它们都是相等的。 但他们并不平等。 尽管1.33333是我们系统中最接近四分之三的数字,但只有r具有该值。

q是1.33332 - 因为x有点小,所以每个加法累积了这个错误,最终的结果有点太小了。 同样,s太大; 它是1.33334,因为y有点太大了。 r得到正确的答案,因为太大的y被x的太小而抵消,结果结果是正确的。

精度的位置数量是否会影响误差的大小和方向?

是; 更高的精度使得误差的大小更小,但是可以改变计算是否产生由于错误导致的损失或增益。 例如:

b = 4.00000 / 7.00000;

b将会是0.57143,它会从0.571428571的真实值上舍入...如果我们去了八个地方,那就是0.57142857,这个地方的误差远远小得多,但方向却相反; 它向下舍入。

因为改变精确度可以改变错误是每个单独计算中的收益还是损失,这可以改变给定的总计算错误是相互强化还是相互抵消。 最终的结果是,有时一个较低精度的计算结果比一个更高精度的计算结果更接近“真实”结果,因为在较低精度的计算中,您很幸运并且错误发生在不同的方向。

我们希望以更高的精度进行计算总能给出更接近真实答案的答案,但是这个论证却表明了另外一种观点。 这就解释了为什么有些时候浮点数的计算给出了“正确的”答案,但是双精度的计算 - 精度的两倍 - 给出了“错误的”答案,是正确的?

是的,这正是你的例子中发生的事情,除了我们有一定数量的二进制精度数字而不是五位数的小数精度。 正如三分之一不能准确地用五位数字或任何有限数字的十进制数字表示的那样,0.1,0.2和0.3不能用任何有限数量的二进制数字精确表示。 其中一些将被取整,其中一些将被取整,并且它们的增加是否会增加错误或取消错误取决于每个系统中有多少个二进制数字的具体细节。 也就是说,精度的改变可以改变或好转的答案。 通常精度越高,答案越接近真实答案,但并不总是如此。

那么如果float和double使用二进制数字,那么如何获得准确的十进制算术计算呢?

如果您需要精确的小数运算,则使用decimal类型; 它使用小数部分,而不是二进制部分。 你付出的代价是它相当大和更慢。 当然,正如我们已经看到的,像三分之一或四分之七的分数不会准确表示。 然而,实际上是小数部分的任何分数都将用零错误表示,最多可达约29位有效数字。

好的,我接受所有浮点方案都会由于表示错误而引入不准确,并且这些不准确性有时可能会根据计算中使用的精度位数彼此累加或取消。 我们是否至少有保证这些不准确性是一致的?

不,你没有这样的浮标或双打保证。 编译器和运行时都被允许以比规范要求的更高的精度执行浮点计算。 特别是,编译器和运行时允许以64位或80位或128位或任何大于32位的比特位进行单精度(32位)算术运算。

编译器和运行时允许这样做,但是他们当时感觉就像这样。 它们不需要从机器到机器,从运行到运行等都是一致的。 由于这只能使计算更准确,所以这不被视为一个错误。 这是一个功能。 一个功能使得编写可预测的程序非常困难,但仍然是一项功能。

那么这意味着在编译时执行的计算(如文字0.1 + 0.2)可以得到与使用变量在运行时执行的相同计算不同的结果吗?

是的。

比较0.1 + 0.2 == 0.3(0.1 + 0.2).Equals(0.3)怎么样?

由于第一个是由编译器计算出来的,第二个是由运行时计算出来的,我只是说他们可以随意使用比规范要求更高的精确度,可以给出不同的结果。 也许其中一个选择只以64位精度进行计算,而另一个选择80位或128位精度进行部分或全部计算,并得到不同的答案。

所以在这里待上一分钟。 你不仅说0.1 + 0.2 == 0.3可以不同于(0.1 + 0.2).Equals(0.3) 。 你说0.1 + 0.2 == 0.3在编译器的意外情况下完全可以被计算为真或假。 它可能在星期二产生,在星期四产生错误,它可能在一台机器上产生错误而在另一台机器上产生错误,如果表达式在同一程序中出现两次,它可能产生真假。 无论如何,这种表达可以具有任何价值; 编译器被允许在这里完全不可靠。

正确。

通常向C#编译器团队报告的方式是某人有一些表达式,当他们在调试中编译时产生true,而在发布模式下编译时产生错误。 这是最常见的情况,因为调试和发布代码生成会改变寄存器分配方案。 但只要选择true或false,编译器就可以对此表达式进行任何操作。 (它不能产生编译时错误。)

这是疯狂。

正确。

我应该为这个烂摊子责怪谁?

不是我,那是确定的。

英特尔决定制作一个浮点数学芯片,但要做出一致的结果要昂贵得多。 在编译器中,关于注册哪些操作与保留堆栈上的操作的小选择可能会增加结果的巨大差异。

我如何确保一致的结果?

如前所述,使用decimal类型。 或者用整数完成所有数学。

我必须使用双打或浮动; 我能做任何事情来鼓励一致的结果吗?

是。 如果将任何结果存储到任何静态字段中,则类型为float或double的类型或数组元素的任何实例字段将保证被截断回32或64位精度。 (这个保证明确地不是针对商店对当地人或者形式参数进行的。)另外,如果你对已经具有该类型的表达式执行了转换(float)(double) ,那么编译器将会发出特殊的代码,结果截断,就像它已被分配给一个字段或数组元素一样。 (在编译时执行的投射 - 即在常量表达式上投射 - 不保证这样做。)

为了澄清最后一点:C#语言规范是否做出了这些保证?

否。运行时保证存储到数组或字段中截断。 C#规范不保证标识转换截断,但Microsoft实现具有回归测试,确保编译器的每个新版本都具有此行为。

关于这个主题,所有语言规范都要说的是,浮点运算可以按照实现的精度以更高的精度执行。


当你写

double a = 0.1d;
double b = 0.2d;
double c = 0.3d;

实际上,这些是不完全0.10.20.3 。 来自IL代码;

  IL_0001:  ldc.r8     0.10000000000000001
  IL_000a:  stloc.0
  IL_000b:  ldc.r8     0.20000000000000001
  IL_0014:  stloc.1
  IL_0015:  ldc.r8     0.29999999999999999

在SO指出这个问题有很多问题,比如(在.NET中小数,浮点和双精度的区别?以及在.NET中处理浮点错误),但是我建议你阅读一个很酷的文章;

What Every Computer Scientist Should Know About Floating-Point Arithmetic

那么,莱皮说的更合乎逻辑。 真实情况在这里, 总体取决于 compiler / computercpu

基于leppie代码,此代码适用于我的Visual Studio 2010和Linqpad,结果为True / False ,但是当我在ideone.com上尝试时,结果将为True / True

检查DEMO

提示:当我编写Console.WriteLine(.1f + .2f == .3f); Resharper警告我;

浮点数与平等算子的比较。 舍入值时可能会失去精度。

在这里输入图像描述


正如评论所说,这是由于编译器不断传播并以更高的精度执行计算(我相信这取决于CPU)。

  var f1 = .1f + .2f;
  var f2 = .3f;
  Console.WriteLine(f1 == f2); // prints true (same as Equals)
  Console.WriteLine(.1f+.2f==.3f); // prints false (acts the same as double)

@Caramiriel还指出.1f+.2f==.3f在IL中被发射为false ,因此编译器在编译时进行了计算。

确认常量折叠/传播编译器优化

  const float f1 = .1f + .2f;
  const float f2 = .3f;
  Console.WriteLine(f1 == f2); // prints false
链接地址: http://www.djcxy.com/p/4719.html

上一篇: (.1f+.2f==.3f) != (.1f+.2f).Equals(.3f) Why?

下一篇: point maths query