Деньги в программировании выглядят как обычные числа. Но стоит использовать неправильный тип данных, и система начинает незаметно терять копейки, округлять суммы или выдавать странные результаты в расчётах. В небольшом проекте это может остаться незамеченным. В продакшене с платежами это превращается в проблему.
Разберёмся, как хранить валюту правильно и какие подходы действительно работают.
Деньги это не просто число
Любая сумма существует не сама по себе, а в связке с валютой. А у валюты есть характеристики, которые напрямую влияют на хранение данных:
код по стандарту ISO 4217;
количество знаков после запятой;
минимальная расчётная единица;
минимальная физическая единица наличности;
возможные изменения точности со временем.
Большинство валют используют два знака после запятой. Но есть исключения: валюты без дробной части, валюты с тремя знаками, а криптовалюты могут иметь 8, 12 и даже 18 знаков после запятой.
Вывод простой: хранить просто число без указания валюты это архитектурная ошибка.
Главные требования к хранению
Если система работает с деньгами, она должна соответствовать следующим требованиям:
Хранить сумму вместе с валютой.
Поддерживать достаточную точность.
Учитывать минимальную расчётную единицу.
Корректно работать при накоплениях и промежуточных вычислениях.
Особенно это важно, если вы рассчитываете комиссии, проценты, дробные начисления или агрегируете тысячи операций.
Подход №1: хранить в целых минорных единицах
Самый надёжный и проверенный способ хранить деньги как целое число в минимальных единицах.
Например:
10 долларов → 1000 центов
5 евро → 500 центов
В базе данных это обычное целое число.
Почему это хорошо
Нет округлений.
Нет потерь точности.
Арифметика предсказуемая.
Где начинаются сложности
Нужно всегда помнить про масштаб.
При выводе требуется преобразование обратно.
Если изменится точность валюты, придётся пересчитывать данные.
В интеграциях легко забыть, что число хранится в центах.
Если используется этот подход, важно либо жёстко зафиксировать масштаб, либо хранить его рядом с числом.
Для хранения лучше использовать большие целые типы. В SQL это может быть BIGINT или DECIMAL с нулевой дробной частью.
Подход №2: использовать Decimal / Numeric
Второй популярный вариант хранить сумму как десятичное число с фиксированной точностью.
Например: DECIMAL(18, 2).
Плюсы
Число выглядит привычно: 10.50.
Не нужно вручную переводить в центы.
Легче читать данные в базе.
Меньше риска ошибиться при интеграции.
На что обратить внимание
Нужно правильно выбрать precision и scale.
При смене требований к точности может потребоваться изменение структуры таблиц.
Нельзя путать Decimal с float.
Этот подход чаще выбирают в корпоративных системах и бухгалтерии, где важна читаемость данных.
Чего делать нельзя
Не используйте float и double
Числа с плавающей точкой работают в двоичной системе. Поэтому:
0.1 + 0.2 может не равняться 0.3.
Для финансов это недопустимо. Даже если ошибка проявляется на 10-м знаке, она может накопиться при массовых расчётах.
Осторожно с типом MONEY
В некоторых СУБД есть специальный тип MONEY. Он кажется удобным, но часто имеет ограничения по точности и масштабированию. В серьёзных проектах обычно предпочитают явный Decimal или целые значения.
А что насчёт сверхточных вычислений?
Иногда бизнес-логика требует идеальной точности при дробных операциях. Например:
взять треть от суммы, а потом умножить результат обратно.
Обычные десятичные числа могут дать не идеально точный результат из-за округления. В теории можно использовать рациональные числа. Хранить дробь как числитель и знаменатель. Это гарантирует абсолютную точность в промежуточных вычислениях.
Но на практике такой подход сложен, плохо поддерживается в базах данных и применяется редко.
Что выбрать на практике
Если обобщить:
Для большинства сервисов подходит Decimal.
Для высоконагруженных систем и платёжной инфраструктуры часто используют хранение в минорных единицах как целое число.
Float и double лучше исключить полностью.
Всегда хранить валюту рядом с суммой.
Закладывать запас точности для накоплений и комиссий.
Деньги требуют аккуратности. Ошибка в типе данных редко проявляется сразу, но почти всегда проявляется в самый неудобный момент.
Если вы проектируете финансовый модуль, лучше потратить час на правильную модель хранения, чем потом неделю искать, куда исчезли копейки.