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

StackOverflow https://stackoverflow.com/questions/179427

  •  05-07-2019
  •  | 
  •  

Вопрос

На этот вопрос уже есть ответ здесь:

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

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.

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

Это было полезно?

Решение

Чтобы контролировать точность арифметики с плавающей запятой, вы должны использовать java.математика.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

Другие советы

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

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

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

Конструктор double преобразует всю точность double к a 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.

Редактировать:

Кроме того, просто чтобы указать, что это не проблема округления 'Java'.Другие языки демонстрируют аналогичное (хотя и не обязательно согласованное) поведение.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);

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

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:

двойной ответ = Математический раунд (комиссия * 100000) / 100000.0;

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

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

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

Это забавный вопрос.

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

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

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

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

А еще лучше использовать Jнаука поскольку 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 определяет, что следует использовать только ДВА знака после запятой.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top