Числа №11. Большие десятичные типы
Из прошлых заметок ясно, почему Float нельзя использовать для денег: вы не
контролируете точность. В этих случаях говорят: используйте классы BigInteger
и BigDecimal. Они хранят циклопические числа с гарантией точности. Что это за
классы такие и как они устроены?
Вспомним, что целое число можно представить массивом разрядов. При сложении разряды складываются и проверяется переполнение. Если оно было, то к следующему разряду прибавляется единица и так далее.
Условный класс BigInteger устроен именно так: он хранит знак и числовой
массив. Каждый его элемент — разряд. При этом разряд очень велик: как правило
это integer, то есть около двух миллиардов! За счет этого достигается
невероятная емкость. Если в каждом элементе MAX_INT значений, а всего их —
тоже MAX_INT, то итоговое число равно
MAX_INT ^ MAX_INT
, то есть два миллиарда в степени два миллиарда. Наверное, если бы каждый атом был Вселенной, то и тогда итоговое число атомов не превысило бы этой цифры.
Складываются такие числа поразрядно, в столбик. Берутся нулевые элементы массивов и суммируются как два integer с учетом переполнения. Если оно было, в нулевой разряд идет остаток, а в следующий накидывается единичка. Аналогично устроено вычитание.
Класс BigDecimal устроен просто: это BigInteger с данными о том, сколько
разрядов хранится после десятичной запятой. Как и в случае с Float, при
сложении и вычитании число “плавает”, но на этот раз без потери точности. Мы не
ограничены 32 битами, в нашем распоряжении вся оперативная память.
Получается, что условные BigDecimal и BigInteger — это программная эмуляция
больших чисел. Операции над ними раскладываются в серию машинных команд,
например когда складываются два разряда. Однако таких разрядов много, плюс мы
проверяем переполнение вручную. Такие операции очень медленны в сравнении с
теми, что выполняются на процессоре. Поэтому к программной эмуляции прибегают
только если точность критически важна.
Postgres предлагает тип numeric — высокоточное хранилище чисел. Он хранит
знак, масштаб и точность — сколько разрядов выделить на число и сколько
проходится на дробную часть. Далее идет массив типа short[] — двухбайтовых
элементов. Каждый элемент — это разряд от 0 до 9999; их число не ограничено.
Один элемент хранит 10.000 значений, два элемента — 100.000.000 значений. Три —
накиньте еще четыре нуля и так далее. Складываются numeric как описано выше,
только переполнение проверяется для порога в 10 тысяч.
Почему в Postgres выбрали именно такой порог, я не знаю. Скорее всего, это ускоряет десятичные операции, например умножение на десятки, деление на сто и так далее.
Если вы пользуетесь numeric, обязательно указывайте точность. Иначе возможна
ситуация, когда в целой части, скажем, 123, а в дробной — километровый
хвост. Numeric относится к типам произвольной длины, и если значение не
поместиться в 512 байтов (или около того), то будет сброшено в
TOAST-хранилище. Проблемы можно избежать, указав точность: sum numeric(15, 2).
Впрочем, даже столь мощные десятичные типы бывают избыточны. Иногда деньги
хранят в микроцентах, положение на плоскости — в нанометрах и так далее. Типа
long вполне хватает для этого. Главное — найти такой масштаб, в рамках
которого вам удобно.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter