检查浮点值是否等于0是否安全?


100

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

虽然我可以理解0.00000000000001和0.00000000000002之间的不精确性,但0本身似乎很难弄乱,因为它什么都没有。如果您对某事一无所知,那就再也不是什么了。

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

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

那会永远返回真吗?


69
该代码中的三元运算符是多余的:)
Joel Coehoorn

5
大声笑你是对的。去吧
吉恩·罗伯茨

我不会这样做,因为您不知道x如何设置为零。如果您仍然想这样做,则可能要舍入x或舍去x以摆脱1e-12或可能在最后标记的标记。
雷克斯·洛根

Answers:


115

它是安全的期望,比较将返回true当且仅当双变量具有完全相同的值0.0(在你原来的代码片段,当然,这种情况)。这与==操作符的语义一致。a == b表示“ a等于b”。

当在纯数学中相同计算的结果为零时,期望某些计算的结果在双精度(或更普遍的是浮点数)算法中为零是不安全的(因为它是不正确的)。这是因为当计算深入时,会出现浮点精度误差-这个概念在数学的实数运算中不存在。


51

如果您需要进行大量“相等”比较,则最好在.NET 3.5中编写一些辅助函数或扩展方法以进行比较:

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

可以通过以下方式使用此方法:

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

4
如果double1和double2的值彼此非常接近,则您可能会遇到相减抵消错误。我将删除Math.Abs​​并单独检查每个分支d1> = d2-e和d1 <= d2 + e
Theodore Zographos 2012年

“由于Epsilon定义了范围接近零的正值的最小值,因此两个相似值之间的差异幅度必须大于Epsilon。通常,它比Epsilon大很多倍。因此,我们建议您这样做比较Double值是否相等时,请勿使用Epsilon。” - msdn.microsoft.com/en-gb/library/ya2zha7s(v=vs.110).aspx
拉斐尔·科斯塔

15

对于您的简单样本,该测试是可以的。但是呢:

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

请记住,.1是二进制的重复小数,因此无法准确表示。然后将其与以下代码进行比较:

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

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


5
实际上,由于某种原因(至少在LINQPad中),这返回true。
Alexey Romanov 2009年

您谈论的“ .1问题”是什么?
Teejay

14

Double.Equals的MSDN条目中:

比较精度

应谨慎使用Equals方法,因为两个看似等效的值由于两个值的精度不同而可能不相等。下面的示例报告Double值.3333和通过1除以3返回的Double是不相等的。

...

除了比较是否相等外,一种推荐的技术还包括定义两个值之间可接受的差异容限(例如其中一个值的0.01%)。如果两个值之间的差的绝对值小于或等于该裕度,则该差很可能是由于精度上的差异,因此,这些值很可能相等。下面的示例使用此技术比较.33333和1/3,即上一个代码示例发现的两个Double值不相等。

另请参见Double.Epsilon


1
不相等的值也可以相等地比较。人们预计,如果x.Equals(y)的话(1/x).Equals(1/y),但是如果不是这种情况x0y1/Double.NegativeInfinity。这些值声明相等,即使它们的倒数不相等。
supercat 2012年

@supercat:它们是等效的。而且他们没有互惠。您可以使用x = 0和重新运行测试y = 0,但仍然可以找到1/x != 1/y
Ben Voigt

@BenVoigt:使用xy类型double?您如何比较结果以使它们报告不相等?请注意,1 / 0.0不是NaN。
超级猫

@supercat:好的,这是IEEE-754出错的原因之一。(首先,1.0/0.0它不是应有的NaN,因为限制不是唯一的。其次,无限比较彼此相等,而没有关注无限度)
Ben Voigt

@BenVoigt:如果零是两个非常小的数字相乘的结果,则将1.0除以该值将产生一个值,该值比较大于任何数量的具有相同符号的小数字,如果小于其中一个,则小于任何数字数字有相反的符号。恕我直言,如果IEEE-754具有无符号零,但正负无穷小,则更好。
超级猫

6

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

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

问题是,程序员有时会忘记进行比较时隐式类型转换(双精度浮点型)正在发生,并且导致错误。


3

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

或者换一种说法,您总是可以将整数值分配给一个双精度型,然后将双精度型比较回相同的整数,并确保它是相等的。

您还可以通过分配一个整数开始,并通过坚持对整数进行加,减或乘以简单比较来继续工作(假设浮点数的结果小于24位,而双精度数的结果小于53位)。因此,您可以在某些受控条件下将浮点数和双精度数视为整数。


我总体上同意您的说法(并赞成),但我认为这实际上取决于是否使用IEEE 754浮点实现。而且我相信每台“现代”计算机都至少使用IEEE 754来存储浮点数(存在不同的奇怪舍入规则)。
Mark Lakata '17

2

不,那不行。所谓的非规格化值(次正规),当比较等于0.0时,将比较为假(非零),但是在方程式中使用时,将被规格化(变为0.0)。因此,将其用作避免被零除的机制并不安全。而是,添加1.0并与1.0比较。这样可以确保将所有次法线都视为零。


次常态也称为反常态
Manuel

尽管使用次标准态可能会或可能不会产生相同的结果,但取决于精确的操作,它们在使用时不会等于零。
2011年

-2

试试看,您会发现==对于double / float不可靠。
double d = 0.1 + 0.2; bool b = d == 0.3;

这是Quora 的答案


-4

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

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

与float相同:

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

5
否。来自double.Epsilon的文档:“如果创建一个自定义算法来确定两个浮点数是否可以视为相等,则必须使用大于Epsilon常数的值来建立可接受的绝对差值,这两个值就应视为相等。(通常,差异的幅度是Epsilon的许多倍。)”
Alastair Maw 2012年

1
@AlastairMaw这适用于检查任意大小的两个double是否相等。为了将相等性检查为零,可以使用double.Epsilon。
jwg 2013年

4
不,不是。您通过某种计算得出的值很可能是epsilon远离零的许多倍,但仍应视为零。您无法从某个地方神奇地在中间结果中获得一大堆额外的精度,只是因为它恰好接近于零。
Alastair Maw 2013年

4
例如:(1.0 / 5.0 + 1.0 / 5.0-1.0 / 10.0-1.0 / 10.0-1.0 / 10.0-1.0 / 10.0)<double。 -324)
Alastair Maw

那么,如果double.Epsilon不合适,建议的精度是多少?10倍的ε可以吗?100倍?
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.