Проблема RoundingMode.HALF_DOWN в Java8

Я использую jdk 1.8.0_45, и наши тесты обнаружили ошибку в роудинге. RoundingMode.HALF_DOWN работает так же, как RoundingMode.HALF_UP, когда последний десятичный знак, определяющий округление, равен 5.

Я обнаружил связанные с этим проблемы с RoundingMode.HALF_UP, но они исправлены в обновлении 40. Я также добавил баг в оракула, но по моему опыту они действительно не отвечают.

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

2 ответа

Решение

Кажется, что это намеренное изменение. Поведение 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

Изменение поведения задокументировано в заметках о выпуске Java 8

При использовании NumberFormat а также DecimalFormat классы, поведение округления предыдущих версий JDK было неправильным в некоторых угловых случаях. [...]

В качестве примера при использовании по умолчанию рекомендуется NumberFormatFormat Форма API: NumberFormat nf = java.text.NumberFormat.getInstance() с последующим nf.format(0.8055d)значение 0.8055d записывается на компьютере как 0.80549999999999999378275106209912337362766265869140625, поскольку это значение не может быть точно представлено в двоичном формате. Здесь правилом округления по умолчанию является "half-even", а результатом вызова format() в JDK 7 является неправильный вывод "0.806", в то время как правильный результат - "0.805", поскольку значение, записанное в памяти компьютер "под" галстуком.

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

Другие вопросы по тегам