检查出来:
var a = Double.NaN;
Console.WriteLine(a == a);
Console.ReadKey();
打印“错误”
var a = Double.NaN;
Console.WriteLine(a.Equals(a));
Console.ReadKey();
打印“真实”!
为什么打印“真实”?由于浮点数规范,NaN的值不等于它自己!所以似乎Equals()方法实现错误... 我错过了什么吗?
答案 0 :(得分:14)
我找到了一篇解决您问题的文章:.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规范提供了解决方案 通过记录这个冲突 这个具体案例见第8.2.5.2节[见下文]
更新:the CLI spec (ECMA-335)第8.2.5节的全文详细阐述了这一点。我在这里复制了相关的部分:
8.2.5值的同一性和相等性
定义了两个二元运算符 在所有价值对上:身份和 平等。它们返回布尔结果,并且是数学的 等价运算符;也就是说,它们是:
- 反身 -
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平等
对于值类型,使用相等运算符 是精确定义的一部分 类型。等式的定义应该 遵守以下规则:
- Equality应该是一个等价运算符,如上所定义。
- 如前所述,身份应该意味着平等。
- 如果其中一个(或两个)操作数是一个盒装值,[...]
实施平等 通过
System.Object
Equals
方法[注意:虽然有两个浮点 NaNs由IEC 60559:1989定义为 总是比较不平等的 System.Object.Equals的合同 要求覆盖必须满足 对等的要求 运营商。因此,
System.Double.Equals
和System.Single.Equals
返回True 当比较两个NaN时,而 等于运算符返回False 那种情况,按照IEC的要求 标准。 结束记录]
以上并未指定==
运算符的属性(最终注释除外);它主要定义ReferenceEquals
和Equals
的行为。对于==
运算符的行为,the C# language spec (ECMA-334)(第14.9.2节)明确了如何处理NaN值:
如果任一操作数[to
operator ==
]为NaN,则结果为false
答案 1 :(得分:8)
Equals
用于哈希表之类的东西。因此,合同要求a.Equals(a)
。
MSDN声明:
以下语句必须适用于Equals方法的所有实现。在列表中,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?
以类似的方式使用IComparer<double>
时,也必须违反浮点标准。由于IComparer
需要一致的总排序。
答案 2 :(得分:5)
如果我冒昧地猜测,可能是支持将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#编译器甚至会警告你,如果你已经覆盖了一个没有另一个) - 很大一部分原因是它们是首先是方便使用类型作为哈希表键。
就像我说的那样,这只是猜测。
答案 3 :(得分:3)
如果您NaN == NaN
为假是正确的,double.Equals
会以NaN
为真的方式特别处理NaN.Equals(NaN)
。这是反射器方法的.NET 4实现:
public bool Equals(double obj)
{
return ((obj == this) || (IsNaN(obj) && IsNaN(this)));
}
答案 4 :(得分:1)
有关何时使用==
或Equals
的详细信息,请查看此link。由杰出的领导人Jon Skeet撰写。