Почему Double.NaN == Double.NaN возвращает false?

155

Я только изучал вопросы OCPJP и нашел этот странный код:

public static void main(String a[]) {
    System.out.println(Double.NaN==Double.NaN);
    System.out.println(Double.NaN!=Double.NaN);
}

Когда я запустил код, я получил:

false
true

Как получается, falseкогда мы сравниваем две вещи, которые выглядят одинаково друг с другом? Что NaNзначит?

инакомыслящий
источник
8
Это действительно странно. Поскольку Double.NaN является статическим финалом, сравнение с == должно возвращать true. +1 за вопрос.
Стефан
2
То же самое верно в питоне:In [1]: NaN==NaN Out[1]: False
ТДЦ
58
То же самое верно для всех языков, которые правильно соответствуют стандарту IEEE 754.
zzzzBov
4
Интуиция: «Hello» - это не число, true (логическое значение) также не число. NaN! = NaN по той же причине «Привет»! = True
Кевин
3
@Stephan: Сравнение с Double.NaN==Double.NaNдолжно действительно возвращать true, если Double.NaNбыли типа java.lang.Double. Тем не менее, его тип является примитивным double, и doubleприменяются операторские правила (которые требуют этого неравенства для соответствия IEEE 754, как объяснено в ответах).
Слёске

Ответы:

139

NaN означает «не число».

Спецификация языка Java (JLS) третьего издания гласит :

Операция, которая переполняется, производит бесконечность со знаком, операция, которая переполняется, производит денормализованное значение или нулевой знак, и операция, которая не имеет математически определенного результата, производит NaN. Все числовые операции с NaN в качестве операнда в результате дают NaN. Как уже было описано, NaN неупорядочен, поэтому операция числового сравнения, включающая один или два возврата NaN , falseи любое !=сравнение, включающее возврат NaN true, в том числе x!=xкогда xравен NaN.

Адриан Митев
источник
4
@nibot: в основном верно . Произойдет любое сравнение с поплавком, соответствующим IEEE false. Так что этот стандарт отличается от Java тем, что IEEE требует этого (NAN != NAN) == false.
Дрю Дорманн
2
Открывая ящик этой Пандоры - где вы видите, что «IEEE требует, чтобы (NAN! = NAN) == false»?
Супервайзер
62

NaN по определению не равен ни одному числу, включая NaN. Это является частью стандарта IEEE 754 и реализуется процессором / FPU. Это не то, что JVM должна добавить какую-либо логику для поддержки.

http://en.wikipedia.org/wiki/NaN

Сравнение с NaN всегда возвращает неупорядоченный результат даже при сравнении с самим собой. ... Предикаты равенства и неравенства не сигнализируют, поэтому x = x, возвращающий false, можно использовать для проверки, является ли x спокойным NaN.

Ява рассматривает весь NaN как тихий NaN.

Питер Лори
источник
1
Это реализовано процессором, или оно встроено в JVM, как упоминает Богемский?
Naweed Chougle
3
JVM должна вызывать все, что будет реализовано правильно. На ПК CPU выполняет всю работу как таковую. На машине без этой поддержки JVM должна это реализовать. (Я не знаю ни одной такой машины)
Питер Лори
В то время, когда 8087 был опцией, библиотека C содержала эмулятор FP. Такие программы, как JVM, не должны были бы беспокоиться об этом в любом случае.
Маркиз Лорн
49

Почему эта логика

NaNзначит Not a Number. Что не число? Что-нибудь. У вас может быть что угодно с одной стороны и что угодно с другой стороны, поэтому ничто не гарантирует, что оба равны. NaNрассчитывается с Double.longBitsToDouble(0x7ff8000000000000L)и, как вы можете видеть в документации longBitsToDouble:

Если аргумент является любое значение в диапазоне 0x7ff0000000000001Lчерез 0x7fffffffffffffffLили в диапазоне от 0xfff0000000000001Lсквозной 0xffffffffffffffffL, то результат будет NaN.

Кроме того, NaNлогически рассматривается внутри API.


Документация

/** 
 * A constant holding a Not-a-Number (NaN) value of type
 * {@code double}. It is equivalent to the value returned by
 * {@code Double.longBitsToDouble(0x7ff8000000000000L)}.
 */
public static final double NaN = 0.0d / 0.0;

Кстати, NaN это тестирование в качестве образца кода:

/**
 * Returns {@code true} if the specified number is a
 * Not-a-Number (NaN) value, {@code false} otherwise.
 *
 * @param   v   the value to be tested.
 * @return  {@code true} if the value of the argument is NaN;
 *          {@code false} otherwise.
 */
static public boolean isNaN(double v) {
    return (v != v);
}

Решение

Что вы можете сделать, это использовать compare/ compareTo:

Double.NaNэтот метод считается равным самому себе и большим, чем все другие doubleзначения (в том числе Double.POSITIVE_INFINITY).

Double.compare(Double.NaN, Double.NaN);
Double.NaN.compareTo(Double.NaN);

Или equals:

Если thisи argumentоба представляют Double.NaN, то equalsметод возвращается true, хотя и Double.NaN==Double.NaNимеет значение false.

Double.NaN.equals(Double.NaN);
falsarella
источник
Знаете ли вы о каком-либо случае, когда NaN != NaNложь может сделать программы более сложными, чем NaN != NaNбыть правдой? Я знаю, что IEEE принял решение много лет назад, но с практической точки зрения я никогда не видел случаев, когда это было бы полезно. Если предполагается, что операция выполняется до тех пор, пока последовательные итерации не дадут одинакового результата, две последовательные итерации приведут к тому, что NaN будет «естественно» определен как условие выхода, если бы не это поведение.
суперкат
@supercat Как вы можете сказать, что два случайных не числа естественно равны? Или, скажем, примитивно равны? Думайте о NaN как об экземпляре, а не о чем-то примитивном. Каждый другой ненормальный результат - это отдельный случай чего-то странного, и даже если оба должны представлять одно и то же, использование == для разных экземпляров должно возвращать false. С другой стороны, при использовании equals это может быть обработано должным образом, как вы и собираетесь. [ Docs.oracle.com/javase/7/docs/api/java/lang/...
falsarella
@falsarella: Вопрос не в том, следует ли считать два случайных числа «определенно равными», а в том, в каких случаях полезно, чтобы любое число сравнивалось как «однозначно неравное» с самим собой. Если кто-то пытается вычислить предел f(f(f...f(x))), и он находит y=f[n](x)для некоторого nтакое, что результат f(y)неотличим от y, то yон будет неотличим от результата любого более глубоко вложенного f(f(f(...f(y))). Даже если кто-то хочет NaN==NaNбыть ложным, то быть Nan!=Nan также ложным будет менее «удивительно», чем x!=xбыть истинным для некоторых х.
суперкат
1
@falsarella: я считаю, что тип Double.NaNне Double, но double, поэтому вопрос связан с поведением double. Хотя существуют функции, которые могут проверять отношение эквивалентности, включающее doubleзначения, единственный известный мне ответ на вопрос «почему» (который является частью первоначального вопроса) - «потому что некоторые люди в IEEE не думали, что тестирование на равенство должно определить отношение эквивалентности ". Кстати, есть ли какой-то краткий идиоматический способ проверки xи yэквивалентности с использованием только примитивных операторов? Все известные мне формулировки довольно неуклюжи.
суперкат
1
лучший и простой ответ. Спасибо
Тарун Нагпал
16

Это может быть не прямой ответ на вопрос. Но если вы хотите проверить, что-то равно Double.NaNвам, следует использовать это:

double d = Double.NaN
Double.isNaN(d);

Это вернется true

JREN
источник
6

Javadoc для Double.NaN говорит все это:

Константа, содержащая значение типа Not-a-Number (NaN) типа double. Это эквивалентно значению, возвращаемому Double.longBitsToDouble(0x7ff8000000000000L).

Интересно, что источник для Doubleопределяет NaNтаким образом:

public static final double NaN = 0.0d / 0.0;

Особое поведение, которое вы описываете, встроено в JVM.

Богемский
источник
5
Это аппаратно связано в JVM или реализовано процессором, как упоминает Питер?
Naweed Chougle
4

согласно стандарту IEEE для арифметики с плавающей запятой для чисел двойной точности,

Стандартное представление IEEE двойной точности с плавающей запятой требует 64-битного слова, которое может быть представлено как пронумерованное от 0 до 63, слева направо

введите описание изображения здесь где,

S: Sign  1 bit
E: Exponent  11 bits
F: Fraction  52 bits 

Если E=2047(все Eони 1) и Fотличны от нуля, то V=NaN(«Не число»)

Что значит,

Если все Eбиты равны 1, и если есть какой-либо ненулевой бит, Fто число равно NaN.

Поэтому, в частности, все последующие номера NaN,

0 11111111 0000000000000000010000000000000000000000000000000000 = NaN
1 11111111 0000010000000000010001000000000000001000000000000000 = NaN
1 11111111 0000010000011000010001000000000000001000000000000000 = NaN

В частности, вы не можете проверить

if (x == Double.NaN) 

проверить, равен ли конкретный результат Double.NaN, потому что все значения «не числа» считаются различными. Однако вы можете использовать Double.isNaNметод:

if (Double.isNaN(x)) // check whether x is "not a number"
Суфиян Гори
источник
3

NaN - это специальное значение, которое обозначает «не число»; это результат некоторых недопустимых арифметических операций, таких как sqrt(-1), и имеет (иногда раздражающее) свойство that NaN != NaN.

Фред Фу
источник
2

Не число представляет результат операций, результат которых не может быть представлен числом. Самая известная операция - 0/0, результат которой неизвестен.

По этой причине NaN не равен ничему (включая другие не-числовые значения). Для получения дополнительной информации, просто посетите страницу википедии: http://en.wikipedia.org/wiki/NaN

Matteo
источник
-1: не представляет результат 0/0. 0/0всегда NaN, но NaN может быть результатом других операций - таких как 2+NaN: an operation that has no mathematically definite result produces NaNсогласно ответу @AdrianMitev
ANeves
Действительно, NaN означает «не число», и это результат всех операций, которые в результате имеют неопределенное или непредставимое значение. Самая известная и распространенная операция - 0/0, но, очевидно, есть множество других операций, которые имеют такой же результат. Я согласен, что мой ответ может быть улучшен, но я не согласен с -1 ... Я только что проверил, что в Википедии также используются операции 0/0 в качестве первого примера работы с результатом NaN ( en.wikipedia.org/wiki/ NaN ).
Маттео
Кроме того, это в исходном коде Java для Double: public static final double NaN = 0.0d / 0.0;
Гийом
1
@Matteo +0, теперь, когда ложное утверждение исчезло. И мои -1 или +1 не для вас, чтобы согласиться или не согласиться; но хорошо оставить комментарий с -1, чтобы автор мог понять, почему его ответ считается бесполезным, - и изменить его, если он того пожелает.
ANeves
@Guillaume, если этот комментарий был предназначен для меня, пожалуйста, перефразируйте его: я не понимаю его.
ANeves
0

По этой ссылке , он имеет различные ситуации и трудно запомнить. Вот как я их помню и различаю. NaNозначает «математически не определено», например: «результат 0, деленный на 0, не определен», и поскольку он не определен, то «сравнение, связанное с неопределенным, конечно, не определено». Кроме того, это больше похоже на математические предпосылки. С другой стороны, как положительная, так и отрицательная бесконечность предопределены и окончательны, например, «положительное или отрицательное бесконечно большое хорошо определено математически».

Тийна
источник