Подтвердить что ты не робот

Двойное умножение на 100, а затем наложение на длинные дает неправильное значение

У меня есть следующий код:

Double i=17.31;
long j=(long) (i*100);
System.out.println(j);

O/P: 1730 //Expected:1731

Double i=17.33;
long j=(long) (i*100);
System.out.println(j);

O/P: 1732 //Expected:1733

Double i=17.32;
long j=(long) (i*100);
System.out.println(j);

O/P: 1732 //Expected:1732{As expected}

Double i=15.33;
long j=(long) (i*100);
System.out.println(j);

O/P: 1533 //Expected:1533{as Expected}

Я пытался Google, но не смог найти причину. Мне жаль, если вопрос тривиален.

4b9b3361

Ответ 1

Кажется, что ни один из ответов не имеет отношения к , почему 17.32 действует иначе.

1. Почему это произошло

Разница в поведении между 17.32 и 17.33 & 17.31 обусловлена ​​правилами IEEE-754 Rounding.

Используется правило округления: from, Виртуальный Java ™ Спецификация станка §2.8.1

Операции округления виртуальной машины Java всегда используют IEEE 754 до ближайшего режима. Неточные результаты округлены до ближайшего представляемое значение, с привязкой к значению с нулем младший бит. Это режим по умолчанию IEEE 754. Виртуальный виртуальный Java машина не дает никаких средств для изменения округления с плавающей запятой Режим


2. Ваш случай:

Двойной: (1 знаковый бит + 11 экспоненциальных битов + 52 дробных разряда = 64 бит). Внутреннее представление после округления ниже:

             1 [63]      11 [62-52]           52 [51-00]
              Sign        Exponent             Fraction

17.31 -->    0 (+)       10000000011 (+4)     1.0001010011110101110000101000111101011100001010001111
17.32 -->    0 (+)       10000000011 (+4)     1.0001010100011110101110000101000111101011100001010010 //rounded up
17.33 -->    0 (+)       10000000011 (+4)     1.0001010101000111101011100001010001111010111000010100

3. Внутреннее представление (Доказательство):

17.31: (сравнение Мантиссы)

Actual:   1.00010100111101011100001010001111010111000010100011110...
Internal: 1.0001010011110101110000101000111101011100001010001111

17.32: (сравнение Мантиссы)

Actual:   1.00010101000111101011100001010001111010111000010100011... 
Internal: 1.0001010100011110101110000101000111101011100001010010    //round-up!

17.33: (сравнение Мантиссы)

Actual:   1.00010101010001111010111000010100011110101110000101000...
Internal: 1.0001010101000111101011100001010001111010111000010100

4. Преобразование обратно в десятичное:

17.31 ->  17.309999999999998721023075631819665431976318359375...
17.32 ->  17.32000000000000028421709430404007434844970703125... //(was rounded up)
17.33 ->  17.3299999999999982946974341757595539093017578125...

( Инструмент анализа IEEE-754

5. Вставить в длинный

РЕДАКТИРОВАТЬ: На шаге умножения есть фактор, который, как сказал @Jeppe Стиг Нильсен, сказал: Результат умножения FP (Ссылка) делает свое собственное округление - ближайший. Это изменяет результаты, как ожидалось, а какие нет, но причина по-прежнему такая же, как указано выше.

Наконец, из-за литого (long) происходит усечение и оставляет вас с результатами, которые вы видите. (1730, 1732, 1732)

Сужение примитивного преобразования: Спецификация языка Java ™ §5.1.3

Если число с плавающей запятой не является бесконечным, точка с плавающей запятой значение округляется до целочисленного значения V, округляя до нуля, используя Режим IEEE 754 с округлением до нуля

Ответ 2

Значение double представлено не как 17.31, а как 17.309999999999999. Поэтому, когда вы умножаете его на 100, вы получаете 1730.99999999999999999. После преобразования в Long ваше значение double обрезается до нуля. Итак, вы получите 1730.

Ответ 3

Как было объяснено, это связано с очень малой точностью с плавающей точкой.

Это можно решить с помощью команды Math.round(), следующим образом:

long j=Math.round(i*100);

Это позволит программе компенсировать очень малые ошибки, которые наследуются с помощью вычислений с плавающей запятой, не используя операцию с полом, по умолчанию (long).

Ответ 4

Это связано с внутренним представлением. Если вы посмотрите на я * 100 в первом случае, вы увидите, что это 1730.9999999999998. Приведение будет только удалять часть после точки (усечен).

Ответ 5

Ответы Cthulhu и svz верны. Если вы хотите умножить удвоение на 100 и избежать ошибок округления с плавающей точкой, вы можете использовать Math.round() для округления результата до ближайшего long после каждого умножения:

Double i=17.31;
long j=Math.round(i*100);
System.out.println(j);

Это будет по-прежнему иметь ошибку с плавающей запятой при работе с чрезвычайно большими (или отрицательными) удвоениями. Чем больше абсолютное значение двойника, тем больше разница между ним и следующим двойником, которое может представлять Java. После некоторого момента последовательные двойники больше, чем целое число, и обычное округление не сможет сгладить разницу. Для приведенных вами примеров это должно работать.

Ответ 6

Когда вы делаете такое длинное преобразование, это слово. Ваш 17.31 действительно может быть 17.30999999999 и почему он привел к 1730 году вместо 1731.

используйте я = я * 100, тогда i.longValue() решит проблему.