Является ли непоследовательность в округлении между 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?
Ответы
Ответ 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 для совместной работы.