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



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



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.



что нужно сделать, чтобы обеспечить правильный расчет?

620   13  

13 ответов:

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

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

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

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

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

двойной конструктор преобразует всю точность double до BigDecimal в то время как статическая фабрика, эффективно преобразует его в 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

еще пример:

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". Другие языки показывают похожие (хотя и не обязательно последовательное) поведение. Ява, по крайней мере, гарантирует последовательное поведение в этом отношении.

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

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 числа начинаются как строки в любом случае.

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

двойной ответ = математика.раунд (dCommission * 100000) / 100000.0;

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

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

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

Это интересный вопрос.

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

в фундаментальная проблема с попыткой округлить его заключается в том, что когда вы делите на коэффициент для его масштабирования, вы фактически вводите еще одну возможность для проблем точности. Любая манипуляция двойниками может привести к проблемам неточности с различной частотой. Особенно, если вы пытаетесь округлить очень значащую цифру (так что ваши операнды
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 даст вам точный контроль над вашей точностью. Идея заключается в том, что Эпсилон приближает вас очень близко, а затем BigDecimal устранит любую неточность, вызванную последующим масштабированием. Хотя, используя bigdecimal позволит снизить ожидаемый производительность вашего приложения.

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

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

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

лучше использовать 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
double rounded = Math.rint(toround * 100) / 100;

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

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

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

использовать %.Оператор 2F для вывода. Проблема решена!

например:

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

приведенный выше код приводит к выходу печати: 877.85

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

Comments

    Ничего не найдено.