Проблема RoundingMode.HALF_DOWN в Java8
Я использую jdk 1.8.0_45, и наши тесты обнаружили ошибку в руинге.
RoundingMode.HALF_DOWN работает так же, как RoundingMode.HALF_UP, когда последний десятичный знак, определяющий округление, равен 5.
Я нашел связанные проблемы с RoundingMode.HALF_UP, но они исправлены в обновлении 40. Я также поместил ошибку в oracle, но по моему опыту они действительно не отвечают.
package testjava8;
import java.math.RoundingMode;
import java.text.DecimalFormat;
public class Formatori {
public static void main(String[] args) {
DecimalFormat format = new DecimalFormat("#,##0.0000");
format.setRoundingMode(RoundingMode.HALF_DOWN);
Double toFormat = 10.55555;
System.out.println("Round down");
System.out.println(format.format(toFormat));
format.setRoundingMode(RoundingMode.HALF_UP);
toFormat = 10.55555;
System.out.println("Round up");
System.out.println(format.format(toFormat));
}
}
Фактический результат:
Округлить
10,5556
Округлять
10,5556
Ожидаемый результат (получить с помощью jdk 1.7):
Округлить
10,5555
Округлять
10,5556
Ответы
Ответ 1
Кажется, что это предполагало изменение. Неправильное поведение JDK 1.7.
Проблема заключается в том, что вы просто не можете представить число 10.55555
с помощью типа double
. Он хранит данные в двоичном формате IEEE, поэтому, когда вы назначаете десятичный номер 10.55555
переменной double
, вы фактически получаете самое близкое значение, которое может быть представлено в формате IEEE: 10.555550000000000210320649784989655017852783203125
. Это число больше, чем 10.55555
, поэтому оно правильно округлено до 10.5556
в режиме HALF_DOWN
.
Вы можете проверить некоторые цифры, которые могут быть точно представлены в двоичном формате. Например, 10.15625
(который 10 + 5/32
, таким образом 1010.00101
в двоичном формате). Это число округляется до 10.1562
в режиме HALF_DOWN
и 10.1563
в режиме HALF_UP
.
Если вы хотите восстановить прежнее поведение, вы можете сначала преобразовать свой номер в BigDecimal
с помощью конструктора BigDecimal.valueOf
, который "переводит double
в BigDecimal
, используя каноническое строковое представление double
":
BigDecimal toFormat = BigDecimal.valueOf(10.55555);
System.out.println("Round down");
System.out.println(format.format(toFormat)); // 10.5555
format.setRoundingMode(RoundingMode.HALF_UP);
toFormat = BigDecimal.valueOf(10.55555);
System.out.println("Round up");
System.out.println(format.format(toFormat)); // 10.5556
Ответ 2
Смена поведения документируется в заметках о выпуске Java 8
При использовании классов NumberFormat
и DecimalFormat
поведение округления предыдущих версий JDK было неправильным в некоторых случаях с углом. [...]
В качестве примера при использовании рекомендованной по умолчанию формы NumberFormatFormat
API: NumberFormat nf = java.text.NumberFormat.getInstance()
, за которой следует nf.format(0.8055d)
, значение 0.8055d записывается на компьютер как 0.80549999999999999378275106209912337362766265869140625, поскольку это значение не может быть представлено точно в двоичном формате. Здесь правило округления по умолчанию - "полу-четное", а результат вызова формата() в JDK 7 - неправильный вывод "0.806", а правильный результат - "0,805", так как значение, записанное в памяти с помощью компьютер "ниже" галстука.
Это новое поведение также реализовано для всех позиций округления, которые могут быть определены любым шаблоном, выбранным программистом (нестандартные шаблоны).