BigDecimal



В этом посте обсудим, зачем нужен этот класс и как не ошибиться при использовании.



Ответ на первый вопрос перед постом — false. Сумма 0.1 и 0.2 равна 0.30000000000000004.



Запустим тот же пример на Python 2.7:

print(0.1 + 0.2)

0.3



Почему python справился с примером, а java — нет? Как писать на java высоконагруженные приложения, если она не может сложить 0.1 и 0.2?😒



Oracle подробно объясняет этот феномен на 80 страницах. Главная проблема в том, как десятичная часть хранится в двоичном формате.



Целые числа записываются через степень двойки однозначно:

9 = 8 + 1 = 2^3 + 2^0 → 1001



Десятичная часть выражается через отрицательную степень двойки. Иногда получается нормально:

0.5 = 2^(-1) → 0.1



Иногда не очень:

0.1 = 2^(-4) + 2^(-5) + 2^(-8) + … →

0.00111101110011001100110011001



Если перевести это обратно в десятичную форму, видно, что хранится там совсем не 0.1, а 0.100000001490116119384765625



С 0.2 похожая ситуация, поэтому результат получается искажённым.



Python 2.7 использует для вычислений ту же систему, но показывает меньше знаков после запятой. Поэтому ответ выглядит нормально. Python 3 выводит больше знаков и результат похож на результат java:

print(0.1 + 0.2)

0.30000000000000004



Зачем использовать такую неточную систему?



Если хранить целую и дробную часть одинаково, то для вычислений не нужно дополнительных преобразований. Результат считается быстро, а уровень погрешности на практике низкий. В нашем примере ошибка на 16 разрядов ниже основного значения, в большинстве случаев это ок.



Для точных вычислений используются три основных метода:

🔸Ограниченная точность (limited-precision decimal)

🔸Символьная логика (symbolic calculations)

🔸Длинная арифметика (arbitrary-precision decimal)



BigDecimal использует последний подход. Число 12.345 хранится как пара:

▫️целое значение: 12345

▫️количество десятичных знаков: 3



За счёт этого BigDecimal хранит числа без потери точности. Целая часть хранится либо в переменной int, либо в массиве. Размер числа ограничен только количеством доступной памяти.



Из минусов:

Медленные вычисления

Большой расход памяти

Много промежуточных объектов

Менее выразительный код



Теперь ответ на второй вопрос перед постом:

объекты BigDecimal(0.2) и BigDecimal("0.2") НЕ равны.



В конструктор

BigDecimal(0.2)

передаётся примитив double, в котором вместо 0.2 лежит

0.20000000000000001110223...

Поэтому объект BigDecimal будет хранить это число, а не 0.2



Это самая частая ошибка при работе с BigDecimal. Для чисел с запятой надёжнее передавать в конструктор строку.