Dlaczego Double.NaN == Double.NaN zwraca false?

155

Właśnie studiowałem pytania OCPJP i znalazłem ten dziwny kod:

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

Kiedy uruchomiłem kod, otrzymałem:

false
true

Jaki jest wynik, falsegdy porównujemy dwie rzeczy, które wyglądają tak samo? Co to NaNznaczy?

Politycznie niezależny
źródło
8
To jest naprawdę dziwne. Ponieważ Double.NaN jest statycznym końcem, porównanie z == powinno zwrócić prawdę. +1 na pytanie.
Stephan,
2
To samo dotyczy Pythona:In [1]: NaN==NaN Out[1]: False
tdc
58
To samo dotyczy wszystkich języków, które są zgodne ze standardem IEEE 754.
zzzzBov
4
Intuicja: „Hello” nie jest liczbą, prawda (boolean) też nie jest liczbą. NaN! = NaN z tego samego powodu „Hello”! = True
Kevin
3
@Stephan: Porównanie z Double.NaN==Double.NaNpowinno rzeczywiście zwrócić prawdę, gdyby Double.NaNbyły typu java.lang.Double. Jednak jego typ jest prymitywny doublei obowiązują reguły operatora double(które wymagają tej nierówności dla zgodności z IEEE 754, jak wyjaśniono w odpowiedziach).
sleske

Odpowiedzi:

139

NaN oznacza „To nie jest liczba”.

Java Language Specification (JLS) Third Edition mówi :

Operacja, która się przepełnia, tworzy nieskończoność ze znakiem, operacja, która powoduje niedomiar, wytwarza zdenormalizowaną wartość lub zero ze znakiem, a operacja, która nie ma matematycznie określonego wyniku, daje NaN. Wszystkie operacje numeryczne z NaN jako operandem dają wynik NaN. Jak już zostało opisane, NaN jest nieuporządkowany, więc operacja numerycznego porównania obejmująca jeden lub dwa NaN zwraca falsei każde !=porównanie obejmujące NaN zwraca true, w tym x!=xkiedy xjest NaN.

Adrian Mitev
źródło
4
@nibot: W większości prawda . Każde porównanie z pływakiem zgodnym z IEEE da wynik false. Tak więc ten standard różni się od Javy tym, że wymaga tego IEEE (NAN != NAN) == false.
Drew Dormann
2
Otwierając puszkę Pandory - gdzie widzisz, że „IEEE wymaga tego (NAN! = NAN) == false”?
Nadzorca
62

NaN z definicji nie jest równe żadnej liczbie, w tym NaN. Jest to część standardu IEEE 754 i zaimplementowana przez CPU / FPU. Nie jest to coś, co JVM musi dodawać jakąkolwiek logikę do obsługi.

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

Porównanie z NaN zawsze zwraca nieuporządkowany wynik, nawet w porównaniu z samym sobą. ... Predykaty równości i nierówności nie sygnalizują, więc x = x zwracające fałsz może być użyte do sprawdzenia, czy x jest cichym NaN.

Java traktuje wszystkie NaN jako ciche NaN.

Peter Lawrey
źródło
1
Czy jest zaimplementowany przez procesor, czy jest na stałe podłączony do JVM, jak wspomina Bohemian?
Naweed Chougle
3
JVM musi wywołać wszystko, co zaimplementuje je poprawnie. Na komputerze PC całą pracę wykonuje procesor. Na maszynie bez tej obsługi JVM musi ją zaimplementować. (Nie znam żadnej takiej maszyny)
Peter Lawrey
W czasach, gdy 8087 był opcją, biblioteka C zawierała emulator FP. Programy takie jak JVM i tak nie musiałyby się tym martwić.
Markiz Lorne
49

Skąd ta logika

NaNznaczy Not a Number. Co to nie jest liczba? Byle co. Możesz mieć wszystko po jednej stronie i wszystko po drugiej, więc nic nie gwarantuje, że oba są równe. NaNjest obliczany za pomocą Double.longBitsToDouble(0x7ff8000000000000L)i jak widać w dokumentacji longBitsToDouble:

Jeśli argumentem jest dowolna wartość w zakresie 0x7ff0000000000001Ldo 0x7fffffffffffffffLlub w zakresie 0xfff0000000000001Ldo 0xffffffffffffffffL, wynikiem jest NaN.

Ponadto NaNjest logicznie traktowany wewnątrz interfejsu API.


Dokumentacja

/** 
 * 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;

Nawiasem mówiąc, NaN jest testowany jako przykładowy kod:

/**
 * 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);
}

Rozwiązanie

Możesz użyć compare/ compareTo:

Double.NaNjest uważany przez tę metodę za równy sobie i większy niż wszystkie inne doublewartości (w tym Double.POSITIVE_INFINITY).

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

Albo equals:

Jeśli thisi argumentoba reprezentują Double.NaN, equalsmetoda zwraca true, mimo że Double.NaN==Double.NaNma wartość false.

Double.NaN.equals(Double.NaN);
falsarella
źródło
Czy znasz jakiś przypadek, w którym posiadanie NaN != NaNfałszu uczyniłoby programy bardziej skomplikowanymi niż posiadanie NaN != NaNprawdy? Wiem, że IEEE podjęło decyzję wieki temu, ale z praktycznego punktu widzenia nigdy nie widziałem przypadków, w których jest to przydatne. Jeśli operacja ma być wykonywana, dopóki kolejne iteracje nie dadzą tego samego wyniku, mając dwie kolejne iteracje, NaN byłby „naturalnie” wykrywany jako warunek wyjścia, gdyby nie to zachowanie.
supercat,
@supercat Jak możesz powiedzieć, że dwie losowe liczby nie są naturalnie równe? Albo powiedzieć, prymitywnie równi? Pomyśl o NaN jako o instancji, a nie o czymś prymitywnym. Każdy inny nieprawidłowy wynik jest innym wystąpieniem czegoś dziwnego i nawet jeśli oba powinny reprezentować to samo, użycie == dla różnych instancji musi zwrócić fałsz. Z drugiej strony, gdy używasz równych sobie, może być obsługiwany zgodnie z zamierzeniami. [ docs.oracle.com/javase/7/docs/api/java/lang/…
falsarella
@falsarella: Problemem nie jest to, czy dwie liczby losowe należy uznać za „zdecydowanie równe”, ale raczej w jakich przypadkach warto porównać dowolną liczbę jako „zdecydowanie nierówną”. Jeśli ktoś próbuje obliczyć granicę f(f(f...f(x)))i znajduje y=f[n](x)dla kogoś ntaki, że wynik f(y)jest nie do odróżnienia od y, to ybędzie nie do odróżnienia od wyniku jakiegokolwiek głębiej zagnieżdżonego f(f(f(...f(y))). Nawet gdyby ktoś chciał NaN==NaNbyć fałszywy, bycie fałszywym Nan!=Nan również byłoby mniej „zaskakujące” niż x!=xbycie prawdziwym dla jakiegoś x.
supercat
1
@falsarella: Uważam, że typ Double.NaNnie jest Double, ale doublewięc pytanie dotyczy zachowania double. Chociaż istnieją funkcje, które mogą testować relację równoważności obejmującą doublewartości, jedyną przekonującą odpowiedzią, jaką znam na pytanie „dlaczego” (która jest częścią pierwotnego pytania), jest „ponieważ niektórzy ludzie w IEEE nie sądzili, zdefiniować relację równoważności ”. Przy okazji, czy istnieje jakiś zwięzły idiomatyczny sposób testowania xi yrównoważności przy użyciu tylko operatorów pierwotnych? Wszystkie znane mi formuły są raczej niezgrabne.
supercat
1
najlepsza i prosta odpowiedź. Dziękuję
Tarun Nagpal
16

Może to nie jest bezpośrednia odpowiedź na to pytanie. Ale jeśli chcesz sprawdzić, czy coś jest równe Double.NaN, użyj tego:

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

To wróci true

JREN
źródło
6

Javadoc dla Double.NaN mówi wszystko:

Stała utrzymująca wartość typu Not-a-Number (NaN) double. Odpowiada wartości zwracanej przez Double.longBitsToDouble(0x7ff8000000000000L).

Co ciekawe, źródło Doubledefiniuje w NaNten sposób:

public static final double NaN = 0.0d / 0.0;

Opisane przez ciebie specjalne zachowanie jest na stałe przypisane do maszyny JVM.

Czeski
źródło
5
Czy jest na stałe podłączony do maszyny JVM, czy też jest implementowany przez procesor, jak wspomina Peter?
Naweed Chougle
4

zgodnie ze standardem IEEE dla arytmetyki zmiennoprzecinkowej dla liczb Double Precision,

Standardowa reprezentacja zmiennoprzecinkowa podwójnej precyzji IEEE wymaga 64-bitowego słowa, które można przedstawić jako ponumerowane od 0 do 63, od lewej do prawej

wprowadź opis obrazu tutaj gdzie,

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

Jeśli E=2047(wszystkie E1) i Fjest niezerowe, to V=NaN(„To nie jest liczba”)

Co znaczy,

Jeśli wszystkie Ebity mają wartość 1 i jeśli jest jakiś niezerowy bit, Fliczba wynosi NaN.

dlatego między innymi wszystkie kolejne liczby są NaN,

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

W szczególności nie możesz testować

if (x == Double.NaN) 

aby sprawdzić, czy dany wynik jest równy Double.NaN, ponieważ wszystkie wartości „niebędące liczbą” są traktowane jako różne. Możesz jednak skorzystać z Double.isNaNmetody:

if (Double.isNaN(x)) // check whether x is "not a number"
Sufiyan Ghori
źródło
3

NaN to specjalna wartość oznaczająca „nie jest liczbą”; jest wynikiem pewnych nieprawidłowych operacji arytmetycznych, takich jak sqrt(-1), i ma tę (czasami irytującą) właściwość NaN != NaN.

Fred Foo
źródło
2

Brak liczby reprezentuje wynik operacji, których wyniku nie można przedstawić za pomocą liczby. Najbardziej znaną operacją jest 0/0, której wynik nie jest znany.

Z tego powodu NaN nie jest równe żadnemu (w tym innym wartościom niebędącym liczbą). Aby uzyskać więcej informacji, po prostu sprawdź stronę wikipedii: http://en.wikipedia.org/wiki/NaN

Matteo
źródło
-1: nie reprezentuje wyniku 0/0. 0/0jest zawsze NaN, ale NaN może być wynikiem innych operacji - takich jak 2+NaN: an operation that has no mathematically definite result produces NaNzgodnie z odpowiedzią @AdrianMitev
ANeves
Rzeczywiście, NaN oznacza „Not a Number” i jest wynikiem wszystkich operacji, których wynikiem jest niezdefiniowana lub niemożliwa do przedstawienia wartość. Najbardziej znaną i powszechną operacją jest 0/0, ale oczywiście jest mnóstwo innych operacji, które dają ten sam wynik. Zgadzam się, że moja odpowiedź mogłaby być poprawiona, ale nie zgadzam się co do -1 ... Właśnie sprawdziłem, że również Wikipedia używa operacji 0/0 jako pierwszego przykładu operacji z wynikiem NaN ( en.wikipedia.org/wiki/ NaN ).
Matteo
Jest to również w źródłach Javy dla Double: public static final double NaN = 0,0d / 0,0;
Guillaume
1
@Matteo +0, teraz, gdy fałszywe stwierdzenie zniknęło. A moje -1 lub +1 nie są dla ciebie, aby się zgodzić lub nie; ale dobrze jest zostawić komentarz z -1, aby autor mógł zrozumieć, dlaczego jego odpowiedź jest uważana za bezużyteczną - i zmienić ją, jeśli sobie tego życzy.
ANeves
@ Guillaume, jeśli ten komentarz był przeznaczony dla mnie, przeformułuj go: nie rozumiem.
ANeves
0

Według tego linku ma różne sytuacje i trudno je zapamiętać. Tak je pamiętam i rozróżniam. NaNoznacza „matematycznie niezdefiniowany”, na przykład: „wynik dzielenia 0 przez 0 jest niezdefiniowany” i ponieważ jest niezdefiniowany, więc „porównanie związane z niezdefiniowanym jest oczywiście niezdefiniowane”. Poza tym działa bardziej jak przesłanki matematyczne. Z drugiej strony, zarówno dodatnia, jak i ujemna nieskończoność są predefiniowane i ostateczne, na przykład „dodatnia lub ujemna nieskończoność jest dobrze zdefiniowana matematycznie”.

Tiina
źródło