Какой тип данных в Go лучше всего подходит для хранения денежных сумм, и почему `float` является плохим выбором?

Ответ

Для работы с деньгами следует избегать типов с плавающей запятой (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