c# 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() реализован неправильно... Я что-то упускаю ?

5 ответов


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

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

так теперь у нас две противоречивые идеи о том, что должно означать равенство. Object:: Equals говорит, что значение BCL типы должны переопределять для предоставления значения равенство, и IEC 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 - это правда.
  • транзитивность – если a op b true и b op c истинно, то a op c is истинный.

кроме того, в то время как личность всегда подразумевает равенство, обратного нет истинный. [...]

8.2.5.1 удостоверение

оператор идентификации определяется CTS следующим образом.

  • если значения имеют разные типы, то они не идентичный.
  • в противном случае, если их точный тип является типом значения, то они идентичны, если и только если битовые последовательности значения одинаковы, шаг за шагом.
  • в противном случае, если их точный тип является ссылочным типом, тогда они идентичные, если и только если местоположения значения такие же.

идентификатор реализован на System.Object через ReferenceEquals метод.

равенство 8.2.5.2

для типы значений, оператор равенства является частью определения точного тип. Определения равенства должны соблюдайте следующие правила:

  • равенство должно быть оператором эквивалентности, как определено выше.
  • идентичность должна подразумевать равенство, как было сказано ранее.
  • если любой (или оба) операнд является коробочным значением, [...]

равенство реализуется на System.Object через Equals метод.

[Примечание: хотя две плавающие точки NaNs определены IEC 60559: 1989 к всегда сравнивают как неравные, контракт на закупку System.Объект.Равняется требует, чтобы переопределения удовлетворяли требования к эквивалентности оператор. Следовательно, System.Double.Equals и System.Single.Equals вернуть True при сравнении двух Нан, в то время как оператор равенства возвращает False in в этом случае, как того требует МЭК норматив. конец Примечания]

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

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


Equals сделан для таких вещей, как хеш-таблицы. И поэтому контракт требует, чтобы a.Equals(a).

MSDN заявляет:

следующие операторы должны быть true для всех реализаций метода Equals. В списке x, y и z представляют ссылки на объекты, которые не являются нулевыми.

x.Equals (x) возвращает true, за исключением случаев, связанных с типами с плавающей запятой. См. МЭК 60559:1989 двоичная арифметика с плавающей точкой для микропроцессорных Системный.

x.Equals (y) возвращает то же значение, что и y.Равно (x).

x.Equals (y) возвращает true, если x и y равны NaN.

If (x.Равно(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, двоичная арифметика с плавающей запятой для микропроцессорных систем.- но в то же время требует, чтобы НАН равнялась НАН. Так почему же они сделали исключение? Из-за разных бабушек?

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


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

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


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

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

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