c# NaN comparison differences between Equals() and ==
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 ifb op a
is true.- Transitive – if
a op b
is true andb op c
is true, thena 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 theReferenceEquals
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 theEquals
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
andSystem.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
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 an IComparer<double>
the floating-point standard must be violated too. Since IComparer
requires a consistent total ordering.
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.