null

Java и деньги

Главная проблема работа с деньгами в программировании заключается в том, что привычные нам десятичные дроби компьютер хранит в двоичной системе. Из-за этого 0.1 + 0.2 в double превращается в 0.30000000000000004. В финансовой сфере такая погрешность недопустима.

В Java есть три основных способа обойти эту проблему. Разберем их:

  1. Два long: рубли и копейки отдельно

Первое, что приходит в голову - разделить сумму на две части и хранить рубли и копейки в разных переменных типа long.

long rubles = 100;
long kopecks = 50;

Плюсы: Максимальная точность, никаких проблем с двоичным представлением.
Минусы: Любая математическая операция превращается в сложный код. Вам нужно вручную переносить разряды при сложении копеек, учитывать знаки при отрицательных суммах и т.д. В общем ад для разработчика.

  1. Один long: всё в копейках

Более прагматичный подход. Мы отказываемся от дробей и храним всю сумму в минорных единицах (в копейках, центах и т.д.).

long totalKopecks = 10050; // 100 рублей 50 копеек

Плюсы: Скорость работы примитивов, нет проблем с округлением при сложении и вычитании.
Минусы: Проблемы начинаются при умножении и делении. Например, при расчете налога 13% от 100.50 рублей вы получите 1306.5 копеек. Придется вручную реализовывать логику округления. Помимо этого становиться сложно работать с валютами, где нет минорных единиц.

  1. BigDecimal: золото

Основная часть профессиональных финансовых систем на Java использует java.math.BigDecimal. Этот класс представляет число в виде массива цифр произвольной длины и отдельной переменной для позиции запятой. Он лишен проблем двоичного округления.

Имейте ввиду - создавать BigDecimal из double это ошибка:

// ТАК ДЕЛАТЬ НЕЛЬЗЯ
BigDecimal bad = new BigDecimal(0.1); // 0.1000000000000000055511151231257827021181583404541015625

// ТАК ПРАВИЛЬНО
BigDecimal good = new BigDecimal("0.1"); // Из строки
BigDecimal alsoGood = BigDecimal.valueOf(0.1); // Или через фабричный метод

Главная сила BigDecimal - строгий контроль над округлением. При делении всегда нужно указывать правило округления, иначе программа упадет с ArithmeticException, если результат это бесконечная дробь.

BigDecimal total = new BigDecimal("100.00");
BigDecimal percent = new BigDecimal("0.13"); // 13%

// Делим нацело с округлением до копеек
// RoundingMode.HALF_EVEN — банковское округление
BigDecimal tax = total.multiply(percent)
                      .setScale(2, RoundingMode.HALF_EVEN);

Итог

  • BigDecimal - выбор для 99% бизнес-приложений, банковских систем. Он медленнее примитивов, но спасает от критических багов с округлением и потерей точности.
  • Хранение в минорных единицах - может подходить для того самого одно процента высоконагруженных систем, где важна каждая наносекунда.
  • Два long - cложность поддержки перевешивает любые плюсы.
Вперед

Коротко о себе:

Работаю кем-то в компании Tune-it. На работе занимаюсь какими-то проектами, связанными с чем-то.

Ничего не найдено. n is 0