14

I have read this post negative and positive zero.

To my understanding following code should give true and true as a output.

However, it is giving false and true as a output.

I'm comparing negative zero with a positive 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));
      }
  }

Why do we have different behavior for 0's for Integer and Float ?

T-Bag
  • 10,916
  • 3
  • 54
  • 118
  • 11
    If you check the javadocs, https://docs.oracle.com/javase/8/docs/api/java/lang/Float.html#equals-java.lang.Object- The definition allows for hash tables to function properly. Also, there is no -0 integer. – matt Dec 11 '19 at 06:35
  • @matt if -0 is not integer then it should be evaluated as false... – T-Bag Dec 11 '19 at 06:40
  • 3
    When you say i2 = -i; i2 takes the exact bit representation of i, there is no way to discern them. `i` and `i2` are exactly the same. Then when you create new `Integer`s they both wrap the exact same value. `I1.equals(I)` will be true. – matt Dec 11 '19 at 06:43
  • 1
    Try `int i = Integer.MIN_VALUE, i2 = -i;`… – Holger Dec 11 '19 at 08:25
  • 1
    There is, by the way, no reason to use `new` for the wrapper types here. Just use, e.g. `Integer i = 0, i2 = -i; System.out.println(i.equals(i2)); Float f1 = 0f, f2 = -f1; System.out.println(f1.equals(f2));` – Holger Dec 11 '19 at 09:13

3 Answers3

17

Ints and floats are pretty different beasts in Java. Ints are encoded as two's complement, which has a single 0 value. Floats use IEEE 754 (the 32-bit variant for floats, and 64-bit for doubles). IEEE 754 is somewhat complex, but for purpose of this answer, you just need to know that it has three sections, the first of which is a sign bit. That means for any float, there's a positive and negative variant¹. That includes 0, so floats actually have two "zero" values, +0 and -0.

As an aside, the two's complement that ints use is not the only way to encode integers in computer science. There are other methods, like ones' complement, but they have quirks — like having both a +0 and -0 as distinct values. ;-)

When you compare float primitives (and doubles), Java treats +0 and -0 as equal. But when you box them, Java treats them separately, as described in Float#equals. This lets the equals method be consistent with their hashCode implementation (as well as compareTo), which just uses the bits of the float (including that signed value) and shoves them as-is into an int.

They could have picked some other option for equals/hashCode/compareTo, but they didn't. I'm not sure what the design considerations there were. But in at least one regard, Float#equals was always going to diverge from the float primitive's ==: In primitives, NaN != NaN, but for all objects, o.equals(o) must also be true. That means that if you had Float f = Float.NaN, then f.equals(f) even though f.floatValue() != f.floatValue().


¹ NaN (not-a-number) values have a sign bit, but it doesn't have any meaning other than for ordering, and Java ignores it (even for ordering).

yshavit
  • 42,327
  • 7
  • 87
  • 124
9

This is one of Float equals exception

there are two exceptions:

If f1 represents +0.0f while f2 represents -0.0f, or vice versa, the equal test has the value false

The why is described also:

This definition allows hash tables to operate properly.

-0 and 0 will represented differently using Float's bit 31:

Bit 31 (the bit that is selected by the mask 0x80000000) represents the sign of the floating-point number.

This isn't the case in Integer

Community
  • 1
  • 1
Ori Marko
  • 56,308
  • 23
  • 131
  • 233
  • question is why ? Is this hard and fast rule that we have to cram :( – T-Bag Dec 11 '19 at 06:34
  • @Joker Added the quote *allows hash tables to operate properly* – Ori Marko Dec 11 '19 at 06:36
  • 4
    A key piece that this answer (and the javadoc) don't mention is that the difference is that in floats, +0 and -0 are different values -- equivalent, but different. Basically, floats have three parts to them, and the first part is a single bit that says whether the float is positive or negative. That's _not_ the case for ints (as represented in Java), which only have a single 0 value. – yshavit Dec 11 '19 at 06:40
  • @yshavit Thanks, could you please share the same as an answer – T-Bag Dec 11 '19 at 06:42
  • 3
    @Joker *Bit 31 (the bit that is selected by the mask 0x80000000) represents the sign of the floating-point number.* – Ori Marko Dec 11 '19 at 06:43
4

For the integers, there is no distinction between -0 and 0 for integers because it uses Twos compliment representation. So your integer example i and i1 are exactly the same.

For the floats, there is a -0 representation, and it's value is equivalent to 0, but the bit representation is different. Hence new Float(0f) and new Float(-0f) would have different representations.

You can see the difference in the bit representations.

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

-2147483648, 0

And if you leave off the f to declare the -0f then it will be treated as an integer, and you won't see any difference in the output.

matt
  • 10,892
  • 3
  • 22
  • 34
  • And yet the primitive float seems to work fine with that. That is `0.0f == -0.0f`. So the different behavior is only in `java.lang.Float`. – ivant Dec 11 '19 at 06:55
  • 3
    @ivant according to IEEE754, "The normal comparison operations, however, treat NaNs as unordered and compare −0 and +0 as equal" https://en.m.wikipedia.org/wiki/IEEE_754 – Andy Turner Dec 11 '19 at 07:24
  • @AndyTurner, yes I understand that. I'm just pointing out that in Java there is a difference in the behavior between the primitive type `float`, which conforms to IEEE754 in this regard and `java.lang.Float`, which does not. So just the difference in the bit representation is not enough to explain this. – ivant Dec 11 '19 at 09:03