2008-10-07 2 views
69

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

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.

Что необходимо сделать для правильного расчета?

ответ

84

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

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

import java.math.BigDecimal; 

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

В результате получается следующий выход.

877.85 = 1586.6 - 708.75 
+7

Невозможно вообще «избежать» арифметических ошибок с плавающей запятой. Количество бит, используемых при представлении числа, всегда будет конечным. Все, что вы можете сделать, это использовать типы данных с более высокой точностью (бит). – 2008-10-07 17:17:49

+0

Это правда. Я отредактирую свой ответ, чтобы более точно отразить использование BigDecimal. – 2008-10-07 17:29:32

+4

Я добавлю, что BigDecimal_division_ нужно обрабатывать немного иначе, чем +, -, *, поскольку по умолчанию он генерирует исключение, если он не может вернуть точное значение (1/3, например). В подобной ситуации я использовал: BigDecimal.valueOf (a) .divide (BigDecimal.valueOf (b), 25, RoundingMode.HALF_UP) .doubleValue(), где 25, если максимальные цифры точности (более чем необходимы двойному результату). – 2011-07-12 16:05:12

3

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

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

6

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

двойной ответ = Math.round (dCommission * 100000)/100000.0;

+1

лучше разделить, что 100000.0 вместо всего 100000; Math.round возвращает long, поэтому в противном случае вы будете использовать целочисленное деление. – 2008-10-07 17:22:00

4

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

+0

Единственный улов здесь в том, что часть процента может быть потеряна в начале процесса - это может быть плохо для финансовых приложений. Если это проблема, вы можете сохранить количество 1/10 центов или любую необходимую вам точность. – 2008-10-07 23:45:59

+0

Если бы я мог вернуться во времени и дать мне свое «трюк», это было бы так. Цена как гроши! (или 1е-3 или 1е-6, который по-прежнему хорош для 2+ миллионов долларов как int) – Trenton 2009-10-27 07:54:36

10

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

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 по крайней мере гарантирует последовательное поведение в этом отношении.

53

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

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

Однако, есть вопрос использования 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 
+3

Я сталкивался с этой проблемой много раз, и это действительно очень раздражает! – Richard 2011-10-04 08:28:18

7

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

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

1

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

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

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

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

-3

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

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

Например:

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

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

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

3

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

Идея ответа 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()); 

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

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

2

еще лучше использовать в качестве 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