+0 und -0 zeigen unterschiedliches Verhalten für int- und float-Daten

16

Ich habe diesen Beitrag negativ und positiv Null gelesen .

Nach meinem Verständnis sollte folgender Code geben true und true als Ausgabe.

Es gibt jedoch falseund trueals Ausgabe.

Ich vergleiche negative Null mit einer positiven Null.

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

Warum haben wir ein unterschiedliches Verhalten für Nullen für Integerund Float?

Joker
quelle
11
Wenn Sie die Javadocs überprüfen, docs.oracle.com/javase/8/docs/api/java/lang/… Die Definition ermöglicht, dass Hash-Tabellen ordnungsgemäß funktionieren. Es gibt auch keine -0-Ganzzahl.
Matt
@matt wenn -0 nicht ganzzahlig ist, sollte es als falsch ausgewertet werden ...
Joker
3
Wenn Sie i2 = -i sagen; i2 nimmt die genaue Bitdarstellung von i, es gibt keine Möglichkeit, sie zu erkennen. iund i2sind genau das gleiche. Wenn Sie dann neue Integers erstellen , schließen beide genau den gleichen Wert ein. I1.equals(I)wird wahr sein.
Matt
1
Versuchen Sie int i = Integer.MIN_VALUE, i2 = -i;...
Holger
1
Es gibt übrigens keinen Grund, hier newfür die Wrapper-Typen zu verwenden. Verwenden Sie einfach zBInteger i = 0, i2 = -i; System.out.println(i.equals(i2)); Float f1 = 0f, f2 = -f1; System.out.println(f1.equals(f2));
Holger

Antworten:

19

Ints und Floats sind in Java ziemlich unterschiedliche Bestien. Ints werden als Zweierkomplement codiert , das einen einzelnen 0-Wert hat. Floats verwenden IEEE 754 (die 32-Bit-Variante für Floats und 64-Bit für Doubles). IEEE 754 ist etwas komplex, aber für diese Antwort müssen Sie nur wissen, dass es drei Abschnitte hat, von denen der erste ein Vorzeichenbit ist. Das heißt, für jeden Float gibt es eine positive und eine negative Variante¹. Das schließt 0 ein, also haben Floats tatsächlich zwei "Null" -Werte, +0 und -0.

Abgesehen davon ist das von Ints verwendete Komplement der beiden nicht die einzige Möglichkeit, Ganzzahlen in der Informatik zu codieren. Es gibt andere Methoden, wie das Komplement von Einsen , aber sie haben Macken - wie sowohl +0 als auch -0 als unterschiedliche Werte. ;-);

Wenn Sie Float-Primitive (und Doubles) vergleichen, behandelt Java +0 und -0 als gleich. Wenn Sie sie jedoch einpacken, werden sie von Java separat behandelt, wie in beschrieben Float#equals. Auf diese Weise kann die Methode equals mit ihrer hashCodeImplementierung (sowie compareTo) konsistent sein , bei der nur die Bits des Gleitkommas (einschließlich des vorzeichenbehafteten Werts) verwendet und unverändert in ein int verschoben werden.

Sie hätten eine andere Option für equals / hashCode / compareTo auswählen können, aber sie haben es nicht getan. Ich bin mir nicht sicher, welche Designüberlegungen es gab. Aber in mindestens einer Hinsicht würde Float#equalsimmer von den Float-Primitiven abweichen ==: In Primitiven NaN != NaN, aber für alle Objekte, o.equals(o)muss auch wahr sein . Das heißt, wenn Sie hatten Float f = Float.NaN, dann f.equals(f)obwohl f.floatValue() != f.floatValue().


¹ NaN-Werte (keine Zahl) haben ein Vorzeichenbit, aber keine andere Bedeutung als für die Bestellung, und Java ignoriert sie (auch für die Bestellung).

yshavit
quelle
10

Dies ist eine Ausnahme von Float gleich

Es gibt zwei Ausnahmen:

Wenn f1 + 0.0f darstellt, während f2 -0.0f darstellt oder umgekehrt, hat der gleiche Test den Wert false

Das Warum wird auch beschrieben:

Diese Definition ermöglicht den ordnungsgemäßen Betrieb von Hash-Tabellen.

-0 und 0 werden mit Floats Bit 31 unterschiedlich dargestellt:

Bit 31 (das von der Maske 0x80000000 ausgewählte Bit) repräsentiert das Vorzeichen der Gleitkommazahl.

Dies ist in nicht der Fall Integer

user7294900
quelle
Frage ist warum? Ist das eine harte und schnelle Regel, die wir stopfen müssen :(
Joker
@Joker Das Zitat wurde hinzugefügt, damit Hash-Tabellen ordnungsgemäß funktionieren
user7294900
4
Ein Schlüsselstück, das in dieser Antwort (und im Javadoc) nicht erwähnt wird, ist, dass der Unterschied darin besteht, dass in Floats +0 und -0 unterschiedliche Werte sind - äquivalent, aber unterschiedlich. Grundsätzlich bestehen Floats aus drei Teilen, und der erste Teil ist ein einzelnes Bit, das angibt, ob der Float positiv oder negativ ist. Dies ist nicht der Fall bei Ints (wie in Java dargestellt), die nur einen einzigen 0-Wert haben.
Yshavit
@yshavit Danke, könnten Sie bitte das gleiche als Antwort teilen
Joker
3
@Joker Bit 31 (das von der Maske 0x80000000 ausgewählte Bit) repräsentiert das Vorzeichen der Gleitkommazahl.
user7294900
5

Für die ganzen Zahlen gibt es keine Unterscheidung zwischen -0 und 0 für ganze Zahlen, da die Zweierkomplementdarstellung verwendet wird . Also dein ganzzahliges Beispiel iund i1genau das gleiche.

Für die Floats gibt es eine -0-Darstellung, und ihr Wert entspricht 0, aber die Bitdarstellung ist unterschiedlich. Daher hätten neuer Float (0f) und neuer Float (-0f) unterschiedliche Darstellungen.

Sie können den Unterschied in den Bitdarstellungen sehen.

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

-2147483648, 0

Und wenn Sie das weglassen, um das fzu deklarieren -0f, wird es als Ganzzahl behandelt, und Sie werden keinen Unterschied in der Ausgabe sehen.

matt
quelle
Und doch scheint der primitive Float damit gut zu funktionieren. Das ist 0.0f == -0.0f. Das unterschiedliche Verhalten ist also nur in java.lang.Float.
ivant
3
@ivant gemäß IEEE754, "Die normalen Vergleichsoperationen behandeln NaNs jedoch als ungeordnet und vergleichen −0 und +0 als gleich" en.m.wikipedia.org/wiki/IEEE_754
Andy Turner
@AndyTurner, ja das verstehe ich. Ich möchte nur darauf hinweisen, dass es in Java einen Unterschied im Verhalten zwischen dem primitiven Typ gibt float, der in dieser Hinsicht IEEE754 entspricht, und dem java.lang.Float, der dies nicht tut. Nur der Unterschied in der Bitdarstellung reicht also nicht aus, um dies zu erklären.
ivant