1. Введение

Библиотека Go Decimal - это мощный инструмент для работы с десятичными числами произвольной точности в языке Go. С его помощью можно выполнять операции сложения, вычитания, умножения и деления без потери точности. Кроме того, она предоставляет функциональность, такую как сериализация/десериализация для баз данных/SQL, а также сериализация/десериализация JSON/XML.

2. Установка

Для установки библиотеки Go Decimal вы можете использовать следующую команду:

go get github.com/shopspring/decimal

Обратите внимание, что библиотека Decimal требует версию Go >=1.7.

3. Основное использование

Для использования библиотеки Decimal в программе на Go импортируйте пакет "github.com/shopspring/decimal". Вот простой пример кода, демонстрирующий основное использование:

package main

import (
	"fmt"
	"github.com/shopspring/decimal"
)

func main() {
	price, err := decimal.NewFromString("136.02")
	if err != nil {
		panic(err)
	}

	quantity := decimal.NewFromInt(3)

	fee, _ := decimal.NewFromString(".035")
	taxRate, _ := decimal.NewFromString(".08875")

	subtotal := price.Mul(quantity)
	preTax := subtotal.Mul(fee).Add(decimal.NewFromFloat(1))
	total := preTax.Mul(taxRate).Add(decimal.NewFromFloat(1))

	fmt.Println("Итого без налога:", subtotal)     // Итого без налога: 408.06
	fmt.Println("Перед налогом:", preTax)        // Перед налогом: 422.3421
	fmt.Println("Налоги:", total.Sub(preTax))    // Налоги: 37.482861375
	fmt.Println("Итого:", total)                 // Итого: 459.824961375
	fmt.Println("Налоговая ставка:", total.Sub(preTax).Div(preTax)) // Налоговая ставка: 0.08875
}

4. Создание переменных Decimal

Библиотека Decimal предоставляет различные методы для создания переменных Decimal. Вот поддерживаемые API:

  • decimal.NewFromBigInt(value *big.Int, exp int32) Decimal
  • decimal.NewFromFloat(value float64) Decimal
  • decimal.NewFromFloat32(value float32) Decimal
  • decimal.NewFromFloatWithExponent(value float64, exp int32) Decimal
  • decimal.NewFromFormattedString(value string, replRegexp *regexp.Regexp) (Decimal, error)
  • decimal.NewFromInt(value int64) Decimal
  • decimal.NewFromInt32(value int32) Decimal
  • decimal.NewFromString(value string) (Decimal, error)
  • decimal.RequireFromString(value string) Decimal

5. Арифметические операции

Библиотека Go Decimal предоставляет несколько арифметических операций, которые можно выполнять над переменными Decimal. Вот некоторые поддерживаемые операции:

  • Add(d2 Decimal) Decimal: Складывает два значения Decimal и возвращает результат.
  • Sub(d2 Decimal) Decimal: Вычитает одно значение Decimal из другого и возвращает результат.
  • Div(d2 Decimal) Decimal: Делит одно значение Decimal на другое и возвращает результат.
  • DivRound(d2 Decimal, precision int32) Decimal: Делит одно значение Decimal на другое и возвращает результат с указанной точностью.
  • Mod(d2 Decimal) Decimal: Вычисляет остаток от деления одного значения Decimal на другое и возвращает результат.
  • Mul(d2 Decimal) Decimal: Умножает два значения Decimal и возвращает результат.

Вы можете использовать эти операции для выполнения обычных арифметических расчетов с Decimal. Вот пример, демонстрирующий использование этих операций:

price, _ := decimal.NewFromString("136.02")
quantity := decimal.NewFromInt(3)

subtotal := price.Mul(quantity)
tax := subtotal.Mul(decimal.NewFromFloat(0.08875))

total := subtotal.Add(tax)

fmt.Println("Итого без налога:", subtotal) // Итого без налога: 408.06
fmt.Println("Налог:", tax)                // Налог: 36.244985
fmt.Println("Итого:", total)              // Итого: 444.304985

6. Операции округления

Библиотека Go Decimal предоставляет несколько операций округления, которые могут использоваться для округления десятичных значений до указанной точности. Вот некоторые доступные операции округления:

  • Round(places int32) Decimal: Округляет десятичное значение до указанного количества десятичных знаков.
  • RoundBank(places int32) Decimal: Округляет десятичное значение по правилам банковского округления до указанного количества десятичных знаков.
  • RoundCash(interval uint8) Decimal: Округляет десятичное значение до определенного интервала, такого как 5 центов, 10 центов, 25 центов, 50 центов или 1 доллар.
  • RoundCeil(places int32) Decimal: Округляет десятичное значение в сторону положительной бесконечности.
  • RoundDown(places int32) Decimal: Округляет десятичное значение в сторону нуля.
  • RoundFloor(places int32) Decimal: Округляет десятичное значение в сторону отрицательной бесконечности.
  • RoundUp(places int32) Decimal: Округляет десятичное значение в сторону отличную от нуля.

6.1. Round

Round округляет десятичное значение до указанного количества десятичных знаков. Если places < 0, то он округляет целую часть до ближайшего 10^(-places).

NewFromFloat(5.45).Round(1).String() // Вывод: "5.5"
NewFromFloat(545).Round(-1).String() // Вывод: "550"

6.2. RoundBank

RoundBank округляет десятичное значение до places десятичных знаков. Если расстояние между последней цифрой, которая должна быть округлена, и двумя ближайшими целыми числами одинаково, округленное значение принимает четное число.

Если places < 0, целая часть будет округлена до ближайшего 10^(-places).

NewFromFloat(5.45).RoundBank(1).String() // Вывод: "5.4"
NewFromFloat(545).RoundBank(-1).String() // Вывод: "540"
NewFromFloat(5.46).RoundBank(1).String() // Вывод: "5.5"
NewFromFloat(546).RoundBank(-1).String() // Вывод: "550"
NewFromFloat(5.55).RoundBank(1).String() // Вывод: "5.6"
NewFromFloat(555).RoundBank(-1).String() // Вывод: "560"

6.3. RoundCash

RoundCash (также известный как округление до копеек / центов) округляет десятичное значение до конкретных интервалов. Оплата наличными в ходе финансовой транзакции будет округлена до ближайшего кратного наименьшей денежной единицы. Доступные интервалы: 5, 10, 25, 50 и 100; любое другое число приведет к исключению.

  5:   Округление до 5 центов 3.43 => 3.45
 10:  Округление до 10 центов 3.45 => 3.50 (5 округляется вверх)
 25:  Округление до 25 центов 3.41 => 3.50
 50:  Округление до 50 центов 3.75 => 4.00
100: Округление до 100 центов 3.50 => 4.00

6.4. RoundCeil

RoundCeil округляет десятичное значение в сторону положительной бесконечности.

NewFromFloat(545).RoundCeil(-2).String()   // Вывод: "600"
NewFromFloat(500).RoundCeil(-2).String()   // Вывод: "500"
NewFromFloat(1.1001).RoundCeil(2).String() // Вывод: "1.11"
NewFromFloat(-1.454).RoundCeil(1).String() // Вывод: "-1.5"

6.5. RoundDown

RoundDown округляет десятичное значение в сторону нуля.

NewFromFloat(545).RoundDown(-2).String()   // Вывод: "500"
NewFromFloat(-500).RoundDown(-2).String()   // Вывод: "-500"
NewFromFloat(1.1001).RoundDown(2).String() // Вывод: "1.1"
NewFromFloat(-1.454).RoundDown(1).String() // Вывод: "-1.5"

6.6. RoundFloor

RoundFloor округляет десятичное число в сторону отрицательной бесконечности.

NewFromFloat(545).RoundFloor(-2).String()   // Вывод: "500"
NewFromFloat(-500).RoundFloor(-2).String()   // Вывод: "-500"
NewFromFloat(1.1001).RoundFloor(2).String() // Вывод: "1.1"
NewFromFloat(-1.454).RoundFloor(1).String() // Вывод: "-1.4"

6.7. RoundUp

RoundUp округляет десятичное число от нуля.

NewFromFloat(545).RoundUp(-2).String()   // Вывод: "600"
NewFromFloat(500).RoundUp(-2).String()   // Вывод: "500"
NewFromFloat(1.1001).RoundUp(2).String() // Вывод: "1.11"
NewFromFloat(-1.454).RoundUp(1).String() // Вывод: "-1.4"

7. Преобразование десятичного типа в строку

Библиотека Go Decimal предоставляет методы для преобразования десятичных значений в строковое представление. Вот некоторые доступные методы:

  • String(): string: Возвращает строковое представление десятичного числа с фиксированной десятичной точкой.
  • StringFixed(places int32) string: Возвращает округленное строковое представление с указанным количеством десятичных знаков.
  • StringFixedBank(places int32) string: Возвращает округленное (банковское округление) строковое представление с указанным количеством десятичных знаков.

Вы можете выбрать подходящий метод в соответствии с вашими потребностями. Ниже приведен пример преобразования десятичного числа в строку:

d := decimal.NewFromFloat(5.45)
str := d.String()

fmt.Println("Строковое представление десятичного числа:", str) // Строковое представление десятичного числа: 5.45

// Пример StringFixed
NewFromFloat(0).StringFixed(2) // Вывод: "0.00"
NewFromFloat(0).StringFixed(0) // Вывод: "0"
NewFromFloat(5.45).StringFixed(0) // Вывод: "5"
NewFromFloat(5.45).StringFixed(1) // Вывод: "5.5"
NewFromFloat(5.45).StringFixed(2) // Вывод: "5.45"
NewFromFloat(5.45).StringFixed(3) // Вывод: "5.450"
NewFromFloat(545).StringFixed(-1) // Вывод: "550"

// Пример StringFixedBank
NewFromFloat(0).StringFixedBank(2) // Вывод: "0.00"
NewFromFloat(0).StringFixedBank(0) // Вывод: "0"
NewFromFloat(5.45).StringFixedBank(0) // Вывод: "5"
NewFromFloat(5.45).StringFixedBank(1) // Вывод: "5.4"
NewFromFloat(5.45).StringFixedBank(2) // Вывод: "5.45"
NewFromFloat(5.45).StringFixedBank(3) // Вывод: "5.450"
NewFromFloat(545).StringFixedBank(-1) // Вывод: "540"

8. Общие вопросы

Q: Почему нельзя использовать float64 напрямую? A: float64 не может точно представлять числа, такие как 0.1, что может привести к небольшим ошибкам. В ситуациях, связанных с финансовыми расчетами, эти ошибки могут накапливаться со временем и вызывать значительные проблемы.

Q: Почему нельзя использовать big.Rat напрямую? A: Хотя big.Rat может представлять рациональные числа, он не подходит для представления валюты. Для финансовых расчетов лучше использовать десятичные числа, так как они могут точно представлять десятичные дроби без потери точности.

Q: Почему API не похож на big.Int? A: API библиотеки Decimal отдает предпочтение удобству использования и корректности перед производительностью. В то время как API big.Int уменьшает выделение памяти в целях повышения производительности, это может привести к сложному и подверженному ошибкам коду. API библиотеки Decimal разработан так, чтобы быть простым и понятным.