Почему использование float для хранения суммы денег в базе данных плохо и какой тип лучше использовать?
Что проверяют
Понимаете ли вы, что двоичная плавающая точка не представляет десятичные дроби точно, и знаете ли, чем это лечится на уровне схемы, а не форматированием при выводе.
Как отвечать
float это двоичная дробь с фиксированным числом разрядов мантиссы. Значения вроде одной десятой в двоичной системе бесконечны, поэтому хранится ближайшее представимое число, а погрешность накапливается на сложениях:
>>> 0.1 + 0.2
0.30000000000000004
>>> sum([0.1] * 10) == 1.0
FalseДля денег это расхождение баланса с суммой операций, несходящаяся сверка и зависимость результата от порядка слагаемых.
Что использовать. В базе numeric с явными точностью и масштабом, например numeric(18, 2): он хранит десятичные разряды и считает точно. Второй рабочий вариант это целое число минимальных единиц в bigint, но тогда масштаб приходится помнить самому и не терять на границах системы. Тип money в PostgreSQL брать не стоит: фиксированный масштаб и зависимость от настроек локали.
В Python этому соответствует decimal.Decimal, драйвер отдаёт numeric именно им. Создавать его надо из строки: Decimal("0.1") точен, а Decimal(0.1) уже унаследует двоичную погрешность. Округление задаётся явно через quantize с нужным режимом.
Где спотыкаются
Считают, что проблема только в выводе, и лечат её печатью с двумя знаками. Ошибка уже внутри числа: форматирование её прячет, а сравнение на равенство и накопленная сумма продолжают врать.
Второе: смешивают типы в одном выражении. В Python умножение Decimal на float бросает ошибку, а вот в базе арифметика numeric с числом двойной точности молча приводит всё к двоичной плавающей точке и теряет точность.
Третье: забывают про режим округления. По умолчанию Decimal округляет к ближайшему чётному, а бухгалтерия часто ждёт округления половины вверх. Несовпадение вылезает копейками при делении, например когда сумму разбивают на части.