+0 i -0 pokazują różne zachowanie danych int i float

16

Przeczytałem ten post ujemne i dodatnie zero .

W moim rozumieniu następujący kod powinien dać true i true jako wynik.

Jednak daje falsei truejako wynik.

Porównuję zero ujemne z dodatnim zero.

public class Test {
     public static void main(String[] args) {
            float f = 0;
            float f2 = -f;
            Float F = new Float(f);
            Float F1 = new Float(f2);
            System.out.println(F1.equals(F));

            int i = 0;
            int i2 = -i;
            Integer I = new Integer(i);
            Integer I1 = new Integer(i2);
            System.out.println(I1.equals(I));
      }
  }

Dlaczego mamy różne zachowanie dla zer dla Integeri Float?

Żartowniś
źródło
11
Jeśli sprawdzisz javadocs, docs.oracle.com/javase/8/docs/api/java/lang/… Definicja umożliwia prawidłowe funkcjonowanie tabel skrótów. Ponadto nie ma liczby całkowitej -0.
mat
@matt, jeśli -0 nie jest liczbą całkowitą, wówczas powinno zostać ocenione jako fałsz ...
Joker
3
Kiedy mówisz i2 = -i; i2 przyjmuje dokładną reprezentację bitową i, nie ma sposobu, aby je rozpoznać. ii i2są dokładnie takie same. Następnie, gdy tworzysz nowe Integer, oba zawierają dokładnie tę samą wartość. I1.equals(I)będzie prawdą.
mat
1
Spróbuj int i = Integer.MIN_VALUE, i2 = -i;
Holger,
1
Nawiasem mówiąc, nie ma powodu, aby używać newtutaj typów opakowań. Wystarczy użyć np.Integer i = 0, i2 = -i; System.out.println(i.equals(i2)); Float f1 = 0f, f2 = -f1; System.out.println(f1.equals(f2));
Holger

Odpowiedzi:

19

Ints i zmiennoprzecinkowe są w Javie zupełnie innymi zwierzętami. Ints są kodowane jako uzupełnienie dwóch , które ma pojedynczą wartość 0. Floats używa IEEE 754 ( 32-bitowy wariant dla float i 64-bit dla double). IEEE 754 jest nieco skomplikowany, ale dla celów tej odpowiedzi wystarczy wiedzieć, że ma on trzy sekcje, z których pierwsza jest bitem znaku. Oznacza to, że dla każdego elementu typu float istnieje wariant dodatni i ujemny¹. Obejmuje to 0, więc liczby zmiennoprzecinkowe faktycznie mają dwie wartości „zero”, +0 i -0.

Nawiasem mówiąc, uzupełnienie tych dwóch, które wykorzystuje ints, nie jest jedynym sposobem kodowania liczb całkowitych w informatyce. Istnieją inne metody, takie jak ich uzupełnianie , ale mają dziwactwa - na przykład mając zarówno +0, jak i -0 jako odrębne wartości. ;-)

Podczas porównywania operacji podstawowych (i dublujących) Java traktuje +0 i -0 jako równe. Ale kiedy je zapakujesz, Java traktuje je osobno, jak opisano w Float#equals. Dzięki temu metoda equals jest spójna z ich hashCodeimplementacją (a także compareTo), która po prostu wykorzystuje bity liczby zmiennoprzecinkowej (w tym tę podpisaną wartość) i umieszcza je w stanie niezmienionym.

Mogli wybrać inną opcję dla equals / hashCode / CompareTo, ale nie zrobili tego. Nie jestem pewien, jakie były rozważania projektowe. Ale przynajmniej w jednym aspekcie Float#equalszawsze miał odbiegać od prymitywnych liczb zmiennoprzecinkowych ==: w elementach pierwotnych NaN != NaN, ale dla wszystkich obiektów, o.equals(o)musi być również prawdziwa . Oznacza to, że jeśli tak Float f = Float.NaN, to f.equals(f)mimo to f.floatValue() != f.floatValue().


¹ Wartości NaN (nie-liczba) mają bit znaku, ale nie mają innego znaczenia niż dla zamawiania, a Java ignoruje je (nawet dla zamawiania).

yshavit
źródło
10

Jest to jeden z wyjątków typu Float

są dwa wyjątki:

Jeśli f1 oznacza + 0,0f, podczas gdy f2 oznacza -0,0f , , lub odwrotnie, test równości ma wartość false

Dlaczego opisano również:

Ta definicja umożliwia prawidłowe działanie tabel skrótów.

-0 i 0 będą reprezentowane inaczej przy użyciu bitu 31 Float:

Bit 31 (bit wybrany przez maskę 0x80000000) reprezentuje znak liczby zmiennoprzecinkowej.

Tak nie jest Integer

użytkownik7294900
źródło
pytanie dlaczego? Czy to jest twarda i szybka zasada, którą musimy wcisnąć :(
Joker
@Joker Dodano cytat pozwala na prawidłowe działanie tabel skrótów
użytkownik7294900
4
Kluczowym elementem, o którym nie wspomina ta odpowiedź (i javadoc), jest to, że różnica polega na tym, że w liczbach zmiennoprzecinkowych +0 i -0 są różnymi wartościami - równoważnymi, ale różnymi. Zasadniczo zmiennoprzecinkowe mają do nich trzy części, a pierwsza część to pojedynczy bit, który mówi, czy zmiennoprzecinkowe jest dodatnie czy ujemne. To nie przypadek za wskazówki (jak przedstawiono w Javie), które mają tylko jedną wartość 0.
yshavit,
@yshavit Dzięki, czy mógłbyś podzielić się tym samym co odpowiedź
Joker
3
@ Bit
user7294900,
5

W przypadku liczb całkowitych nie ma rozróżnienia między -0 i 0 dla liczb całkowitych, ponieważ używa reprezentacji komplementu Twos . Więc twój przykład liczb całkowitych ii i1są dokładnie takie same.

W przypadku liczb zmiennoprzecinkowych istnieje reprezentacja -0, a jej wartość jest równa 0, ale reprezentacja bitowa jest inna. Dlatego nowy Float (0f) i nowy Float (-0f) miałyby różne reprezentacje.

Możesz zobaczyć różnicę w reprezentacjach bitowych.

System.out.println(Float.floatToIntBits(-0f) + ", " + Float.floatToIntBits(0f));

-2147483648, 0

A jeśli odejdziesz od fdeklarowania, -0fwtedy będzie traktowane jako liczba całkowita i nie zobaczysz żadnej różnicy w wyniku.

matowy
źródło
A jednak wydaje się, że prymitywny float działa z tym dobrze. Że jest 0.0f == -0.0f. Więc inne zachowanie jest tylko w java.lang.Float.
ivant
3
@ivant według IEEE754: „Normalne operacje porównywania traktują jednak NaN jako nieuporządkowane i porównują wartości -0 i +0 jako równe” en.m.wikipedia.org/wiki/IEEE_754
Andy Turner
@AndyTurner, tak, rozumiem to. floatWskazuję tylko, że w Javie istnieje różnica w zachowaniu między typem pierwotnym , który pod tym względem jest zgodny z IEEE754, a java.lang.Floatktóry nie. Tak więc sama różnica w reprezentacji bitowej nie wystarczy, aby to wyjaśnić.
ivant