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

Является ли непоследовательность в округлении между Java 7 и Java 8 ошибкой?

Я вижу несоответствия в округлении в классе DecimalFormat между java 7 и java 8. Вот мой тестовый пример:

DecimalFormatTest.java

import java.text.DecimalFormat;

public class DecimalFormatTest {
    public static void main(String[] args) {
        DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance();
        format.setDecimalSeparatorAlwaysShown(true);
        format.setMinimumFractionDigits(1);
        format.setMaximumFractionDigits(1);
        System.out.println(format.format(83.65));
    }
}

В среде выполнения Java (TM) SE (сборка 1.7.0_51-b13) вывод:

83.6

В среде выполнения Java (TM) SE Runtime (сборка 1.8.0-b132) вывод:

83.7

Является ли это ошибкой регрессии? Или были изменены правила округления с выпуском Java 8?

4b9b3361

Ответ 1

Похоже, что это была давняя ошибка в JDK 7, которая была окончательно исправлена. См. Например:

Существует проект плана по предоставлению JDK 8 следующего консультанта, который объясняет проблему:

----------------------------------------------- ---------------------- Область: Основные библиотеки /java.text

Синопсис: исправлено неправильное поведение JDK7 округления. округление по методу NumberFormat/DecimalFormat format() имеет изменено, когда значение очень близко к галстуку, сидящему точно в округленное положение, указанное в шаблоне форматирования.

Характер несовместимости: поведенческий

Описание. При использовании классов NumberFormat/DecimalFormat округление поведения предыдущих версий JDK было неправильным в некотором углу случаев. Это неправильное поведение произошло при вызове метода format() с значение, которое очень близко к галстуку, при заданном положении округления по образцу используемого экземпляра NumberFormat/DecimalFormat точно сидя на позиции галстука. В этом случае неправильная двойная округление или ошибочное поведение без округления.

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

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

RFE       7131459


Ответ 2

Как упоминалось в других ответах на этот вопрос, JDK 8 сделал преднамеренные изменения округления DecimalFormat JDK-7131459: DecimalFormat производит неправильный формат() результаты, близкие к галстуку.

Однако эти изменения ввели реальную ошибку, указанную как JDK-8039915: Неверный NumberFormat.format() HALF_UP округление, когда последний точно в точках округления больше 5. Например:

99.9989 -> 100.00
99.9990 ->  99.99

Проще говоря, это демонстрирует случай, когда большее число округляется, а меньшее число округляется: (x <= y) != (round(x) <= round(y)). Похоже, что это влияет только на режим округления HALF_UP, который является видом округления, обученным в классах арифметических классов: 0,5 раунда от нуля, всегда.

Эта проблема существует в версиях Oracle и OpenJDK Java 8 и обновлениях 8u5, 8u11, 8u20, 8u25 и 8u31.

Oracle исправила эту ошибку в обновлении Java 8 40

Неофициальный патч для выполнения доступен для более ранних версий

Благодаря исследованию Holger в этом ответе к соответствующему вопросу я смог разработать патч для выполнения, и мой работодатель освободил его в соответствии с условиями лицензии GPLv2 с Classpath Exception 1 (то же, что и исходный код OpenJDK).

Проект исправления и исходный код размещены на GitHub с более подробной информацией об этой ошибке, а также ссылками на загружаемые двоичные файлы. Патч работает во время выполнения, поэтому никаких изменений в файлах Java на диске не делается, и он должен быть безопасным для использования во всех версиях Oracle Java >= 6 и, по крайней мере, через версию 8 (включая u40 и более поздние версии).

1 Я не юрист, но я понимаю, что GPLv2 w/CPE позволяет коммерческое использование в двоичной форме без применения GPL для совместной работы.