0

Как округлить 0.745 до 0.75 с использованием BigDecimal.ROUND_HALF_UP?

11

Я столкнулся с странной проблемой при использовании класса 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 ответ(ов)

1

Не создавайте объекты 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, проблем нет
}

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

0

Используйте BigDecimal.valueOf(double d) вместо new BigDecimal(double d). Второй способ может приводить к ошибкам точности, связанным с представлением чисел с плавающей запятой (float и double). Метод valueOf создает объект BigDecimal, который точно представляет значение, предотвращая возможные потери точности, возникающие при конструировании из double.

Пример:

BigDecimal bd = BigDecimal.valueOf(0.1);

Таким образом, рекомендуется всегда использовать BigDecimal.valueOf() для создания экземпляров BigDecimal из значений типа double.

0

Это, возможно, поможет вам понять, что пошло не так.

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).

0

Для вашего интереса, чтобы сделать то же самое с 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, немного меньше, чем они выглядят.

0

Варианты доступны, например:

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 через строковое представление. Выбирайте тот метод, который наилучшим образом соответствует вашим требованиям.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь