+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));
Holger

Відповіді:


19

Інти і поплавки - це зовсім різні звірі на Яві. Інти кодуються як доповнення двох , яке має єдине значення 0. Поплавці використовують IEEE 754 ( 32-бітний варіант для поплавців, а 64-розрядний для парних). IEEE 754 є дещо складним, але для цілей цієї відповіді вам просто потрібно знати, що він має три розділи, перший з яких є бітовим знаком. Це означає, що для будь-якого поплавця є і позитивний, і негативний варіант¹. Це включає 0, тому поплавці насправді мають два значення "нуль", +0 і -0.

На додаток, доповнення обох, яке використовує інт, - не єдиний спосіб кодування цілих чисел у інформатиці. Існують і інші методи, наприклад доповнення , але вони мають химерність - наприклад, мають як окремі значення і +0, і -0. ;-)

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

Вони могли вибрати інший варіант для рівняння / hashCode / CompareTo, але цього не зробили. Я не впевнений, які там були дизайнерські міркування. Але, принаймні в одному відношенні, Float#equalsзавжди збирався відходити від поплавкового примітиву ==: У примітивах NaN != NaN, але для всіх об'єктів, o.equals(o)теж має бути правдою . Це означає, що якщо ти мав Float f = Float.NaN, то f.equals(f)хоч f.floatValue() != f.floatValue().


Values ​​Значення NaN (не-число) мають біт знаків, але він не має іншого значення, крім того, щоб замовити, і Java ігнорує його (навіть для замовлення).


10

Це один з винятків Float рівний

є два винятки:

Якщо f1 являє собою + 0,0f, тоді як f2 являє собою -0,0f , або навпаки, тест рівності має значення false

Причини також описані:

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

-0 і 0 будуть представлені по-різному, використовуючи біт 31 Float:

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

Це не так у Росії Integer


питання чому? Це важке і швидке правило, яке нам доводиться виконувати :(
Джокер

@Joker Додана цитата дозволяє хеш-таблицям працювати належним чином
user7294900

4
Основна деталь, яку ця відповідь (і javadoc) не згадує, полягає в тому, що різниця полягає в тому, що в плавцях +0 і -0 різні значення - еквівалентні, але різні. В основному, поплавці мають до них три частини, і перша частина - це єдиний біт, який говорить про те, позитивний чи негативний поплавок. Це не стосується ints (як представлено на Java), які мають лише одне значення 0.
ішавіт

@yshavit Спасибі, будь ласка, поділіться тим самим, що й відповідь
Джокер

3
@Joker Bit 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.
ivant

3
@ivant згідно IEEE754, "Однак, звичайні операції порівняння трактують NaNs як не упорядковані, а порівняння −0 та +0 є рівними" en.m.wikipedia.org/wiki/IEEE_754
Енді Тернер

@AndyTurner, так, я це розумію. Я просто вказую, що в Java є різниця в поведінці між примітивним типом float, який відповідає IEEE754 в цьому плані, а java.lang.Floatякий - ні. Тож просто різниці в представленні бітів недостатньо для пояснення цього.
ivant
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.