Ответ
Для работы с деньгами следует избегать типов с плавающей запятой (float32
, float64
) из-за их неточности в представлении десятичных дробей. Лучшими практиками являются:
1. int
(в минимальных единицах)
Хранить сумму в минимальных денежных единицах (копейках, центах). Это самый простой, быстрый и распространенный подход.
- Преимущества: Высокая производительность, простота, отсутствие проблем с точностью, возможность использовать в качестве ключа
map
. - Недостатки: Требуется ручное преобразование при отображении пользователю (деление на 100).
// 123 рубля 45 копеек хранятся как 12345
priceInKopecks := 12345
// Для отображения
fmt.Printf("Цена: %.2f руб.n", float64(priceInKopecks)/100.0)
2. Специализированные пакеты (decimal
)
Использовать библиотеки для работы с десятичной арифметикой с фиксированной точностью. Самая популярная — github.com/shopspring/decimal
.
- Преимущества: Гарантированная точность, удобные методы для арифметических операций (сложение, вычитание, округление).
- Недостатки: Ниже производительность по сравнению с
int
, добавляет внешнюю зависимость, экземплярыdecimal.Decimal
не являются сравниваемыми и не могут быть ключами вmap
.
import "github.com/shopspring/decimal"
price1, _ := decimal.NewFromString("100.50")
price2 := decimal.NewFromFloat(0.2)
// Точное сложение
total := price1.Add(price2) // "100.7"
Почему float
— плохой выбор?
Типы float
хранят числа в двоичном формате, который не может точно представить большинство десятичных дробей. Это приводит к ошибкам округления.
// Классический пример
fmt.Println(0.1 + 0.2) // Выведет: 0.30000000000000004
// При работе с деньгами это недопустимо
var price float64 = 100.50
var tax float64 = 0.21
fmt.Println(price * tax) // Выведет: 21.105000000000004