С# NaN сравнительные различия между Equals() и ==
Проверьте это:
var a = Double.NaN;
Console.WriteLine(a == a);
Console.ReadKey();
Печать "False"
var a = Double.NaN;
Console.WriteLine(a.Equals(a));
Console.ReadKey();
Печатает "True"!
Почему он печатает "True"? Из-за спецификации чисел с плавающей запятой значение NaN не равно самому себе! Таким образом, кажется, что метод Equals() реализован неправильно...
Я что-то пропустил?
Ответы
Ответ 1
Я нашел статью, посвящённую вашему вопросу: .NET Security Blog: Почему == и метод Equals возвращают разные результаты для значений с плавающей точкой
Согласно МЭК 60559: 1989, два числа с плавающей запятой со значениями NaN никогда не равны. Однако, в соответствии со спецификацией System.Object:: Equals метод, это желательно переопределить этот метод для предоставить семантику равенства ценности. [...]
Итак, теперь у нас есть две противоречивые идеи о том, что должно означать Равенство. Object:: Equals говорит, что значение BCL типы должны переопределяться для обеспечения ценности равенство, а МЭК 60559 говорит, что NaN не равен NaN. Раздел я спецификация ECMA обеспечивает разрешение для этот конфликт, сделав заметку о этот конкретный случай в разделе 8.2.5.2 [ниже]
Обновление:. Полный текст раздела 8.2.5 из спецификации CLI (ECMA-335) проливает некоторые больше света на этом. Я скопировал соответствующие биты здесь:
8.2.5 Идентичность и равенство значений
Существует два бинарных оператора по всем парам значений: тождество и равенство. Они возвращают логический результат и являются математическими операторов эквивалентности; то есть они:
- Рефлексивный -
a op a
является истинным. - Симметричный -
a op b
является истинным тогда и только тогда, когда b op a
истинно. - Transitive - если
a op b
истинно, а b op c
- true, тогда a op c
правда.
Кроме того, хотя идентификация всегда означает равенство, обратное не правда. [...]
8.2.5.1 Идентификация
Идентичный оператор определяется CTS следующим образом.
- Если значения имеют разные точные типы, то они не идентичны.
- В противном случае, если их точный тип - тип значения, то они идентичны, если и только если битовые последовательности значения по-разному совпадают.
- В противном случае, если их точный тип является ссылочным типом, то они идентичны тогда и только тогда, когда местоположения значений одинаковы.
Идентичность реализуется на System.Object
с помощью метода ReferenceEquals
.
8.2.5.2 Равенство
Для типов значений оператор равенства является частью определения точного тип. Определения равенства должны соблюдать следующие правила:
- Равенство должно быть оператором эквивалентности, как определено выше.
- Идентичность должна подразумевать равенство, как указано ранее.
- Если операнд (или оба) является коротким значением, [...]
Равенство реализуется на System.Object
через Equals
Метод.
[Примечание: хотя две с плавающей запятой NaN определены в МЭК 60559: 1989 всегда сравниваются как неравные, контракт для System.Object.Equals требует, чтобы переопределения удовлетворяли требования к эквивалентности оператор. Следовательно, System.Double.Equals
и System.Single.Equals
return True при сравнении двух NaN, тогда как оператор равенства возвращает False в в этом случае, как того требует МЭК стандарт. end note]
Приведенное выше не указывает свойства оператора ==
вообще (кроме последней заметки); это прежде всего определяет поведение ReferenceEquals
и Equals
. Для поведения оператора ==
спецификация языка С# (ECMA-334) (раздел 14.9.2) ясно о том, как лечить Значения NaN:
Если любой из операндов [до operator ==
] равен NaN, результат будет false
Ответ 2
Equals
выполняется для вещей, таких как hashtables. Таким образом, контракт требует, чтобы a.Equals(a)
.
Состояние MSDN:
Следующие утверждения должны быть верны для всех реализаций метода Equals. В списке x, y и z представляют ссылки на объекты, которые не являются нулевыми.
x.Equals(x) возвращает true, за исключением случаев, связанных с типами с плавающей точкой. См. IEC 60559: 1989, Бинарная арифметика с плавающей точкой для микропроцессорных систем.
x.Equals(y) возвращает то же значение, что и y.Equals(x).
x.Equals(y) возвращает true, если оба x и y являются NaN.
Если (x.Equals(y) & y.Equals(z)) возвращает true, то x.Equals(z) возвращает true.
Последовательные вызовы x.Equals(y) возвращают одно и то же значение, если объекты, на которые ссылаются x и y, не изменяются.
x.Equals(null) возвращает false.
См. GetHashCode для дополнительных требуемых действий, относящихся к методу Equals.
То, что я нахожу странным, заключается в том, что он утверждает, что "x.Equals(x) возвращает true, за исключением случаев, когда речь идет о типах с плавающей точкой. См. IEC 60559: 1989, Бинарная арифметика с плавающей точкой для микропроцессорных систем". но в то же время требует, чтобы NaN равнялось NaN. Так почему они ввели это исключение? Из-за разных NaN?
Аналогичным образом при использовании IComparer<double>
также должен быть нарушен стандарт с плавающей запятой. Поскольку IComparer
требует согласованного полного упорядочения.
Ответ 3
Если бы я рискнул предположить, возможно, это связано с поддержкой использования 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
по этой причине (компилятор С# даже предупреждает вас, если вы переопределяете один без другого) - большая часть того, почему они существуют, заключается в том, чтобы облегчить использование типы как ключи хеш-таблицы.
Как я уже сказал, это всего лишь предположение.
Ответ 4
Пока вы правы, что NaN == NaN
является ложным, double.Equals
по-разному обрабатывает NaN
таким образом, что NaN.Equals(NaN)
является истинным. Здесь реализация .NET 4 метода из отражателя:
public bool Equals(double obj)
{
return ((obj == this) || (IsNaN(obj) && IsNaN(this)));
}
Ответ 5
Ознакомьтесь с этой ссылкой для получения дополнительной информации о том, когда использовать ==
или Equals
. Написано прославленным лидером Джоном Скитом.