看一下这个 :
var a = Double.NaN;
Console.WriteLine(a == a);
Console.ReadKey();
打印“假”
var a = Double.NaN;
Console.WriteLine(a.Equals(a));
Console.ReadKey();
打印“真实”!
为什么打印“真实”?由于浮点数规范,NaN的值不等于它自己!所以似乎Equals()方法实现错误......我错过了什么?
我发现了一篇解决你问题的文章:.NET Security Blog: Why == and the Equals Method Return Different Results for Floating Point Values
根据IEC 60559:1989,具有NaN值的两个浮点数永远不相等。但是,根据System.Object :: Equals方法的规范,需要重写此方法以提供值相等语义。 [...]
所以现在我们对Equals应该意味着什么有两个相互矛盾的想法。 Object :: Equals表示BCL值类型应覆盖以提供值相等,而IEC 60559表示NaN不等于NaN。 ECMA规范的分区I通过在8.2.5.2节[下面]中记录这个特定情况来解决这个冲突。
更新:the CLI spec (ECMA-335)第8.2.5节的全文对此有了更多的了解。我在这里复制了相关的部分:
8.2.5 Identity and equality of values
在所有值对上定义了两个二元运算符:identity和equality。它们返回一个布尔结果,是数学等价运算符;也就是说,它们是:
- 反身 -
a op a
是真的。- 对称 - 当且仅当
a op b
为真时,b op a
才是真的。- 传递 - 如果
a op b
是真的并且b op c
是真的,那么a op c
是真的。此外,虽然身份总是意味着平等,但事实并非如此。 [...]
8.2.5.1身份
身份运营商由CTS定义如下。
- 如果值具有不同的确切类型,则它们不相同。
- 否则,如果它们的确切类型是值类型,那么它们是相同的,当且仅当值的位序列是相同的时,才是逐位的。
- 否则,如果它们的确切类型是引用类型,那么它们是相同的,当且仅当值的位置相同时。
通过
System.Object
方法在ReferenceEquals
上实现身份。8.2.5.2平等
对于值类型,等于运算符是精确类型定义的一部分。等式的定义应遵守以下规则:
- 如上所述,平等应该是等价运算符。
- 如前所述,身份应该意味着平等。
- 如果其中一个(或两个)操作数是一个盒装值,[...]
通过
System.Object
方法在Equals
上实现平等。[注意:尽管IEC 60559:1989定义了两个浮点NaN以始终比较为不等,但System.Object.Equals的合同要求覆盖必须满足等价运算符的要求。因此,
System.Double.Equals
和System.Single.Equals
在比较两个NaN时返回True,而在这种情况下,等式运算符返回False,符合IEC标准的要求。结束说明]
以上并未指定==
运算符的属性(最终注释除外);它主要定义了ReferenceEquals
和Equals
的行为。对于==
算子的行为,the C# language spec (ECMA-334)(第14.9.2节)清楚地知道如何处理NaN值:
如果任一操作数[to
operator ==
]为NaN,则结果为false
Equals
是为哈希表之类的东西而制作的。因此它的合同要求a.Equals(a)
。
MSDN声明:
对于Equals方法的所有实现,以下语句必须为true。在列表中,x,y和z表示非空的对象引用。
x.Equals(x)返回true,但涉及浮点类型的情况除外。参见IEC 60559:1989,微处理器系统的二进制浮点算法。
x.Equals(y)返回与y.Equals(x)相同的值。
如果x和y都是NaN,则x.Equals(y)返回true。
如果(x.Equals(y)&& y.Equals(z))返回true,则x.Equals(z)返回true。
只要未修改x和y引用的对象,对x.Equals(y)的连续调用将返回相同的值。
x.Equals(null)返回false。
有关Equals方法的其他必需行为,请参阅GetHashCode。
我发现奇怪的是它声明“x.Equals(x)返回true,除了涉及浮点类型的情况。参见IEC 60559:1989,微处理器系统的二进制浮点运算。”但同时要求NaN等于NaN。那他们为什么要把这个例外放进去呢?因为不同的NaNs?
以类似的方式使用anIComparer<double>
时,也必须违反浮点标准。由于IComparer
需要一致的总排序。
如果我冒险猜测,可能是支持使用double
值作为字典中的键。
如果x.Equals(y)
为false
和x = double.NaN
返回y = double.NaN
,那么你可以得到这样的代码:
var dict = new Dictionary<double, string>();
double x = double.NaN;
dict.Add(x, "These");
dict.Add(x, "have");
dict.Add(x, "duplicate");
dict.Add(x, "keys!");
我认为大多数开发人员会觉得这种行为不太直观。但更加违反直觉的是:
// This would output false!
Console.WriteLine(dict.ContainsKey(x));
基本上,通过Equals
的实现永远不会返回某个值的true
,你将拥有一个能够为键提供以下奇怪行为的类型:
ContainsKey
无法检测到,因此......Remove
删除请记住,由于这个原因,Equals
与GetHashCode
密切相关(C#编译器甚至会警告你,如果你已经覆盖了一个而没有另一个) - 为什么他们首先在那里的很大一部分是为了方便使用类型作为哈希表键。
就像我说的那样,这只是猜测。
虽然你是正确的NaN == NaN
是假的,double.Equals
特别处理NaN
不同,以NaN.Equals(NaN)
为真。这是反射器方法的.NET 4实现:
public bool Equals(double obj)
{
return ((obj == this) || (IsNaN(obj) && IsNaN(this)));
}
有关何时使用link或==
的更多信息,请查看此Equals
。由杰出的领导人Jon Skeet撰写。