Можем ли мы использовать double для хранения денежных полей и использовать BigDecimal для арифметики - PullRequest
5 голосов
/ 06 декабря 2011

Мне известна проблема с double / float, и рекомендуется использовать BigDecimal вместо double / float для представления денежных полей.Но double / float более эффективен и экономит место.Тогда мой вопрос: допустимо использовать double / float для представления денежных полей в классе Java, но использовать BigDecimal, чтобы позаботиться об арифметике (то есть преобразовать double / float в BigDecimal перед любой арифметикой) и проверке на равенство?

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

Есть ли какие-либо подводные камни для этого?Заранее спасибо.

Ответы [ 6 ]

8 голосов
/ 06 декабря 2011

Нет, вы не можете.

Предположим, что double достаточно для хранения двух значений x и y. Затем вы конвертируете их в безопасные BigDecimal и умножаете их. Результат является точным, однако, если вы сохраните результат умножения обратно в double, скорее всего, вы потеряете точность. Доказательство:

double x = 1234567891234.0;
double y = 1234567891234.0;
System.out.println(x);
System.out.println(y);

BigDecimal bigZ = new BigDecimal(x).multiply(new BigDecimal(y));
double z = bigZ.doubleValue();
System.out.println(bigZ);
System.out.println(z);

Результаты:

1.234567891234E12          //precise 'x'
1.234567891234E12          //precise 'y'
 1524157878065965654042756  //precise 'x * y'
1.5241578780659657E24      //loosing precision

x и y являются точными, а также умножение с использованием BigDecimal. Однако после возврата к double мы теряем младшие значащие цифры.

6 голосов
/ 14 декабря 2011

Я бы также порекомендовал вам использовать только BigDecimal для ВСЕЙ арифметики, которая может включать валюту.

Убедитесь, что вы всегда используете конструктор String BigDecimal. Зачем? Попробуйте следующий код в тесте JUnit:

assertEquals(new BigDecimal("0.01").toString(), new BigDecimal(0.01).toString());

Вы получите следующий вывод:

expected:<0.01[]> but was <0.01[000000000000000020816681711721685132943093776702880859375]>

По правде говоря, вы не можете хранить ТОЧНО 0,01 как «двойную» сумму. Только BigDecimal хранит номер, который вам нужен ИМЕННО , как вы хотите.

И помните, что BigDecimal является неизменным. Будет скомпилировано следующее:

BigDecimal amount = new BigDecimal("123.45");
BigDecimal more = new BigDecimal("12.34");
amount.add(more);
System.out.println("Amount is now: " + amount);

но результат будет:

Сумма сейчас: 123,45

Это потому, что вам нужно присвоить результат новой (или той же) переменной BigDecimal.

Другими словами:

amount = amount.add(more)
3 голосов
/ 06 декабря 2011

Что приемлемо, зависит от вашего проекта.Вы можете использовать double и long в некоторых проектах.Однако в других проектах это считается недопустимым.В виде двойного числа вы можете представить значения до 70 000 000 000 000,00 цента (больше, чем государственный долг США), с фиксированным длинным местом вы можете точно представить 90 000 000 000 000 000,00.

Если вам приходится иметь дело с гиперинфляционными валютами (aплохая идея в любом случае) но по какой-то причине все равно нужно учитывать каждый цент, используйте BigDecimal.

Если вы используете double или long или BigDecimal, вы должны округлить результат.Как это сделать, зависит от типа данных, и BigDecimal наименее подвержен ошибкам, поскольку вам необходимо указать, какое округление и точность для различных операций.С двойной или длинной, вы оставлены на свои собственные устройства.

2 голосов
/ 06 декабря 2011

long будет намного лучшим выбором, чем double / float.

Вы уверены, что использование типа BigDecimal станет настоящим узким местом?

0 голосов
/ 28 июля 2014

Если double используется только для хранения десятичных значений, тогда да, вы можете при некоторых условиях: если вы можете гарантировать, что ваши значения имеют не более 15 десятичных цифр, то преобразование значение до double (53 бита точности) и преобразование double обратно в десятичное число с точностью до 15 цифр (или меньше) даст вам исходное значение, т.е. без потерь, из применения теоремы Дэвида Матула, доказанной в его статья Входящие и исходящие преобразования . Обратите внимание, что для применения этого результата преобразования должны быть выполнены с правильным округлением .

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

0 голосов
/ 06 декабря 2011

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

«Правильное» решение этой проблемы, по моему опыту, заключается в хранениизначения в виде целых чисел (например, Long), представляющих тысячи долларов.Это дает достаточное разрешение для большинства задач, например, начисления процентов, в то время как проблема использования поплавков / двойников обходится стороной.В качестве дополнительного «бонуса» для этого требуется примерно столько же памяти, сколько для чисел с плавающей запятой / удваивается.

...