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

c# NaN comparison differences between Equals() and ==

提问人:illegal-immigrant 提问时间:2/8/2011 最后编辑:illegal-immigrant 更新时间:4/16/2019 访问量:3358

问:

看看这个:

    var a = Double.NaN;

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

打印“False”

    var a = Double.NaN;

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

打印“真实”!

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

C# 等于 NAN

评论

0赞 Shamim Hafiz - MSFT 2/8/2011
你能举例说明结果不同的地方吗?
4赞 BlueMonkMN 2/8/2011
我认为这个问题被不当关闭,因为它询问的是 == 和 Equals 之间的区别,而不是为什么 == 返回 true。提问者已经很清楚为什么 == 返回 false,但继续问为什么 Equals 返回 true,这是一个不同的问题。
3赞 illegal-immigrant 2/8/2011
为什么关闭?这个问题是关于“为什么 .NET 中的 Equals() 不遵循浮点数规范”,而不是关于链接问题中涵盖的内容。
0赞 Jim Balter 6/5/2014
它被关闭了,因为有些人是白痴......这个问题与所谓的重复无关。

答:

3赞 Ani 2/8/2011 #1

虽然你是对的,但这是错误的,特别是以不同的方式处理,这是真的。下面是 reflector 中该方法的 .NET 4 实现:NaN == NaNdouble.EqualsNaNNaN.Equals(NaN)

public bool Equals(double obj)
{
    return ((obj == this) || (IsNaN(obj) && IsNaN(this)));
}
6赞 Dan Tao 2/8/2011 #2

如果我冒昧地猜测,这可能是为了支持在字典中使用值作为键。double

如果返回 for 和 ,则可以有如下代码:x.Equals(y)falsex = double.NaNy = 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));

基本上,对于某个值的实现,您将拥有一种能够为键提供以下奇怪行为的类型:Equalstrue

  • 可以无限次添加到字典中
  • 无法使用 检测到 ,因此...ContainsKey
  • 永远无法使用Remove

请记住,这与这个原因密切相关(如果您重写了一个而没有另一个,C# 编译器甚至会警告您)——它们首先存在的很大一部分原因是为了方便将类型用作哈希表键。EqualsGetHashCode

就像我说的,这只是一个猜测。

14赞 Justin 2/8/2011 #3

我找到了一篇关于您的问题的文章:.NET 安全博客:为什么 == 和 Equals 方法为浮点值返回不同的结果

根据 IEC 60559:1989,两个 值为 NaN 永远不会相等。然而 根据规范 System.Object::Equals 方法,它是 希望将此方法覆盖为 提供价值相等语义。 [...]

所以现在我们有两个相互矛盾的想法 等于应该意味着什么。 Object::Equals 表示 BCL 值 类型应重写以提供值 平等,IEC 60559 说 NaN 不等于 NaN。分区 I 的 ECMA 规范提供了以下分辨率 通过记下关于 第 8.2.5.2 节中的此特定情况 [下文]


更新:CLI 规范 (ECMA-335) 第 8.2.5 节的全文对此进行了更多说明。我在这里复制了相关位:

8.2.5 价值认同和平等

定义了两个二进制运算符 在所有价值观对上:身份平等。它们返回布尔值结果,并且是数学等价运算符;也就是说,它们是:

  • 反身性——是真的。a op a
  • 对称 – 当且仅当为 true 时为 true。a op bb op a
  • 传递 – 如果为 true 并且是 true,则为 真。a op bb op ca op c

此外,虽然身份总是 意味着平等,反之则不然 真。[...]

8.2.5.1 身份

标识运算符由 CTS 定义如下。

  • 如果值具有不同的确切类型,则它们不相同。
  • 否则,如果它们的确切类型是值类型,则它们在 并且仅当 值是相同的,一点一点地。
  • 否则,如果它们的确切类型是引用类型,则它们是 当且仅当位置相同时 的值是相同的。

Identity 是通过该方法实现的。System.ObjectReferenceEquals

8.2.5.2 平等

对于值类型,相等运算符 是确切定义的一部分 类型。平等的定义应 请遵守以下规则:

  • 如上所述,相等应为等价运算符。
  • 如前所述,身份应该意味着平等。
  • 如果任一(或两者)操作数是装箱值,则 [...]

相等是通过该方法实现的。System.ObjectEquals

[注意:虽然两个浮点数 NaN 由 IEC 60559:1989 定义 总是比较为不等,, System.Object.Equals 的协定 要求替代必须满足 等同性的要求 算子。因此,并返回 True 在比较两个 NaN 时,而 相等运算符返回 False 根据IEC的要求,这种情况 标准。结束语System.Double.EqualsSystem.Single.Equals]

以上根本没有指定运算符的属性(最后一个注释除外);它主要定义 和 的行为。对于运算符的行为,C# 语言规范 (ECMA-334) (第 14.9.2 节) 清楚地说明了如何处理 NaN 值:==ReferenceEqualsEquals==

如果任一操作数 [to ] 为 NaN,则结果为 falseoperator ==

评论

0赞 Dan Tao 2/10/2011
老实说,我不确定我是否真的理解这里的推理。本文讨论了ECMA规范和IEEE规范之间的冲突,因为前者说应该提供价值相等语义,而后者则说它不等于它本身。这里的冲突在哪里?基于IEEE规范的类型的“等价运算符的要求”难道不与IEEE规范中定义的相等性要求相同吗?我只是不太明白这个解释。Object.Equalsdouble.NaN
0赞 Justin 2/10/2011
@Dan - 你是对的;这并不是一个完整的答案。请看看我上面的更新是否解决了问题。
0赞 Dan Tao 2/10/2011
我认为关于值类型的标识的部分为我澄清了这一点:“当且仅当值的位序列是相同的,一点一点地相同时,它们是相同的”——显然,两个值就是这种情况(这与“标识应该意味着相等”)。很好的参考。double.NaN
0赞 Jim Balter 6/5/2014
@DanTao 查看等价运算符的 Reflexive 属性的定义。不可能在任何包含 NaN 的集合上定义一个等价运算符,从而定义一个相等运算符......无论是否将相等定义为逐位等价。并且逐位等价仅适用于标识,而不适用于相等,并且只有 ReferenceEquals 才需要标识。
9赞 CodesInChaos 2/8/2011 #4

Equals是为哈希表之类的东西而制作的。因此,它的合同要求.a.Equals(a)

MSDN 指出:

对于 Equals 方法的所有实现,以下语句必须为 true。在列表中,x、y 和 z 表示不为 null 的对象引用。

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。

请参阅 GetHashCode,了解与 Equals 方法相关的其他必需行为。

我觉得奇怪的是,它指出“x.Equals(x) 返回 true,除非涉及浮点类型。参见 IEC 60559:1989,微处理器系统的二进制浮点算术“,但同时要求 NaN 等于 NaN。那么,他们为什么要把这个例外放进去呢?因为不同的NaN?

以类似的方式,当使用浮点标准时,也必须违反浮点标准。由于需要一致的总排序。 IComparer<double>IComparer

评论

0赞 Donal Fellows 2/8/2011
IEEE NaN确实是一种非常奇怪的动物。它在数据中的出现通常表明正在发生一些非常奇怪的事情,并且事情已经出了严重的错误。假装它不存在。不要理会幕后的那个男人。
0赞 CodesInChaos 2/8/2011
IEEE浮点有许多怪癖。许多不同的NaN,无穷大的性质,两个不同的零,denorms,...你不能忽视它们,因为这些怪癖可能会破坏其他代码所做的一些假设。
1赞 Kevin Cathcart 11/1/2011
第一句话的一个原因是因为扩展了精度。在 x86 处理器中,浮点寄存器中的浮点(双精度)值为 80 位,而内存中的这些值向下舍入为 64 位。编译器可以随时在存储器和寄存器之间自由移动值,并使用完整的扩展精度进行比较。结果是不能保证对于浮点,即使不是!这经常会让那些认为自己理解浮点的人感到困惑。x==xxxNaN