Co w Javie oznacza NaN?


107

Mam program, który próbuje zmniejszyć doubleliczbę do żądanej liczby. Wynik, który otrzymuję, to NaN.

Co to NaNznaczy w Javie?


Istnieje dobry opis NaN i typowych pułapek podczas korzystania z NaN w Javie: ppkwok.blogspot.co.uk/2012/11/ ...
Phil

Jeśli pytasz „po co jest NaN?” w Javie (lub jakimkolwiek innym języku), mogę podać przypadek użycia, w którym jest to bardzo przydatne: kiedy mam dwuwymiarową tablicę liczb zmiennoprzecinkowych, ale moje obliczenia nie mają sensownej wartości dla jakiejś części tej dwuwymiarowej tablicy, Wypełnię tę wartość „NaN”. Można to wykorzystać do zasygnalizowania dalszym użytkownikom moich obliczeń (np. Gdy jest on przekształcony w obraz rastrowy) „nie zwracajcie uwagi na wartość w tym momencie”. Bardzo przydatne!
Dan H

A tak przy okazji, co - dokładnie - to znaczy „pomniejszyć” podwójną? Ciekawy ...
Dan H

Odpowiedzi:


153

Zaczerpnięte z tej strony :

„NaN” oznacza „nie jest liczbą”. „Nan” jest wytwarzane, jeśli operacja zmiennoprzecinkowa ma pewne parametry wejściowe, które powodują, że operacja daje niezdefiniowany wynik. Na przykład liczba 0,0 podzielona przez 0,0 jest arytmetycznie niezdefiniowana. Obliczanie pierwiastka kwadratowego z liczby ujemnej również jest niezdefiniowane.


16
Dodatkowo, NaN jest dość wyraźnie zdefiniowane w IEEE Standard for Floating-Point Arithmetic (IEEE 754), za którym Java ślepo podąża. Czytanie standardu otwiera oczy na wiele rzeczy, a jedną z nich jest wielokrotność zera.
Esko

37
Ponadto, NaNma ciekawą właściwość bycia tylko „numer”, która nie jest taka sama jak sama gdy porównywane. Dlatego powszechny (iw wielu językach jedyny) test, jeśli liczba xjest NaNnastępujący:boolean isNaN(x){return x != x;}
quazgar

3
Link w odpowiedzi jest martwy?
Pang

3
... "Pierwiastek kwadratowy z liczby ujemnej jest nieokreślony (w arytmetyce)" ... Nie! jego faktycznie ii niektóre języki, takie jak umowy Pythona bardzo dobrze z nim ... To może być nie tak w javaty
Rafael T

5
@RafaelT Powiedziałbym, że jest niezdefiniowany w niezłożonej arytmetyce. W Javie nie ma możliwości przypisania liczby zespolonej do liczby zmiennoprzecinkowej lub podwójnej. Python jest wpisywany dynamicznie, dlatego w tym przypadku może być po prostu zwrócenie liczby zespolonej.
sstn

19

NaNoznacza „Not a Number” i jest w zasadzie reprezentacją specjalnej wartości zmiennoprzecinkowej w standardzie zmiennoprzecinkowym IEE 754 . NaN ogólnie oznacza, że ​​wartość jest czymś, czego nie można wyrazić za pomocą prawidłowej liczby zmiennoprzecinkowej.

Konwersja da tę wartość, gdy konwertowana wartość jest czymś innym, na przykład podczas konwersji ciągu, który nie reprezentuje liczby.


Jak konwertować? Z parseFloat()czy parseDouble? Albo coś innego?
Alonso del Arte

14

NaNoznacza „Not a Number” i jest wynikiem niezdefiniowanych operacji na liczbach zmiennoprzecinkowych, takich jak na przykład dzielenie zera przez zero. (Zauważ, że chociaż dzielenie liczby niezerowej przez zero jest zwykle niezdefiniowane w matematyce, nie daje to NaN, ale dodatnią lub ujemną nieskończoność).


5

NaNoznacza „To nie jest liczba”. Jest to specjalna wartość zmiennoprzecinkowa, która oznacza, że ​​wynik operacji nie został zdefiniowany lub nie można go przedstawić jako liczby rzeczywistej.

Zobacz tutaj, aby uzyskać więcej informacji na temat tej wartości.




4

To nie jest liczba. Jest to powszechna reprezentacja niemożliwej wartości liczbowej w wielu językach programowania.


4

Minimalny działający przykład

Pierwszą rzeczą, którą musisz wiedzieć, jest to, że koncepcja NaN jest implementowana bezpośrednio na sprzęcie procesora.

Wydaje się, że wszystkie główne współczesne procesory są zgodne z IEEE 754, który określa formaty zmiennoprzecinkowe, a NaN, które są po prostu specjalnymi wartościami zmiennoprzecinkowymi, są częścią tego standardu.

Dlatego koncepcja będzie bardzo podobna w każdym języku, w tym w Javie, który po prostu emituje kod zmiennoprzecinkowy bezpośrednio do procesora.

Zanim przejdziesz dalej, możesz najpierw przeczytać następujące odpowiedzi, które napisałem:

Teraz trochę akcji w Javie. Większość interesujących funkcji, które nie są w języku podstawowym, znajduje się wewnątrz java.lang.Float.

Nan.java

import java.lang.Float;
import java.lang.Math;

public class Nan {
    public static void main(String[] args) {
        // Generate some NaNs.
        float nan            = Float.NaN;
        float zero_div_zero  = 0.0f / 0.0f;
        float sqrt_negative  = (float)Math.sqrt(-1.0);
        float log_negative   = (float)Math.log(-1.0);
        float inf_minus_inf  = Float.POSITIVE_INFINITY - Float.POSITIVE_INFINITY;
        float inf_times_zero = Float.POSITIVE_INFINITY * 0.0f;
        float quiet_nan1     = Float.intBitsToFloat(0x7fc00001);
        float quiet_nan2     = Float.intBitsToFloat(0x7fc00002);
        float signaling_nan1 = Float.intBitsToFloat(0x7fa00001);
        float signaling_nan2 = Float.intBitsToFloat(0x7fa00002);
        float nan_minus      = -nan;

        // Generate some infinities.
        float positive_inf   = Float.POSITIVE_INFINITY;
        float negative_inf   = Float.NEGATIVE_INFINITY;
        float one_div_zero   = 1.0f / 0.0f;
        float log_zero       = (float)Math.log(0.0);

        // Double check that they are actually NaNs.
        assert  Float.isNaN(nan);
        assert  Float.isNaN(zero_div_zero);
        assert  Float.isNaN(sqrt_negative);
        assert  Float.isNaN(inf_minus_inf);
        assert  Float.isNaN(inf_times_zero);
        assert  Float.isNaN(quiet_nan1);
        assert  Float.isNaN(quiet_nan2);
        assert  Float.isNaN(signaling_nan1);
        assert  Float.isNaN(signaling_nan2);
        assert  Float.isNaN(nan_minus);
        assert  Float.isNaN(log_negative);

        // Double check that they are infinities.
        assert  Float.isInfinite(positive_inf);
        assert  Float.isInfinite(negative_inf);
        assert !Float.isNaN(positive_inf);
        assert !Float.isNaN(negative_inf);
        assert one_div_zero == positive_inf;
        assert log_zero == negative_inf;
            // Double check infinities.

        // See what they look like.
        System.out.printf("nan            0x%08x %f\n", Float.floatToRawIntBits(nan           ), nan           );
        System.out.printf("zero_div_zero  0x%08x %f\n", Float.floatToRawIntBits(zero_div_zero ), zero_div_zero );
        System.out.printf("sqrt_negative  0x%08x %f\n", Float.floatToRawIntBits(sqrt_negative ), sqrt_negative );
        System.out.printf("log_negative   0x%08x %f\n", Float.floatToRawIntBits(log_negative  ), log_negative  );
        System.out.printf("inf_minus_inf  0x%08x %f\n", Float.floatToRawIntBits(inf_minus_inf ), inf_minus_inf );
        System.out.printf("inf_times_zero 0x%08x %f\n", Float.floatToRawIntBits(inf_times_zero), inf_times_zero);
        System.out.printf("quiet_nan1     0x%08x %f\n", Float.floatToRawIntBits(quiet_nan1    ), quiet_nan1    );
        System.out.printf("quiet_nan2     0x%08x %f\n", Float.floatToRawIntBits(quiet_nan2    ), quiet_nan2    );
        System.out.printf("signaling_nan1 0x%08x %f\n", Float.floatToRawIntBits(signaling_nan1), signaling_nan1);
        System.out.printf("signaling_nan2 0x%08x %f\n", Float.floatToRawIntBits(signaling_nan2), signaling_nan2);
        System.out.printf("nan_minus      0x%08x %f\n", Float.floatToRawIntBits(nan_minus     ), nan_minus     );
        System.out.printf("positive_inf   0x%08x %f\n", Float.floatToRawIntBits(positive_inf  ), positive_inf  );
        System.out.printf("negative_inf   0x%08x %f\n", Float.floatToRawIntBits(negative_inf  ), negative_inf  );
        System.out.printf("one_div_zero   0x%08x %f\n", Float.floatToRawIntBits(one_div_zero  ), one_div_zero  );
        System.out.printf("log_zero       0x%08x %f\n", Float.floatToRawIntBits(log_zero      ), log_zero      );

        // NaN comparisons always fail.
        // Therefore, all tests that we will do afterwards will be just isNaN.
        assert !(1.0f < nan);
        assert !(1.0f == nan);
        assert !(1.0f > nan);
        assert !(nan == nan);

        // NaN propagate through most operations.
        assert Float.isNaN(nan + 1.0f);
        assert Float.isNaN(1.0f + nan);
        assert Float.isNaN(nan + nan);
        assert Float.isNaN(nan / 1.0f);
        assert Float.isNaN(1.0f / nan);
        assert Float.isNaN((float)Math.sqrt((double)nan));
    }
}

GitHub upstream .

Biegnij z:

javac Nan.java && java -ea Nan

Wynik:

nan            0x7fc00000 NaN
zero_div_zero  0x7fc00000 NaN
sqrt_negative  0xffc00000 NaN
log_negative   0xffc00000 NaN
inf_minus_inf  0x7fc00000 NaN
inf_times_zero 0x7fc00000 NaN
quiet_nan1     0x7fc00001 NaN
quiet_nan2     0x7fc00002 NaN
signaling_nan1 0x7fa00001 NaN
signaling_nan2 0x7fa00002 NaN
nan_minus      0xffc00000 NaN
positive_inf   0x7f800000 Infinity
negative_inf   0xff800000 -Infinity
one_div_zero   0x7f800000 Infinity
log_zero       0xff800000 -Infinity

Z tego uczymy się kilku rzeczy:

  • dziwne operacje pływające, które nie mają żadnego sensownego wyniku, dają NaN:

    • 0.0f / 0.0f
    • sqrt(-1.0f)
    • log(-1.0f)

    wygeneruj plik NaN.

    W C faktycznie można zażądać podniesienia sygnałów w takich operacjach w feenableexceptcelu ich wykrycia, ale nie sądzę, aby było to ujawnione w Javie: Dlaczego dzielenie liczby całkowitej przez zero 1/0 daje błąd, ale zmiennoprzecinkowe 1 / 0,0 zwraca „Inf”?

  • dziwne operacje, które są na granicy plus lub minus nieskończoności, dają jednak + - nieskończoność zamiast NaN

    • 1.0f / 0.0f
    • log(0.0f)

    0.0 prawie mieści się w tej kategorii, ale prawdopodobnie problem polega na tym, że może dojść do plusa lub minus nieskończoności, więc pozostawiono ją jako NaN.

  • jeśli NaN jest wartością wejściową operacji zmiennoprzecinkowej, wyjście również ma tendencję do NaN

  • istnieje kilka możliwych wartości NaN 0x7fc00000, 0x7fc00001, 0x7fc00002, chociaż x86_64 wydaje się wygenerować tylko 0x7fc00000.

  • NaN i nieskończoność mają podobną reprezentację binarną.

    Rozważmy kilka z nich:

    nan          = 0x7fc00000 = 0 11111111 10000000000000000000000
    positive_inf = 0x7f800000 = 0 11111111 00000000000000000000000
    negative_inf = 0xff800000 = 1 11111111 00000000000000000000000
                                | |        |
                                | |        mantissa
                                | exponent
                                |
                                sign
    

    Na tej podstawie potwierdzamy, co określa IEEE754:

    • zarówno NaN, jak i nieskończoności mają wykładnik == 255 (wszystkie jedynki)
    • nieskończoności mają mantysę == 0. Dlatego są tylko dwie możliwe nieskończoności: + i -, rozróżniane przez bit znaku
    • NaN ma mantysę! = 0. Jest zatem kilka możliwości, z wyjątkiem mantysy == 0, która jest nieskończonością
  • NaN mogą być dodatnie lub ujemne (górny bit), chociaż nie ma to wpływu na normalne operacje

Testowane w Ubuntu 18.10 amd64, OpenJDK 1.8.0_191.


3

Nie jest facetem od Javy, ale w JS i innych językach używam tego "Not a Number", co oznacza, że ​​jakaś operacja spowodowała, że ​​stała się niepoprawną liczbą.



3

Nieprawidłowa wartość zmiennoprzecinkowa (np. Wynik dzielenia przez zero)

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


Spieram się z tą odpowiedzią. Po pierwsze: „NaN” JEST prawidłową wartością dla float IEEE! (W końcu jest to zdefiniowane w specyfikacji ... więc jest "ważne", prawda?). Po drugie: „dzielenie przez zero” może być reprezentowane przez IEEE „Pozytywna nieskończoność” lub „Negatywna nieskończoność”; lepszym przykładem „NaN” jest „zero podzielone przez zero”, jak poprawnie wskazały inne odpowiedzi.
Dan H

„Prawidłowa wartość” i „zdefiniowana w specyfikacji” to nie to samo. Zgoda na 0/0.
Vladimir Dyuzhev
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.