Почему Double.NaN равен самому себе, когда он завернут в экземпляр Double?

Из этот вопрос Я узнал Double.NaN не равен себе.

Я проверял это для себя и заметил, что это не так, если вы переносите Double.NaN в экземпляр Double. Например:

public class DoubleNaNTest {
    public static void main(String[] args) {
        double primitive = Double.NaN;
        Double object = new Double(primitive);

        // test 1 - is the primitive is equal to itself?
        boolean test1 = primitive == primitive;

        // test 2 - is the object equal to itself?
        boolean test2 = object.equals(object);

        // test 3 - is the double value of the object equal to itself?
        boolean test3 = object.doubleValue() == object.doubleValue();

        System.out.println("Test 1 = " + test1);
        System.out.println("Test 2 = " + test2);
        System.out.println("Test 3 = " + test3);
    }
}

Выходы:

Test 1 = false
Test 2 = true
Test 3 = false

Мне кажется, что все три теста должны оцениваться как false, так как все три операции эквивалентны (как если бы вы использовали что-то другое, а затем Double.NaN).

Может ли кто-нибудь объяснить, что здесь происходит?

Ответ 1

Что происходит, метод equals намеренно отклоняется от плавающей точки IEE. Цитирование из Javadoc для метода equals(Object) java.lang.Double.

Однако есть два исключения:

  • Если d1 и d2 оба представляют Double.NaN, то метод equals возвращает true, хотя Double.NaN == Double.NaN имеет значение ложь.
  • Если d1 представляет +0.0, тогда как d2 представляет -0.0, или наоборот, равный тест имеет значение false, даже хотя значение +0.0 == - 0.0 имеет значение true.

Это определение позволяет хэш-таблицам правильно работать.

Результат состоит в том, что если вы хотите 100% -ную совместимость с плавающей точкой IEE, вам необходимо явно удалить экземпляры java.lang.Double и сравнить полученные значения double.

Ответ 2

Это так хэш-таблицы работают правильно

Они сознательно отклонились от пути IEEE, так что хэш-таблицы будут работать.

Вы можете получить часть истории в api docs. Остальная часть истории: каждый класс, который наследует объект Object, который есть все они, имеет контракт на сохранение инвариантов объекта. Контракт существует, так что остальная часть библиотеки может реализовать все эти красивые коллекции и вещи. Ничто на самом деле не мешает вам нанести ущерб этому, но тогда вы не можете быть уверены, что все, что возьмет объект, будет работать.

Ответ 3

Если у одного есть два неизменяемых объекта, которые определяют Equals, чтобы возвращать true, когда каждое поле в одном объекте сообщает себя Equal в соответствующее поле в другом, и если ни один из кода, который использует объекты, не заботится о ссылочном равенстве, тогда должно быть возможно заменить все ссылки на один объект ссылками на другой. Если объекты генерируются, например, анализируя данные, считываемые с диска, и если многие объекты будут сравниваться равными, замена многих разных экземпляров ссылками на один экземпляр может значительно повысить производительность. Обратите внимание, что правильность такой подстановки не зависит от того, являются ли объекты мелко или глубоко неизменными, при условии, что любые измененные объекты, вложенные в них, будут сообщаться как неравные ни с чем другим, кроме них.

Для правильной работы такой логики важно, чтобы объекты, о которых идет речь, были полностью эквивалентны. Каждый объект должен быть равен самому себе, поэтому Double.NaN должен равняться Double.NaN. Требование полной эквивалентности подразумевает, что положительный нуль не должен сообщать себе равный отрицательному нулю (поскольку, если бы это произошло, объект, который имеет положительный нуль, мог бы вести себя иначе, чем один отрицательный ноль).

Обратите внимание, кстати, что это область, где .net отличается от Java (некоторые неэквивалентные значения с плавающей запятой и Decimal представляют отчет Equals); во многих отношениях, я думаю, что .net превосходит, но эта деталь является тем, что .net получило неправильный характер.