Проблема 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", так как значение, записанное в памяти с помощью компьютер "ниже" галстука.

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