💰 Деньги в Go без потери копеек — 26 июня 2026 г. в 07:00:08.686
💰 Деньги в Go без потери копеек Как работать с деньгами в Go Дробные типы в Go хранят значения в двоичном виде, и многие десятичные дроби в нём не представимы точно. Из-за этого простые суммы дают неожиданный результат, а ошибки копятся с каждой операцией. Ниже разберём, как считать деньги без потери копеек. ℹ️ Почему float64 не подходит Классический пример: fmt.Println(0.1 + 0.2) // 0.30000000000000004 На одной операции это незаметно, но в отчёте на тысячи позиций расхождение в копейки превращается в расхождение в рубли. Сравнивать такие суммы через == тоже нельзя, потому что равные на бумаге значения в памяти отличаются. 📎 Способ первый. Целые числа в минимальных единицах Самый надёжный приём хранить сумму в наименьшей единице валюты, то есть в копейках или центах, как int64. Целочисленная арифметика точна, сложение и вычитание не теряют ничего: type Money int64 // сумма в копейках func (m Money) String() string { sign := "" if m < 0 { sign = "-" m = -m } return fmt.Sprintf("%s%d.%02d", sign, m/100, m%100) } Цену 199 рублей 99 копеек вы храните как Money(19999), а метод String собирает её обратно в читаемый вид. Этого подхода хватает для подавляющего большинства задач с фиксированной валютой. 📎 Способ второй. Библиотека shopspring/decimal Целые числа неудобны там, где много делений и процентов, например при расчёте НДС или распределении суммы между участниками. Тут помогает пакет decimal, который хранит число как набор цифр с десятичной точкой и считает в десятичной системе: import "github.com/shopspring/decimal" price := decimal.RequireFromString("199.99") tax := price.Mul(decimal.RequireFromString("0.20")) total := price.Add(tax).Round(2) fmt.Println(total.StringFixed(2)) // 239.99 Обратите внимание, что значение задаётся строкой через RequireFromString, а не из float64. Так в число не попадёт двоичная погрешность ещё на входе. 📎 Округление и деление При делении суммы остаток никуда не девается, и его нужно осознанно куда-то отнести. decimal позволяет задать правило округления явно: total := decimal.RequireFromString("100.00") share := total.Div(decimal.NewFromInt(3)).Round(2) fmt.Println(share.StringFixed(2)) // 33.33 Сумма трёх таких долей даст 99.99, и одну копейку придётся добавить к одной из частей вручную. Это не баг библиотеки, а свойство самих денег, и решать его нужно по правилам вашей предметной области. Для денег в Go не используйте float64 и точка.

