c # NaN сравнения различий между Equals () и == - PullRequest
15 голосов
/ 08 февраля 2011

Проверьте это:

    var a = Double.NaN;

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

Печатает "Ложь"

    var a = Double.NaN;

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

Печатает "True"!

Почему печатается "True"?Из-за спецификации чисел с плавающей запятой значение NaN не равно само себе!Похоже, что метод Equals () реализован неправильно ... Я что-то упустил?

Ответы [ 5 ]

14 голосов
/ 08 февраля 2011

Я нашел статью, посвященную вашему вопросу: Блог безопасности .NET: Почему == и метод Equals возвращают разные результаты для значений с плавающей запятой

Согласно МЭК 60559: 1989, два числа с плавающей запятой со значениями NaN никогда не равны. Тем не мение, согласно спецификации для Метод System.Object :: Equals, это желательно переопределить этот метод обеспечить семантику равенства значений. [...]

Так что теперь у нас есть две противоречивые идеи из того, что равно значить. Object :: Equals говорит, что значение BCL типы должны переопределять, чтобы обеспечить значение равенство, а МЭК 60559 говорит, что NaN не равно NaN. Раздел I из спецификация ECMA обеспечивает разрешение для этот конфликт, сделав заметку о этот конкретный случай в разделе 8.2.5.2 [ниже]


Обновление: Полный текст раздела 8.2.5 из спецификации CLI (ECMA-335) проливает некоторый свет на это. Я скопировал соответствующие биты здесь:

8.2.5 Идентичность и равенство ценностей

Определены два бинарных оператора на всех парах значений: идентичность и равенство . Они возвращают логический результат и являются математическими операторы эквивалентности ; то есть они:

  • Рефлексивно - a op a верно.
  • Симметричный - a op b равен true, если и только если b op a равен true.
  • Transitive - если a op b верно и b op c верно, тогда 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 возврат True при сравнении двух NaN, а Оператор равенства возвращает False в в этом случае, как того требует МЭК стандарт. Конечная нота ]

Приведенное выше описание вообще не определяет свойства оператора == (за исключением заключительного примечания); это в первую очередь определяет поведение ReferenceEquals и Equals. Что касается поведения оператора ==, то в спецификации языка C # (ECMA-334) (раздел 14.9.2) ясно, как обрабатывать значения NaN:

Если любой из операндов [до operator ==] равен NaN, результат равен false

8 голосов
/ 08 февраля 2011

Equals создан для таких вещей, как хеш-таблицы.И поэтому он требует, чтобы 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, за исключением случаев, когда используются типы с плавающей точкой.МЭК 60559: 1989 «Двоичная арифметика с плавающей точкой для микропроцессорных систем».но в то же время требует, чтобы NaN равнялся NaN.Так почему же они вставили это исключение?Из-за различных NaN?

Аналогичным образом, при использовании IComparer<double> стандарт с плавающей точкой также должен быть нарушен.Поскольку IComparer требует последовательного общего заказа.

5 голосов
/ 08 февраля 2011

Если бы я рискнул предположить, возможно, это связано с поддержкой использования значений 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 по этой самой причине (компилятор C # даже предупреждает вас, если вы переопределили один без другого) - большая часть того, почему они есть в первом место для облегчения использования типов в качестве ключей хеш-таблицы.

Как я уже сказал, это всего лишь предположение.

3 голосов
/ 08 февраля 2011

Хотя вы правы, что NaN == NaN ложно, double.Equals специально обрабатывает NaN по-другому, таким образом, NaN.Equals(NaN) верно Вот реализация .NET 4 метода от отражателя:

public bool Equals(double obj)
{
    return ((obj == this) || (IsNaN(obj) && IsNaN(this)));
}
1 голос
/ 08 февраля 2011

Проверьте эту ссылку для получения дополнительной информации о том, когда использовать == или Equals.Автор прославленного лидера Джона Скита.

...