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