2013-07-27 13 views
20

Czytam Effective Java przez Joshua Bloch i punkt 8: Słuchajcie umowy generalnej gdy przesłanianie równa, to stwierdzenie jest napisaneDlaczego nie możemy użyć „==” porównać dwa pływaka lub podwójne numery

dla pól zmiennoprzecinkowych, użyj metody Float.compare; i dla podwójnych pól, użyj Double.compare. Specjalna obróbka zmiennopozycyjnych i podwójnych pól jest konieczna dzięki istnieniu wartości Float.NaN, -0,0f i analogicznych podwójnych stałych ;

Może ktoś wyjaśnić mi przykład, dlaczego nie możemy używać == dla pływaka lub podwójne porównaniu

+2

Czy NaN jest równe dowolnemu innemu NaN? Czy jest większy niż, mniejszy lub równy jakiejkolwiek innej liczbie? Czy -0,0f jest równe 0,0f? –

+0

http://stackoverflow.com/a/9341669/1276341 – MrLore

+0

To zależy od tego, co chcesz zrobić. IIRC (nie trzymaj mnie tego) 'NaN == NaN' jest fałszywe, np. Ale oficjalna specyfikacja "porównania" nie daje wiele informacji o tym, jak obsługiwane są przypadki "dziwne". Ważniejsze jest, aby pamiętać, aby nigdy nie porównywać "równości" w ogóle, z wyjątkiem szczególnych przypadków, gdy wiesz, co robisz. –

Odpowiedz

5

float (i double) mają jakieś specjalne sekwencje bitów, które są zastrzeżone dla szczególnych znaczeń, które nie są „numery” :

  • Ujemna nieskończoność, reprezentacja wewnętrzna 0xff800000
  • Pozytywna nieskończoność, reprezentacja wewnętrzna 0x7f800000
  • Not a Number, reprezentacja wewnętrzna 0x7fc00000

Każdy z tych zwrotów 0 (co oznacza, że ​​są „takie same”), w porównaniu do samego korzystania Float.compare(), ale następujące porównania używając == różnią się od tego dla Float.NaN:

Float.NEGATIVE_INFINITY == Float.NEGATIVE_INFINITY // true 
Float.POSITIVE_INFINITY == Float.POSITIVE_INFINITY // true 
Float.NaN == Float.NaN // false 

Dlatego przy porównywaniu float wartości, aby być spójne dla wszystkich wartości, w tym szczególną wartość Float.NaN, Float.compare() jest najlepszym rozwiązaniem.

To samo dotyczy double.

+0

@EricPostpischil 'Float.compare()' zwraca 'int', nie" false "(lub" true "). Wszystkie następujące wywołania funkcji 'Float.compare()' zwracają '0':' Float.compare (Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY) ',' Float.compare (Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY), 'Float.poradź (Float.NaN, Float.NaN) '. Jestem tego pewien, ponieważ właśnie go wykonałem. – Bohemian

+0

@EricPostpischil OK - Edytowałem odpowiedź, aby wyjaśnić, co oznacza zero (nawet jeśli jest to w javadoc z 'compare()') – Bohemian

16

Od apidoc, Float.compare:

Porównuje dwie wartości określone pływaka. Znak wartości całkowita zwracana jest taka sama, jak w przypadku liczb całkowitych, które byłyby zwrócony przez wywołanie:

nowy float (f1) .compareTo (nowa Float (F2))

Float.compareTo:

Porównuje dwa obiekty Float numerycznie. Istnieją dwa sposoby, w których porównania wykonywane za pomocą tej metody różnią się od tych wykonywanych przez język Java numerycznych operatorów porównania (<, < =, ==,> =>), gdy stosuje się do pierwotnych wartości float:

  • Float. NaN jest przez tę metodę uznawany za równy sobie i większy niż wszystkie pozostałe wartości zmiennoprzecinkowe (w tym Float.POSITIVE_INFINITY).
  • W tej metodzie wartość0,0f jest większa niż -0,0f.

Zapewnia to, że naturalna kolejność obiektów Float nałożonych tą metodą jest równa wartościom równym.

Rozważmy następujący kod:

System.out.println(-0.0f == 0.0f); //true 
    System.out.println(Float.compare(-0.0f, 0.0f) == 0 ? true : false); //false  
    System.out.println(Float.NaN == Float.NaN);//false 
    System.out.println(Float.compare(Float.NaN, Float.NaN) == 0 ? true : false); //true 
    System.out.println(-0.0d == 0.0d); //true 
    System.out.println(Double.compare(-0.0d, 0.0d) == 0 ? true : false);//false  
    System.out.println(Double.NaN == Double.NaN);//false 
    System.out.println(Double.compare(Double.NaN, Double.NaN) == 0 ? true : false);//true   

ouput nie jest prawidłowe, ponieważ coś, co nie jest liczbą, nie jest po prostu liczbą, i powinny być traktowane na równi z punktu porównania liczby widok. Oczywiste jest również, że 0=-0.

Zobaczmy co Float.compare robi:

public static int compare(float f1, float f2) { 
    if (f1 < f2) 
     return -1;   // Neither val is NaN, thisVal is smaller 
    if (f1 > f2) 
     return 1;   // Neither val is NaN, thisVal is larger 

    int thisBits = Float.floatToIntBits(f1); 
    int anotherBits = Float.floatToIntBits(f2); 

    return (thisBits == anotherBits ? 0 : // Values are equal 
      (thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN) 
      1));       // (0.0, -0.0) or (NaN, !NaN) 
} 

Float.floatToIntBits:

Zwraca reprezentację podanej wartości zmiennoprzecinkowej według zmiennoprzecinkowych IEEE 754 "single Format" układ nieco . Bit 31 (bit wybrany przez maskę 0x80000000) reprezentuje znak liczby zmiennoprzecinkowej. Bity 30-23 (bity wybrane przez maskę 0x7f800000) reprezentują wykładnik. Bity 22-0 (bity wybrane przez maskę 0x007fffff) reprezentują wartość istotną (czasami zwaną mantysą) liczby zmiennoprzecinkowej.

Jeśli argumentem jest nieskończoność dodatnia, wynikiem jest 0x7f800000.

Jeśli argumentem jest ujemna nieskończoność, wynikiem jest 0xff800000.

Jeśli argumentem jest NaN, wynikiem jest 0x7fc00000.

We wszystkich przypadkach wynik jest liczbą całkowitą, przy podawaniu w sposób intBitsToFloat (int) wytworzy wartość zmiennoprzecinkową samo jako argument floatToIntBits (lecz wszystkie wartości NaN są w postaci pojedynczej "kanoniczna" wartość NaN).

Z JLS 15.20.1. Numerical Comparison Operators <, <=, >, and >=

Wynik porównania zmiennoprzecinkowej, jak określono w specyfikacji standardu IEEE 754, jest

  • Jeżeli jedna argumentu jest liczbą, a następnie wynik jest fałszywy.

  • Wszystkie wartości inne niż NaN są uporządkowane, z ujemną nieskończonością mniejszą niż wszystkie wartości skończone i dodatnią nieskończonością większą niż wszystkie wartości skończone.

  • Pozytywne zero i ujemne zero są uważane za równe. Na przykład -0.0 < 0.0 jest fałszywe, ale -0.0 < = 0.0 jest prawdziwe.

  • Należy jednak zauważyć, że metody Math.min i Math.max traktują ujemne zero jako bezwzględnie mniejsze niż dodatnie zero.

ścisłego porównań gdzie operandy są pozytywne i negatywne zerowy zerowy wynik będzie źle.

Z JLS 15.21.1. Numerical Equality Operators == and !=:

Wynik porównania zmiennoprzecinkowej, jak określono w specyfikacji standardu IEEE 754, jest badanie równość

zmiennoprzecinkowych są wykonywane zgodnie z zasady standardu IEEE 754:

  • Jeśli któryś argument jest NaN, wówczas wynik == jest fałszywy, ale wynik = prawda!. Rzeczywiście, test x! = X jest prawdziwy wtedy i tylko wtedy, gdy wartość x wynosi NaN. Metody Float.isNaN i Double.isNaN mogą być również użyte do sprawdzenia, czy wartość jest NaN.

  • Pozytywne zero i ujemne zero są uważane za równe. Na przykład -0.0 == 0.0 jest prawdziwe.

  • W przeciwnym razie dwie różne wartości zmiennoprzecinkowe są uważane za nierówne przez operatorów równości. W szczególności istnieje jedna wartość reprezentująca pozytywną nieskończoność i jedna wartość reprezentująca ujemną nieskończoność; każdy porównuje się tylko z samym sobą, a każdy porównuje nierówność ze wszystkimi innymi wartościami.

Dla porównania równości gdzie oba operandy są NaN wynik będzie źle.

Od total ordering (=, <, >,<=, >=) jest używany przez wiele ważnych algorytmów (patrz all the classes that implement the Comparable interface) lepiej jest użyć metody porównania, ponieważ da to bardziej spójne zachowanie.

Konsekwencją total ordering in the context of the IEEE-754 standard jest różnica między dodatnim i ujemnym zerem.

Na przykład, jeśli używasz operatora równości zamiast metody porównywania i posiadasz pewien zbiór wartości, a twoja logika kodu podejmuje pewne decyzje w oparciu o kolejność elementów i jakoś zaczynasz otrzymywać nadwyżkę wartości NaN będą one traktowane jako różne wartości zamiast tych samych wartości.

Prawdopodobnie spowoduje to błąd w zachowaniu programu proporcjonalny do ilości/szybkości wartości NaN. A jeśli masz dużo dodatnich i ujemnych zer, to tylko jedna para wpływa na twoją logikę z błędem.

Float uses Format 32-bitowy IEEE-754 i podwójny uses 64-bitowy format IEEE-754.

1

Istnieją dwa powody, aby porównać obiektów zmiennoprzecinkowych:

  • robie matematyki, więc chcę, aby porównać ich wartości liczbowych. Numerycznie, -0 jest równe +0, a NaN nie jest równe cokolwiek, nawet sobie, ponieważ "równy" jest właściwością, którą mają tylko liczby, a NaN nie jest liczbą.
  • Pracuję z obiektami w komputerze, więc muszę rozróżnić różne obiekty i uporządkować je. Jest to konieczne na przykład do sortowania obiektów w drzewie lub innym kontenerze.

Operator == zapewnia porównania matematyczne. Zwraca false w przypadku NaN == NaN i jest prawdziwe w przypadku procedur Procedury zapewniają porównywanie obiektów. Porównując NaN z samym sobą, wskazują one, że jest on taki sam (zwracając zero). Porównując -0.f do +0.f, wskazują one, że są różne (zwracając wartość niezerową).

Powiązane problemy