Как разрешить двойную проблему Java Rounding

Похоже, что вычитание вызывает какую-то проблему, и результирующее значение неверно.

double tempCommission = targetPremium.doubleValue()*rate.doubleValue()/100d;

78,75 = 787,5 * 10,0/100d

double netToCompany = targetPremium.doubleValue() - tempCommission;

708,75 = 787,5 - 78,75

double dCommission = request.getPremium().doubleValue() - netToCompany;

877.8499999999999 = 1586,6 - 708,75

Итоговое ожидаемое значение будет 877.85.

Что нужно сделать для правильного расчета?

Ответ 1

Чтобы контролировать точность арифметики с плавающей запятой, вы должны использовать java.math.BigDecimal. Прочтите о необходимости BigDecimal Джона Зуковски за дополнительной информацией.

Учитывая ваш пример, последняя строка будет выглядеть следующим образом: BigDecimal.

import java.math.BigDecimal;

BigDecimal premium = BigDecimal.valueOf("1586.6");
BigDecimal netToCompany = BigDecimal.valueOf("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

Это приводит к следующему результату.

877.85 = 1586.6 - 708.75

Ответ 2

Как указывалось в предыдущих ответах, это является следствием выполнения арифметики с плавающей запятой.

Как показано в предыдущем посте, когда вы делаете числовые вычисления, используйте java.math.BigDecimal.

Однако при использовании BigDecimal есть gotcha. Когда вы конвертируете из двойного значения в BigDecimal, у вас есть выбор использования нового конструктора BigDecimal(double) или статического метода BigDecimal.valueOf(double) factory. Используйте статический метод factory.

Двойной конструктор преобразует всю точность double в BigDecimal, а статический factory эффективно преобразует его в String, а затем преобразует его в BigDecimal.

Это становится актуальным, когда вы используете те тонкие ошибки округления. Число может отображаться как .585, но внутренне его значение равно "0.58499999999999996447286321199499070644378662109375". Если вы использовали конструктор BigDecimal, вы получите число, которое НЕ равно 0,585, тогда как статический метод даст вам значение, равное 0,585.

double value = 0.585;
System.out.println(new BigDecimal(value));
System.out.println(BigDecimal.valueOf(value));

в моей системе дает

0.58499999999999996447286321199499070644378662109375
0.585

Ответ 3

Другой пример:

double d = 0;
for (int i = 1; i <= 10; i++) {
    d += 0.1;
}
System.out.println(d);    // prints 0.9999999999999999 not 1.0

Вместо этого используйте BigDecimal.

EDIT:

Кроме того, просто чтобы указать, что это не проблема "округления" Java. Другие языки выставки аналогичное (хотя и не обязательно последовательное) поведение. Java по крайней мере гарантирует последовательное поведение в этом отношении.

Ответ 4

Я бы изменил приведенный выше пример следующим образом:

import java.math.BigDecimal;

BigDecimal premium = new BigDecimal("1586.6");
BigDecimal netToCompany = new BigDecimal("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

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

import java.math.BigDecimal;

BigDecimal premium = BigDecimal.valueOf(158660, 2);
BigDecimal netToCompany = BigDecimal.valueOf(70875, 2);
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

Я думаю, что эти варианты лучше, чем использование парных. В номерах webapps все равно начинаются как строки.

Ответ 5

Каждый раз, когда вы выполняете вычисления с удвоением, это может произойти. Этот код даст вам 877.85:

double answer = Math.round(dCommission * 100000)/100000.0;

Ответ 6

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

Ответ 7

См. ответы на этот вопрос. По существу то, что вы видите, является естественным следствием использования арифметики с плавающей запятой.

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

Ответ 8

Это забавная проблема.

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

Однако фундаментальная проблема с попыткой округлить ее заключается в том, что когда вы делите на коэффициент, чтобы перемасштабировать его, вы фактически вводите еще одну возможность для точных проблем. Любая манипуляция двойниками может вводить проблемы неточности с разной частотой. Особенно, если вы пытаетесь округлить значительную цифру (так что ваши операнды равны < 0), например, если вы выполните следующее с кодом Timons:

System.out.println(round((1515476.0) * 0.00001) / 0.00001);

В результате получится 1499999.9999999998, где цель состоит в том, чтобы округлить в единицах 500000 (т.е. мы хотим 1500000)

На самом деле единственный способ быть абсолютно уверенным в том, что вы устранили неточность, - это пройти через BigDecimal для масштабирования. например.

System.out.println(BigDecimal.valueOf(1515476.0).setScale(-5, RoundingMode.HALF_UP).doubleValue());

Использование комбинации стратегии epsilon и стратегии BigDecimal даст вам прекрасный контроль над вашей точностью. Идея, являющаяся epsilon, очень близка, и BigDecimal устранит любую неточность, вызванную изменением масштаба. Хотя использование BigDecimal снижает ожидаемую производительность вашего приложения.

Мне было указано, что последний шаг использования BigDecimal для его масштабирования не всегда необходим для некоторых случаев использования, когда вы можете определить, что нет входного значения, которое окончательное деление может повторно ввести ошибку. В настоящее время я не знаю, как правильно это определить, поэтому, если кто-нибудь знает, как тогда я был бы рад услышать об этом.

Ответ 9

Пока что самый элегантный и эффективный способ сделать это в Java:

double newNum = Math.floor(num * 100 + 0.5) / 100;

Ответ 10

Еще лучше использовать JScience, так как BigDecimal довольно ограничен (например, без функции sqrt)

double dCommission = 1586.6 - 708.75;
System.out.println(dCommission);
> 877.8499999999999

Real dCommissionR = Real.valueOf(1586.6 - 708.75);
System.out.println(dCommissionR);
> 877.850000000000

Ответ 11

double rounded = Math.rint(toround * 100) / 100;

Ответ 12

Несмотря на то, что для точных вычислений вы не должны использовать удвоения, мне помог следующий трюк, если вы все равно округляете результаты.

public static int round(Double i) {
    return (int) Math.round(i + ((i > 0.0) ? 0.00000001 : -0.00000001));
}

Пример:

    Double foo = 0.0;
    for (int i = 1; i <= 150; i++) {
        foo += 0.00010;
    }
    System.out.println(foo);
    System.out.println(Math.round(foo * 100.0) / 100.0);
    System.out.println(round(foo*100.0) / 100.0);

Какие принты:

0.014999999999999965
0.01
0.02

Дополнительная информация: http://en.wikipedia.org/wiki/Double_precision

Ответ 13

Это довольно просто.

Используйте оператор%.2f для вывода. Проблема решена!

Например:

int a = 877.8499999999999;
System.out.printf("Formatted Output is: %.2f", a);

Приведенный выше код приводит к выводу на печать: 877,85

Оператор%.2f определяет, что должны использоваться только ДВА десятичных знаков.