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

二次信任 提交于 2019-12-18 04:34:15

问题


Check this out :

    var a = Double.NaN;

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

Prints "False"

    var a = Double.NaN;

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

Prints "True"!

Why it prints "True"? Due to floating point numbers specification, value that is NaN is not equal to itself! So it seems that Equals() method is implemented wrong... Am I missing something ?


回答1:


I found an article addressing your question: .NET Security Blog: Why == and the Equals Method Return Different Results for Floating Point Values

According to IEC 60559:1989, two floating point numbers with values of NaN are never equal. However, according to the specification for the System.Object::Equals method, it's desirable to override this method to provide value equality semantics. [...]

So now we have two conflicting ideas of what Equals should mean. Object::Equals says that the BCL value types should override to provide value equality, and IEC 60559 says that NaN does not equal NaN. Partition I of the ECMA spec provides resolution for this conflict by making a note about this specific case in section 8.2.5.2 [below]


Update: The full text of section 8.2.5 from the CLI spec (ECMA-335) sheds some more light on this. I've copied the relevant bits here:

8.2.5 Identity and equality of values

There are two binary operators defined on all pairs of values: identity and equality. They return a Boolean result, and are mathematical equivalence operators; that is, they are:

  • Reflexive – a op a is true.
  • Symmetric – a op b is true if and only if b op a is true.
  • Transitive – if a op b is true and b op c is true, then a op c is true.

In addition, while identity always implies equality, the reverse is not true. [...]

8.2.5.1 Identity

The identity operator is defined by the CTS as follows.

  • If the values have different exact types, then they are not identical.
  • Otherwise, if their exact type is a value type, then they are identical if and only if the bit sequences of the values are the same, bit by bit.
  • Otherwise, if their exact type is a reference type, then they are identical if and only if the locations of the values are the same.

Identity is implemented on System.Object via the ReferenceEquals method.

8.2.5.2 Equality

For value types, the equality operator is part of the definition of the exact type. Definitions of equality should obey the following rules:

  • Equality should be an equivalence operator, as defined above.
  • Identity should imply equality, as stated earlier.
  • If either (or both) operand is a boxed value, [...]

Equality is implemented on System.Object via the Equals method.

[Note: Although two floating point NaNs are defined by IEC 60559:1989 to always compare as unequal, the contract for System.Object.Equals requires that overrides must satisfy the requirements for an equivalence operator. Therefore, System.Double.Equals and System.Single.Equals return True when comparing two NaNs, while the equality operator returns False in that case, as required by the IEC standard. end note]

The above does not specify the properties of the == operator at all (except for the final note); it is primarily defining the behavior of ReferenceEquals and Equals. For the behavior of the == operator, the C# language spec (ECMA-334) (section 14.9.2) is clear about how to treat NaN values:

If either operand [to operator ==] is NaN, the result is false




回答2:


Equals is made for things like hashtables. And thus it contract requires that a.Equals(a).

MSDN states:

The following statements must be true for all implementations of the Equals method. In the list, x, y, and z represent object references that are not null.

x.Equals(x) returns true, except in cases that involve floating-point types. See IEC 60559:1989, Binary Floating-point Arithmetic for Microprocessor Systems.

x.Equals(y) returns the same value as y.Equals(x).

x.Equals(y) returns true if both x and y are NaN.

If (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true.

Successive calls to x.Equals(y) return the same value as long as the objects referenced by x and y are not modified.

x.Equals(null) returns false.

See GetHashCode for additional required behaviors pertaining to the Equals method.

What I find strange is that it states "x.Equals(x) returns true, except in cases that involve floating-point types. See IEC 60559:1989, Binary Floating-point Arithmetic for Microprocessor Systems." but at the same time requires that NaN equals NaN. So why did they put that exception in? Because of different NaNs?

In a similar way when using anIComparer<double> the floating-point standard must be violated too. Since IComparer requires a consistent total ordering.




回答3:


If I were to venture a guess, it might be that this is to support the use of double values as keys in a dictionary.

If x.Equals(y) returned false for x = double.NaN and y = double.NaN, then you could have code like this:

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!");

I think the majority of developers would find this behavior rather unintuitive. But even more counterintuitive would be this:

// This would output false!
Console.WriteLine(dict.ContainsKey(x));

Basically, with an implementation of Equals that never returns true for a certain value, what you would have is a type capable of providing keys with the following bizarre behavior:

  • Could be added an unlimited number of times to a dictionary
  • Could not be detected using ContainsKey, and therefore...
  • Could never be removed using Remove

Remember that Equals is very closely related to GetHashCode for this very reason (the C# compiler even warns you if you've overridden one without the other)—a big part of why they're there in the first place is to facilitate the use of types as hash table keys.

Like I said, it's just a guess.




回答4:


While you're correct that NaN == NaN is false, double.Equals specially handles NaN differently, in a way that NaN.Equals(NaN) is true. Here's the .NET 4 implementation of the method from reflector:

public bool Equals(double obj)
{
    return ((obj == this) || (IsNaN(obj) && IsNaN(this)));
}


来源:https://stackoverflow.com/questions/4933769/c-sharp-nan-comparison-differences-between-equals-and

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!