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

2019-07-20 15:48发布

我的问题是不是浮动的精度。 这是为什么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

Answer 1:

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

为什么是它的十分之一加上十分之二并不总是等于十分之三的浮点运算?

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

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,这已远,错误的,但在相反的方向远远小于幅度; 它舍去。

因为改变精度可以改变一个错误是增益或每个单独计算损失,这可以改变一个给定的聚合计算的误差是否彼此加强或相互抵消。 最终的结果是,有时低精度计算比更高的精度计算更接近“真”的结果,因为在低精度计算你得到幸运,并且错误是在不同的方向。

我们希望,这样做更高精度的计算总是给出的答案更接近真实的答案,否则这种说法表示。 这就解释了为什么有时在彩车的计算给出了“正确”的答案,但在双打的计算 - 它具有两倍的精度 - 给出了“错误”的答案,正确吗?

是的,这正是发生在你的例子,除了分数精确度,与其五位数字,我们有一定数量的二进制精度数字。 正如三分之一不能准确地表示在5 - 或任何有限数量 - 十进制数字,0.1,0.2和0.3不能准确地在任何有限数量的二进制数位来表示。 一些将被围捕,其中一些将被舍去,而不论是否它们的添加增加了差错或抵消误差取决于许多二进制数字如何在每个系统的具体细节。 也就是说,在精密的改变可以改变的回答是好还是坏。 一般来说,精度越高,越接近答案是真正的答案,但并非总是如此。

我怎样才能得到准确的数字算术计算的话,如果float和double使用二进制数字?

如果您需要准确的十进制数学然后使用decimal类型; 它使用小数,而不是二进制小数。 你付出的代价是,这是相当大的慢。 当然,正如我们已经看到的,像三分之一或七分之四馏分不会被准确地表示。 任何分数,实际上是一个小数但是将与零误差来表示,最高约29显著数字。

行,我同意所有浮点方案介绍由于表示错误的不准确性,并且这些不准确性有时会积聚或相互抵消基于在计算中所使用的精度的比特数。 难道我们至少得到了保证,那些不准确将是一致的

不,你有浮筒或双打没有这样的保证。 编译器和运行时都允许在比由规范要求的精确度更高的执行浮点计算。 特别地,编译器和运行时被允许做单精度(32位)算术在64位或80位或128位或任何位数比他们喜欢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可以被计算为true或false完全由编译器的心血来潮。 它可以产生星期二真假星期四,它可以产生一台机器上真假另一个,如果表达式在同一个程序出现了两次就可以产生两个真假。 该表达式可以具有以任何理由任一值; 允许编译器是完全不可靠这里。

正确。

这通常是报告给C#编译器团队的方式是,有人当他们在调试编译错误,当他们在释放模式编译产生真正的一些表达。 这是最常见的情况是,由于调试和发布代码生成修改寄存器分配方案本作物了。 但允许编译器做这种表达任何它喜欢的,只要它选择真或假。 (那可不一定,说,产生编译时错误。)

这是疯狂。

正确。

我应该和谁应该为这混乱?

不是我,那是肯定织补。

英特尔决定做一个浮点运算芯片,它是远远做出一致的结果更加昂贵。 小选择在什么操作enregister VS什么操作,以保持在栈上最多可以添加到结果差异很大的编译器。

如何确保一致的结果?

使用decimal类型,正如我以前说过。 或做你的数学中的整数。

我必须用双打或浮筒; 我可以做任何事情来鼓励一致的结果?

是。 如果任何结果存储到任何静态字段 ,类型float或双然后可以保证的一类数组元素 任何实例字段被截断回32或64位精度。 (这保证明确为加盟店当地人或正式的参数进行。)此外,如果你做一个运行时强制转换为(float)(double)上已经是该类型的,则编译器会发出强制的特殊代码的表达结果截断,就好像它已被分配给一个字段或数组元素。 (其执行在编译时广播 - 也就是使人们对常量表达式 - 都不能保证做到这一点。)

要澄清的是最后一点:没有了C# 语言规范使这些保障?

号存储到一个数组或场截断运行时保证。 C#的规格并不能保证身份投截断但微软实现了回归测试,确保编译器的每一个新版本有此行为。

所有的语言规范有说关于这个问题的是,浮点运算可以在更高的精度在执行的自由裁量权进行。



Answer 2:

当你写

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指向这样一个问题LOF( 十进制,float和double在.NET之间的区别是什么?并与漂浮在.NET点处理错误 ,但我建议你阅读才叫爽条);

What Every Computer Scientist Should Know About Floating-Point Arithmetic

那么 ,什么leppie 说是更符合逻辑。 真实的情况是在这里, 完全以依赖compiler / computercpu

基于leppie代码,这个代码工作在我的Visual Studio 2010Linqpad,结果True / False ,但是当我试图在ideone.com ,其结果将是True / True

检查DEMO

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

浮动的比较点平等操作数。 可能的精度损失,而舍入值。



Answer 3:

如在评论说,这是由于这样的编译器操作的方式常量传送和执行以较高的精度计算(I相信这是依赖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被发射作为false的IL,因此编译器没有在编译时计算。

为了确认恒定折叠/传播的编译器优化

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


Answer 4:

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


Answer 5:

==约为比较精确的浮动值。

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



文章来源: (.1f+.2f==.3f) != (.1f+.2f).Equals(.3f) Why?