+0 и -0 показывают различное поведение для данных типа int и float

16

Я прочитал этот пост отрицательный и положительный ноль .

В моем понимании следующий код должен давать true и true в качестве вывода.

Тем не менее, это дает falseи trueв качестве вывода.

Я сравниваю отрицательный ноль с положительным нулем.

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

Почему у нас разное поведение для 0 Integerи Float?

джокер
источник
11
Если вы проверяете javadocs, docs.oracle.com/javase/8/docs/api/java/lang/… Определение позволяет хэш-таблицам функционировать должным образом. Также нет целого числа -0.
матовый
@matt, если -0 не является целым числом, тогда оно должно быть оценено как ложное ...
Джокер
3
Когда вы говорите, i2 = -i; i2 принимает точное битовое представление i, их невозможно различить. iи i2точно так же. Затем, когда вы создаете новые Integers, они оба переносят одно и то же значение. I1.equals(I)будет правдой.
матовый
1
Попробуйте int i = Integer.MIN_VALUE, i2 = -i;...
Хольгер
1
Между прочим, здесь нет причин использовать newтипы обёрток. Просто используйте, например,Integer i = 0, i2 = -i; System.out.println(i.equals(i2)); Float f1 = 0f, f2 = -f1; System.out.println(f1.equals(f2));
Хольгер

Ответы:

19

Интты и поплавки - это довольно разные звери в Java. Интты кодируются как два дополнения , которое имеет одно значение 0. Float использует IEEE 754 ( 32-битный вариант для float и 64-битный для double). IEEE 754 несколько сложен, но для ответа на этот вопрос вам просто нужно знать, что он состоит из трех разделов, первый из которых является знаковым битом. Это означает, что для любого поплавка есть положительный и отрицательный варианты. Это включает в себя 0, так что плавающие на самом деле имеют два «нулевых» значения, +0 и -0.

Кроме того, дополнение к двум, используемое целыми числами, не единственный способ кодировать целые числа в компьютерной науке. Существуют и другие методы, например , дополнение к ним , но у них есть свои причуды, например, наличие как +0, так и -0 в качестве различных значений. ;-)

Когда вы сравниваете примитивы с плавающей точкой (и удваивает), Java обрабатывает +0 и -0 как равные. Но когда вы их упаковываете, Java обрабатывает их отдельно, как описано в Float#equals. Это позволяет методу equals быть совместимым с их hashCodeреализацией (а также compareTo), который просто использует биты с плавающей точкой (включая значение со знаком) и помещает их как есть в int.

Они могли бы выбрать другой вариант для equals / hashCode / compareTo, но они этого не сделали. Я не уверен, какие были соображения дизайна. Но, по крайней мере, в одном отношении Float#equalsвсегда собирались расходиться с примитивами поплавка ==: в примитивах NaN != NaN, но для всех объектов, o.equals(o)также должно быть верно . Это означает, что если у вас есть Float f = Float.NaN, то f.equals(f)даже если f.floatValue() != f.floatValue().


¹ Значения NaN (not-a-number) имеют знаковый бит, но он не имеет никакого значения, кроме как для упорядочения, а Java игнорирует его (даже для упорядочения).

yshavit
источник
10

Это одно из исключений с плавающей точкой

Есть два исключения:

Если f1 представляет + 0.0f, тогда как f2 представляет -0.0f или наоборот, равный тест имеет значение false

Почему также описано:

Это определение позволяет хеш-таблицам работать правильно.

-0 и 0 будут представлены по-разному с помощью бита Float 31:

Бит 31 (бит, выбранный маской 0x80000000) представляет знак числа с плавающей запятой.

Это не так в Integer

user7294900
источник
вопрос почему? Это жесткое и быстрое правило, которое мы должны втиснуть :(
Джокер
@Joker Добавил цитату, позволяющую правильно работать хеш-таблицам
user7294900
4
Ключевая часть, которую этот ответ (и javadoc) не упоминают, заключается в том, что разница в том, что в числах с плавающей запятой +0 и -0 - разные значения - эквивалентные, но разные. По существу, поплавки состоят из трех частей, и первая часть представляет собой один бит, который говорит, является ли число с плавающей точкой положительным или отрицательным. Это не относится к целым числам (как представлено в Java), которые имеют только одно значение 0.
ишавит
@yshavit Спасибо, не могли бы вы поделиться тем же, что и ответ
Джокер
3
@Joker Бит 31 (бит, выбранный маской 0x80000000) представляет знак числа с плавающей запятой.
user7294900
5

Для целых чисел нет различия между -0 и 0 для целых чисел, потому что он использует представление комплимента Twos . Так что ваш пример целочисленный iи i1точно такой же.

Для чисел с плавающей запятой существует представление -0, и его значение эквивалентно 0, но представление битов отличается. Следовательно, новый Float (0f) и новый Float (-0f) будут иметь разные представления.

Вы можете увидеть разницу в битовых представлениях.

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

-2147483648, 0

И если вы не укажете fобъявление, -0fто оно будет рассматриваться как целое число, и вы не увидите никакой разницы в выводе.

матовый
источник
И все же примитивный поплавок, кажется, прекрасно с этим работает. То есть 0.0f == -0.0f. Так что другое поведение только в java.lang.Float.
Иван
3
@ivant в соответствии с IEEE754: «Обычные операции сравнения, однако, обрабатывают NaN как неупорядоченные и сравнивают −0 и +0 как равные» en.m.wikipedia.org/wiki/IEEE_754
Энди Тернер
@ AndyTurner, да, я понимаю это. Я просто указываю на то, что в Java есть различие в поведении между типом примитива float, который соответствует IEEE754 в этом отношении, и java.lang.Float, который не делает. Так что просто разницы в представлении битов недостаточно, чтобы объяснить это.
IVant