Нужно ли указывать тип precision для числового поля суммы денег?
Что проверяют
Понимаете ли вы, что деньги в числах с плавающей точкой это не мелкая неточность, а прямой источник расхождений в отчётности. Заодно смотрят, совпадает ли у вас тип в базе и тип в коде: питонист нередко аккуратно объявляет NUMERIC, а потом достаёт значение во float и теряет всё, что выиграл.
Как отвечать
Да, точность указывается явно. Рабочих варианта два:
NUMERIC(p, s)илиDECIMAL(p, s)в базе: точный десятичный тип,NUMERIC(12, 2)хранит 1234567890.12 без округления;- целое число минимальных единиц, то есть копеек, в
BIGINT: арифметика целочисленная, но масштаб приходится помнить самому.
Двоичные FLOAT и DOUBLE PRECISION не подходят: 0.1 в двоичной системе непредставима, и ошибка копится при каждом сложении. В Python деньги живут в decimal.Decimal, создаются из строки, а не из float, и округляются явно:
from decimal import Decimal, ROUND_HALF_UP
total = (Decimal("100.00") / 3).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)Драйвер psycopg отдаёт numeric как Decimal сам, в SQLAlchemy за это отвечает Numeric(asdecimal=True). Масштаб выбирается по валюте: два знака не универсальны, у иены знаков нет, у динара их три. Курсы, ставки и промежуточные расчёты держат больше знаков, чем итог, а округление делается один раз, на границе.
Где спотыкаются
Пишут Decimal(0.1) и считают задачу решённой. В конструктор уже пришёл испорченный float, и внутри окажется длинный двоичный хвост. Источником должна быть строка или целое число.
Берут FLOAT, а неточность прячут форматированием при выводе. На экране сходится, в сумме за месяц нет: округляется только показ, накопленная ошибка остаётся в данных.
Не хранят валюту рядом с суммой. Одна колонка на разные валюты превращает любое сложение в бессмыслицу, и такой отчёт ломается ровно в тот день, когда появляется второй рынок.