c#NaN比较Equals()和==之间的差异

时间:2011-02-08 13:43:28

标签: c# equals nan

检查出来:

    var a = Double.NaN;

    Console.WriteLine(a == a);
    Console.ReadKey();

打印“错误”

    var a = Double.NaN;

    Console.WriteLine(a.Equals(a));
    Console.ReadKey();

打印“真实”!

为什么打印“真实”?由于浮点数规范,NaN的值不等于它自己!所以似乎Equals()方法实现错误... 我错过了什么吗?

5 个答案:

答案 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的要求   标准。 结束记录]

以上并未指定==运算符的属性(最终注释除外);它主要定义ReferenceEqualsEquals的行为。对于==运算符的行为,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)falsex = 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
  • 删除

请记住,EqualsGetHashCode非常密切相关(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撰写。