RoundingMode.UP с DecimalFormat не работает должным образом - PullRequest
14 голосов
/ 01 августа 2020

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

1.08 -> 2
9.5 -> 10

, но округляет 0,08 как 0 вместо 1. Я что-то здесь упустил?

DecimalFormat df = new DecimalFormat("0");
df.setRoundingMode(RoundingMode.UP);
Double number = 0.08;
System.out.println(df.format(number)); // expecting 1 but produces 0
    
number = 1.08;
System.out.println(df.format(number)); // correctly produces 2

1 Ответ

9 голосов
/ 01 августа 2020

Это похоже на проявление ошибки JDK-8174722 , зарегистрированной для JDK8 и все еще открытой с исправленной версией «tbd» (я вижу такое же поведение на JDK14). В этом отчете об ошибке округление 0.0001 до двух знаков после запятой неверно приводит к 0.00, а не 0.01.

Я попытался отследить этот расчет по исходному коду DecimalFormat и обнаружил несколько предположения о количестве значащих цифр, сохраненные для совместимости, но, очевидно, являющиеся причиной подобных проблем. Моя догадка, основанная на исходном коде, заключается в том, что если первое di git после запрошенной точности равно 0 (или достаточно близко), функция округляется в меньшую сторону. Такое поведение противоречит документации, в которой четко указано иное.

Если бы это было легко исправить, я полагаю, связанная ошибка была бы исправлена ​​задолго до этого, но она также имеет низкий приоритет.

Как предполагалось в комментариях, использование функций Math.ceil(), Math.floor() и Math.round() обеспечит более предсказуемые / согласованные результаты и будет достаточным для большинства случаев использования.

В своих комментариях вы предложили пользователь предоставил RoundingMode, поэтому, если вам нужно прямое применение этого перечисления, MCVE отчета об ошибке включает обходной путь с использованием BigDecimal.setScale(). @ Андреас опубликовал приложение этого обходного пути в этот комментарий , чтобы создать эту строку:

// Note: this ignores Locale
BigDecimal.valueOf(number).setScale(0, RoundingMode.UP).toPlainString();

Другой вариант с использованием DecimalFormat позволит изменить настройки локали и будет:

df.format(BigDecimal.valueOf(number).setScale(0, RoundingMode.UP));

Обратите внимание, что, поскольку вы использовали бы округление в setScale(), вам не нужно также использовать его в DecimalFormat.

...