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

我的问题不是浮动精度。 这是为什么Equals()==不同。

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

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

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

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

为什么十点十分之十​​在浮点运算中并不总是等于十分之三呢?

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

 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,因为有点太大了。 r得到正确的答案,因为y的太大的大小被x的太小取消,结果是正确的。

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

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

 b = 4.00000 / 7.00000; 

b将是0.57143,从0.571428571的真实值向上取整。如果我们到了八个位置,那就是0.57142857,这个位置的误差幅度远远小得多,但却是相反的。 它向下舍入。

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

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

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

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

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

好的,我同意所有浮点scheme都会由于表示错误而引入不准确的错误,而且这些错误有时可以根据计算中使用的精度位数相互累积或相互抵消。 我们至less有保证这些不准确性是一致的吗?

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

编译器和运行时允许这样做, 但是他们当时感觉就像这样 。 从机器到机器,从运行到运行等都不一定是一致的。 由于这只能使计算更准确,这不被视为一个错误。 这是一个function。 一个function,使得难以置信的编写可预测的程序,但一个function。

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

是的。

那么比较0.1 + 0.2 == 0.3(0.1 + 0.2).Equals(0.3)呢?

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

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

正确。

通常向C#编译器团队报告的方式是某人有一些expression式,当他们在debugging中编译时产生true,而在发布模式下编译时产生错误。 这是最常见的情况,因为debugging和发布代码生成会改变寄存器分配scheme。 但编译器只要selecttrue或false,就可以用这个expression式来做任何事情。 (它不能产生编译时错误。)

这是疯狂。

正确。

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

不是我,那是确定的。

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

我如何确保一致的结果?

如前所述,使用decimaltypes。 或者用整数做所有的math。

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

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

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

否。 运行时保证存储到数组或字段中截断。 C#规范不保证身份转换被截断,但Microsoft实现具有回归testing,以确保每个新版本的编译器都有这种行为。

关于这个问题,所有的语言规范都要说浮点运算可以在执行的时候以更高的精度执行。

当你写

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

实际上 ,这些并不完全是0.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中decimal,float和double之间的区别?以及在.NET中 处理浮点错误 ),但是我build议你阅读一个叫cool的文章;

What Every Computer Scientist Should Know About Floating-Point Arithmetic

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

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

检查演示

提示 :当我写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 

FWIW下面的testing通过

 float x = 0.1f + 0.2f; float result = 0.3f; bool isTrue = x.Equals(result); bool isTrue2 = x == result; Assert.IsTrue(isTrue); Assert.IsTrue(isTrue2); 

所以问题实际上就是这条线

0.1f + 0.2f == 0.3f

正如所说的可能是编译器/个人电脑特定

我认为目前为止,大多数人都是从错误的angular度来看待这个问题

更新:

我想另一个好奇的考验

 const float f1 = .1f + .2f; const float f2 = .3f; Assert.AreEqual(f1, f2); passes Assert.IsTrue(f1==f2); doesnt pass 

单一平等执行:

 public bool Equals(float obj) { return ((obj == this) || (IsNaN(obj) && IsNaN(this))); } 

==是关于比较确切的浮点数值。

Equals是一个可以返回true或false的布尔方法。 具体实施可能会有所不同。