关于c#:将等于的浮点值检查为0是否安全?

Is it safe to check floating point values for equality to 0?

我知道通常不能依赖于双精度或十进制类型的值之间的相等性,但我想知道0是否是一个特殊情况。

虽然我能理解0.0000000000001和0.0000000000002之间的不精确性,但0本身似乎很难搞砸,因为它只是一个小问题。如果你什么都不精确,那就不是什么了。

但我对这个话题不太了解,所以我不想说。

1
2
double x = 0.0;
return (x == 0.0) ? true : false;

那会永远回到现实吗?


如果且仅当双变量的值正好为0.0,则可以安全地预期比较将返回true(当然,在原始代码片段中是这样)。这与==运算符的语义一致。a == b表示"a等于b"。

当纯数学中的同一计算结果为零时,期望某些计算结果在双(或更一般地说,浮点)算术中为零是不安全的(因为这是不正确的)。这是因为当计算深入地面时,会出现浮点精度误差——这是数学中实数算术中不存在的概念。


如果需要进行大量的"相等"比较,最好在.NET 3.5中编写一个小的助手函数或扩展方法进行比较:

1
2
3
4
public static bool AlmostEquals(this double double1, double double2, double precision)
{
    return (Math.Abs(double1 - double2) <= precision);
}

这可以通过以下方式使用:

1
2
double d1 = 10.0 * .1;
bool equals = d1.AlmostEquals(0.0, 0.0000001);


对于你的简单样品,这个测试是可以的。但是这个呢:

1
bool b = ( 10.0 * .1 - 1.0 == 0.0 );

记住.1是一个二进制的重复小数,不能精确表示。然后将其与此代码进行比较:

1
2
double d1 = 10.0 * .1; // make sure the compiler hasn't optimized the .1 issue away
bool b = ( d1 - 1.0 == 0.0 );

我将让您运行一个测试来查看实际结果:您更可能以这种方式记住它。


来自double.equals的msdn项:

Precision in Comparisons

The Equals method should be used with
caution, because two apparently
equivalent values can be unequal due
to the differing precision of the two
values. The following example reports
that the Double value .3333 and the
Double returned by dividing 1 by 3 are
unequal.

...

Rather than comparing for equality,
one recommended technique involves
defining an acceptable margin of
difference between two values (such as
.01% of one of the values). If the
absolute value of the difference
between the two values is less than or
equal to that margin, the difference
is likely to be due to differences in
precision and, therefore, the values
are likely to be equal. The following
example uses this technique to compare
.33333 and 1/3, the two Double values
that the previous code example found
to be unequal.

另请参见double.epsilon。


当您比较不同类型的浮点值实现(例如比较float和double)时,就会出现问题。但对于同一类型,这不应该是个问题。

1
2
3
float f = 0.1F;
bool b1 = (f == 0.1); //returns false
bool b2 = (f == 0.1F); //returns true

问题是,程序员有时会忘记隐式类型转换(double-to-float)是为了进行比较而发生的,它会导致一个bug。


如果数字直接分配给浮点或双精度数,则可以安全地对零或任何整数进行测试,对于双精度数,可以用53位表示,对于浮点数,可以用24位表示。

或者换一种方式,您可以始终将整数值赋给一个double,然后将double与相同的整数进行比较,并保证它是相等的。

您还可以从分配一个整数开始,通过坚持用整数进行加、减或乘,继续进行简单的比较(假设浮点数的结果小于24位,而双精度浮点数的结果小于53位)。所以可以在特定的控制条件下将浮点和双精度数视为整数。


不,不好。所谓的非规范化值(subnormal),当与0.0比较时,会比较为假(非零),但当用于方程时,会被规范化(变为0.0)。因此,将其用作避免被零除的机制是不安全的。相反,添加1.0并与1.0进行比较。这将确保所有次法线都被视为零。


试试这个,你会发现==对于double/float是不可靠的。double d = 0.1 + 0.2;
bool b = d == 0.3;

这是Quora的答案。


实际上,我认为最好使用以下代码将双精度值与0.0进行比较:

1
2
double x = 0.0;
return (Math.Abs(x) < double.Epsilon) ? true : false;

同样适用于浮动:

1
2
float x = 0.0f;
return (Math.Abs(x) < float.Epsilon) ? true : false;