Как округлить 0.745 до 0.75 с использованием BigDecimal.ROUND_HALF_UP?
Я столкнулся с странной проблемой при использовании класса BigDecimal
в Java. Я попытался выполнить следующие операции:
double doubleVal = 1.745;
double doubleVal1 = 0.745;
BigDecimal bdTest = new BigDecimal(doubleVal);
BigDecimal bdTest1 = new BigDecimal(doubleVal1);
bdTest = bdTest.setScale(2, BigDecimal.ROUND_HALF_UP);
bdTest1 = bdTest1.setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println("bdTest:" + bdTest); // 1.75
System.out.println("bdTest1:" + bdTest1); // 0.74 проблеемммм ????????????
Однако я получил неожиданные результаты. В выходных данных значение bdTest
равно 1.75
, что ожидаемо, но значение bdTest1
равно 0.74
, и это вызывает у меня недоумение. Почему так получается?
5 ответ(ов)
Не создавайте объекты BigDecimal из значений типа float или double, так как они могут потерять точность. Вместо этого используйте целые числа (int) или строки (String).
В вашем примере код работает правильно после изменения типа с double на String:
public static void main(String[] args) {
String doubleVal = "1.745";
String doubleVal1 = "0.745";
BigDecimal bdTest = new BigDecimal(doubleVal);
BigDecimal bdTest1 = new BigDecimal(doubleVal1);
bdTest = bdTest.setScale(2, BigDecimal.ROUND_HALF_UP);
bdTest1 = bdTest1.setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println("bdTest:" + bdTest); // 1.75
System.out.println("bdTest1:" + bdTest1); // 0.75, проблем нет
}
Таким образом, вы получаете ожидаемый результат без опасений потерять точность, что важно при работе с финансовыми или другими чувствительными к точности расчетами.
Используйте BigDecimal.valueOf(double d)
вместо new BigDecimal(double d)
. Второй способ может приводить к ошибкам точности, связанным с представлением чисел с плавающей запятой (float и double). Метод valueOf
создает объект BigDecimal
, который точно представляет значение, предотвращая возможные потери точности, возникающие при конструировании из double.
Пример:
BigDecimal bd = BigDecimal.valueOf(0.1);
Таким образом, рекомендуется всегда использовать BigDecimal.valueOf()
для создания экземпляров BigDecimal
из значений типа double.
Это, возможно, поможет вам понять, что пошло не так.
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal bdTest = new BigDecimal(0.745);
BigDecimal bdTest1 = new BigDecimal("0.745");
bdTest = bdTest.setScale(2, BigDecimal.ROUND_HALF_UP);
bdTest1 = bdTest1.setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println("bdTest:" + bdTest); // выводит "bdTest:0.74"
System.out.println("bdTest1:" + bdTest1); // выводит "bdTest:0.75"
}
}
Проблема заключается в том, что ваш ввод (типа double x=0.745;
) не может точно представить 0.745. На самом деле, он сохраняет значение, которое немного меньше этого числа. Для BigDecimal
это значение уже ниже 0.745, из-за чего происходит округление в меньшую сторону.
Попробуйте не использовать конструкторы BigDecimal(double/float)
.
Для вашего интереса, чтобы сделать то же самое с double
, можно использовать следующий код:
double doubleVal = 1.745;
double doubleVal2 = 0.745;
doubleVal = Math.round(doubleVal * 100 + 0.005) / 100.0;
doubleVal2 = Math.round(doubleVal2 * 100 + 0.005) / 100.0;
System.out.println("bdTest: " + doubleVal); //1.75
System.out.println("bdTest1: " + doubleVal2); //0.75
Либо можно упростить этот процесс с помощью форматирования строк:
double doubleVal = 1.745;
double doubleVal2 = 0.745;
System.out.printf("bdTest: %.2f%n", doubleVal);
System.out.printf("bdTest1: %.2f%n", doubleVal2);
Оба варианта выведут:
bdTest: 1.75
bdTest1: 0.75
Я предпочитаю сохранять код как можно проще. 😉
Как отметил @mshutov, вам нужно немного доработать код, чтобы гарантировать, что значения, находящиеся на полпути, всегда округляются в большую сторону. Это связано с тем, что такие числа, как 265.335
, немного меньше, чем они выглядят.
Варианты доступны, например:
Double d = 123.12;
BigDecimal b = new BigDecimal(d, MathContext.DECIMAL64); // b = 123.1200000
b = b.setScale(2, BigDecimal.ROUND_HALF_UP); // b = 123.12
BigDecimal b1 = new BigDecimal(collectionFileData.getAmount(), MathContext.DECIMAL64).setScale(2, BigDecimal.ROUND_HALF_UP); // b1 = 123.12
d = (double) Math.round(d * 100) / 100;
BigDecimal b2 = new BigDecimal(d.toString()); // b2 = 123.12
Каждый из этих подходов позволяет создать объект BigDecimal
, который будет правильно отображать значение с двумя знаками после запятой. В первом примере значение инициализируется напрямую из Double
, затем устанавливается нужный масштаб. Во втором примере мы извлекаем значение из коллекции и также задаем масштаб. В третьем примере происходит округление значения Double
до двух десятичных знаков, и оно преобразуется в BigDecimal
через строковое представление. Выбирайте тот метод, который наилучшим образом соответствует вашим требованиям.
Как округлить число до n знаков после запятой в Java
ArithmeticException: "Нескончаемое десятичное представление; нет точного десятичного результата"
Почему Math.round(0.49999999999999994) возвращает 1?
Как объявить массив в одну строку?
Создание репозитория Spring без сущности