ArithmeticException выдается во время BigDecimal.divide - PullRequest
31 голосов
/ 01 мая 2010

Я думал, java.math.BigDecimal должен быть Ответом и торговлей; необходимости выполнения арифметики бесконечной точности с десятичными числами.

Рассмотрим следующий фрагмент:

import java.math.BigDecimal;
//...

final BigDecimal one = BigDecimal.ONE;
final BigDecimal three = BigDecimal.valueOf(3);
final BigDecimal third = one.divide(three);

assert third.multiply(three).equals(one); // this should pass, right?

Я ожидаю, что assert пройдет, но на самом деле выполнение даже не добирается: one.divide(three) вызывает выброс ArithmeticException!

Exception in thread "main" java.lang.ArithmeticException:
Non-terminating decimal expansion; no exact representable decimal result.
    at java.math.BigDecimal.divide

Оказывается, это поведение явно задокументировано в API :

В случае divide точное отношение может иметь бесконечно длинное десятичное разложение; например, 1, деленное на 3. Если частное имеет бесконечное десятичное разложение, и указана операция для получения точного результата, выдается ArithmeticException. В противном случае возвращается точный результат деления, как и для других операций.

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

final BigDecimal third = one.divide(three, 33, RoundingMode.DOWN);
System.out.println(three.multiply(third));
// prints "0.999999999999999999999999999999999"

Конечно, очевидный вопрос сейчас: «Какой смысл ???». Я думал, что BigDecimal - это решение, когда нам нужна точная арифметика, например. для финансовых расчетов. Если мы не можем даже divide точно, то насколько это может быть полезно? На самом ли деле это служит общей цели, или это полезно только в очень нишевом приложении, где, к счастью, вам вообще не нужно divide вообще?

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

Ответы [ 9 ]

23 голосов
/ 01 мая 2010

Если это неправильный ответ, что МОЖЕМ использовать для точного деления финансовых расчетов? (Я имею в виду, у меня нет специальности финансов, но они все еще используют разделение, верно ???).

Тогда я был в начальной школе 1 , они научили меня, что, когда вы делите на 1 на 3, вы получаете 0,33333 ... то есть повторяющийся десятичный знак. Деление чисел, представленных в десятичной форме, НЕ является точным. Фактически для любой фиксированной базы будут дроби (результат деления одного целого числа на другое), которые нельзя представить точно как число с плавающей запятой конечной точности в этой базе. (Номер будет иметь повторяющуюся часть ...)

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

BigDecimal Javadoc говорит это:

Класс BigDecimal предоставляет пользователю полный контроль над поведением округления. Если режим округления не указан и точный результат не может быть представлен, генерируется исключение; в противном случае вычисления могут быть выполнены с выбранной точностью и режимом округления путем предоставления соответствующего объекта MathContext для операции.

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

РЕДАКТИРОВАТЬ - в ответ на эти наблюдения из OP.

Как BigDecimal обнаруживает бесконечное повторяющееся десятичное число?

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

Он должен отслеживать и обнаруживать цикл в дивиденде. МОЖЕТ БЫТЬ ВЫБРАНО, чтобы справиться с этим другим путем, отметив, где находится повторяющаяся часть и т. Д.

Полагаю, что BigDecimal можно было бы указать для точного представления повторяющегося десятичного числа; то есть как BigRational класс. Однако это сделает реализацию более сложной и более дорогой в использовании 2 . А так как большинство людей ожидают, что числа будут отображаться в десятичном виде, и проблема повторяющегося десятичного числа повторяется в этой точке.

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


1 - Это была отличная начальная школа ...

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

8 голосов
/ 01 мая 2010

Класс BigDecimal не BigFractional. Из некоторых ваших комментариев кажется, что вы просто хотите пожаловаться, что кто-то не встроил все возможные алгоритмы обработки чисел в этот класс. Финансовые приложения не нуждаются в бесконечной десятичной точности; просто идеально точные значения с требуемой точностью (обычно 0, 2, 4 или 5 десятичных цифр).

На самом деле я имел дело со многими финансовыми приложениями, которые используют double. Мне это не нравится, но так было написано (не на Java). Когда есть обменные курсы и конверсии единиц, тогда есть и потенциальные проблемы округления и синяков. BigDecimal устраняет последнее, но есть еще первое для деления.

6 голосов
/ 01 мая 2010

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

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

a/b + c

равно

(a + bc) / b.
4 голосов
/ 01 мая 2010

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

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

В Швейцарии они округляются до 0,05, поскольку у них больше нет 1 или 2 Раппен монет.

2 голосов
/ 27 июня 2012

Чтобы разделить сохранение, вы должны установить MATHcontext,

BigDecimal bd = new BigDecimal(12.12, MathContext.DECIMAL32).divide(new BigDecimal(2)).setScale(2, RoundingMode.HALF_UP);

2 голосов
/ 01 мая 2010

Вы должны предпочесть BigDecimal для финансовых расчетов. Округление должно быть указано бизнесом. Например. сумма (100,00 $) должна быть разделена поровну на три счета. Там должно быть бизнес-правило, счет которого занимает дополнительный цент.

Двойное число, числа с плавающей запятой не подходят для использования в финансовых приложениях, поскольку они не могут точно представлять дробные части 1, не являющиеся экспонентами от 2 рассмотрим 0,6 = 6/10 = 1 * 1/2 + 0 * 1/4 + 0 * 1/8 + 1 * 1/16 + ... = 0,1001 ... b

Для математических расчетов вы можете использовать символическое число, например, сохранение знаменателя и числителя или даже целого выражения (например, это число равно sqrt (5) +3/4). Поскольку это не основной вариант использования Java API, вы найдете его там.

2 голосов
/ 01 мая 2010

Есть ли необходимость в

a=1/3;
b=a*3;

resulting in

b==1;

в финансовых системах? Я думаю, нет. В финансовых системах определяется, какой раунд и масштаб должны использоваться при выполнении расчетов. В некоторых ситуациях режим и шкала определены в законе. Все компоненты могут полагаться на такое определенное поведение. Возврат b == 1 будет ошибкой, поскольку он не будет выполнять указанное поведение. Это очень важно при расчете цен и т. Д.

Это похоже на спецификации IEEE 754 для представления чисел с плавающей точкой в ​​двоичных разрядах. Компонент не должен оптимизировать «лучшее» представление без потери информации, потому что это нарушит контракт.

1 голос
/ 01 мая 2010

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

Насколько я знаю, "арифметики бесконечной точности с десятичными числами" просто не произойдет. Если вам нужно работать с десятичными знаками, то, что вы делаете, вероятно, хорошо, просто ловите исключения. В противном случае быстрый поиск в Google найдет несколько интересных ресурсов для работы с дробями в Java:

http://commons.apache.org/math/userguide/fraction.html

http://www.merriampark.com/fractions.htm

Лучший способ представить дробь в Java?

0 голосов
/ 01 мая 2010

Обратите внимание, что мы используем компьютер ... В компьютере много оперативной памяти, а точность требует оперативной памяти. Поэтому, когда вам нужна бесконечная точность, вам нужно
(infinite * infinite) ^ (infinite * Integer.MAX_VALUE) терабайт оперативной памяти ...

Я знаю, 1 / 3 - это 0.333333..., и должно быть возможно сохранить его в оперативной памяти, как "один разделенный на три", и тогда вы можете умножить его обратно, и у вас должно быть 1. Но я не думаю, что в Java есть что-то подобное ...
Возможно, вам нужно выиграть Нобелевскую цену за то, что вы написали что-то, что делает это. ;-)

...