C# (.1f+;.2f==0.3f)!=(.1f+;.2f).等于(.3f)为什么?

C# (.1f+;.2f==0.3f)!=(.1f+;.2f).等于(.3f)为什么?,c#,equality,floating-accuracy,C#,Equality,Floating Accuracy,我的问题不是关于浮动精度的。这是关于为什么Equals()不同于= 我理解为什么.1f+.2f==0.3f是假(而.1m+.2m==0.3m是真 我知道==是引用,.Equals()是值比较。(编辑:我知道还有更多。) 但是为什么(.1f+.2f).Equals(.3f)为真,而(.1d+.2d).Equals(.3d)仍然为假 .1f + .2f == .3f; // false (.1f + .2f).Equals(.3f); // true (.1

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

我理解为什么
.1f+.2f==0.3f
(而
.1m+.2m==0.3m

我知道
==
是引用,
.Equals()
是值比较。(编辑:我知道还有更多。)

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

 .1f + .2f == .3f;              // false
(.1f + .2f).Equals(.3f);        // true
(.1d + .2d).Equals(.3d);        // false
当你写作时

double a = 0.1d;
double b = 0.2d;
double c = 0.3d;
实际上,它们并不完全是
0.1
0.2
0.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
这里有很多问题,比如(和),但我建议你们读一篇很酷的文章,叫做

好吧,leppie更符合逻辑。实际情况是,完全取决于
编译器
/
计算机
cpu

基于leppie代码,此代码在我的Visual Studio 2010和Linqpad上工作,结果是
True
/
False
,但当我尝试它时,结果将是
True
/
True

检查

提示:当我编写
Console.WriteLine(.1f+.2f==.3f)时重新竖琴警告我

使用相等运算符比较浮点数。可能的 舍入值时失去精度


如评论中所述,这是由于编译器执行常量传播并以更高的精度执行计算(我认为这取决于CPU)

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

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

  const float f1 = .1f + .2f;
  const float f2 = .3f;
  Console.WriteLine(f1 == f2); // prints false

FWIW以下测试通过

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

如前所述,它可能是特定于编译器/pc的

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

更新:

我想这是另一个奇怪的测试

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

这个问题措辞混乱。让我们把它分解成许多小问题:

为什么在浮点运算中,十分之一加十分之二并不总是等于十分之三

让我给你打个比方。假设我们有一个数学系统,其中所有数字都精确到小数点后五位。假设你说:

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使用二进制数字,那么如何获得精确的十进制算术计算呢

如果需要精确的十进制数学,则使用
十进制
类型;它使用十进制分数,而不是二进制分数。你付出的代价是我
b = 4.00000 / 7.00000;