1. Introdução

A biblioteca Decimal Go é uma ferramenta poderosa para lidar com decimais de ponto fixo de precisão arbitrária na linguagem Go. Ela permite realizar operações de adição, subtração, multiplicação e divisão sem perder precisão. Além disso, fornece funcionalidades como serialização/desserialização para bancos de dados/SQL, bem como serialização/desserialização para JSON/XML.

2. Instalação

Para instalar a biblioteca Decimal Go, você pode utilizar o seguinte comando:

go get github.com/shopspring/decimal

Por favor, note que a biblioteca Decimal requer Go versão >=1.7.

3. Uso Básico

Para usar a biblioteca Decimal em um programa Go, importe o pacote "github.com/shopspring/decimal". Aqui está um exemplo simples demonstrando o uso básico:

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:", subtotal)                  // Subtotal: 408.06
	fmt.Println("Pré-imposto:", preTax)                 // Pré-imposto: 422.3421
	fmt.Println("Impostos:", total.Sub(preTax))         // Impostos: 37.482861375
	fmt.Println("Total:", total)                        // Total: 459.824961375
	fmt.Println("Taxa de imposto:", total.Sub(preTax).Div(preTax)) // Taxa de imposto: 0.08875
}

4. Criação de Variáveis Decimais

A biblioteca Decimal fornece vários métodos para criar variáveis Decimais. Abaixo estão as APIs suportadas:

  • 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. Operações Aritméticas

A biblioteca Decimal Go fornece diversas operações aritméticas que podem ser realizadas em variáveis Decimais. Aqui estão algumas operações suportadas:

  • Add(d2 Decimal) Decimal: Adiciona dois valores Decimais e retorna o resultado.
  • Sub(d2 Decimal) Decimal: Subtrai um valor Decimal de outro e retorna o resultado.
  • Div(d2 Decimal) Decimal: Divide um valor Decimal por outro e retorna o resultado.
  • DivRound(d2 Decimal, precision int32) Decimal: Divide um valor Decimal por outro e retorna o resultado com a precisão especificada.
  • Mod(d2 Decimal) Decimal: Calcula o módulo (restante) de um valor Decimal dividido por outro e retorna o resultado.
  • Mul(d2 Decimal) Decimal: Multiplica dois valores Decimais e retorna o resultado.

Você pode utilizar essas operações para realizar cálculos aritméticos comuns em valores Decimais. Aqui está um exemplo demonstrando o uso dessas operações:

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:", subtotal) // Subtotal: 408.06
fmt.Println("Imposto:", tax)        // Imposto: 36.244985
fmt.Println("Total:", total)        // Total: 444.304985

No exemplo acima, utilizamos o método Mul() para calcular o subtotal multiplicando price e quantity. Em seguida, calculamos o imposto multiplicando o subtotal pela taxa de imposto. Por fim, calculamos o total adicionando o subtotal e o imposto utilizando o método Add().

6. Operações de Arredondamento

A biblioteca Decimal do Go fornece diversas operações de arredondamento que podem ser usadas para arredondar valores decimais para uma precisão específica. Aqui estão algumas operações de arredondamento disponíveis:

  • Round(places int32) Decimal: Arredonda o decimal para o número especificado de casas decimais.
  • RoundBank(places int32) Decimal: Arredonda o decimal usando o arredondamento do banqueiro para o número especificado de casas decimais.
  • RoundCash(interval uint8) Decimal: Arredonda o decimal para um intervalo específico, como 5 centavos, 10 centavos, 25 centavos, 50 centavos ou 1 real.
  • RoundCeil(places int32) Decimal: Arredonda o decimal em direção ao infinito positivo.
  • RoundDown(places int32) Decimal: Arredonda o decimal em direção a zero.
  • RoundFloor(places int32) Decimal: Arredonda o decimal em direção ao infinito negativo.
  • RoundUp(places int32) Decimal: Arredonda o decimal para longe de zero.

6.1. Arredondar

A função Round arredonda o decimal para o número especificado de casas decimais. Se places < 0, arredonda a parte inteira para o múltiplo mais próximo de 10^(-places).

NewFromFloat(5.45).Round(1).String() // Saída: "5.5"
NewFromFloat(545).Round(-1).String() // Saída: "550"

6.2. Arredondar com Banqueiro

A função RoundBank arredonda o decimal para places casas decimais. Se a distância entre o último dígito a ser arredondado e os dois inteiros mais próximos for igual, o valor arredondado é o número par.

Se places < 0, a parte inteira será arredondada para o múltiplo mais próximo de 10^(-places).

NewFromFloat(5.45).RoundBank(1).String() // Saída: "5.4"
NewFromFloat(545).RoundBank(-1).String() // Saída: "540"
NewFromFloat(5.46).RoundBank(1).String() // Saída: "5.5"
NewFromFloat(546).RoundBank(-1).String() // Saída: "550"
NewFromFloat(5.55).RoundBank(1).String() // Saída: "5.6"
NewFromFloat(555).RoundBank(-1).String() // Saída: "560"

6.3. Arredondar para Cash

A função RoundCash (também conhecida como arredondamento de centavos) arredonda o decimal para intervalos específicos. O valor pagável de uma transação em dinheiro será arredondado para o múltiplo mais próximo da unidade monetária menor. Os intervalos disponíveis são: 5, 10, 25, 50 e 100; qualquer outro número resultará em uma exceção.

  5:   Arredondamento de 5 centavos 3.43 => 3.45
 10:  Arredondamento de 10 centavos 3.45 => 3.50 (5 é arredondado para cima)
 25:  Arredondamento de 25 centavos 3.41 => 3.50
 50:  Arredondamento de 50 centavos 3.75 => 4.00
100: Arredondamento de 100 centavos 3.50 => 4.00

6.4. Arredondar para Cima

A função RoundCeil arredonda o decimal em direção ao infinito positivo.

NewFromFloat(545).RoundCeil(-2).String()   // Saída: "600"
NewFromFloat(500).RoundCeil(-2).String()   // Saída: "500"
NewFromFloat(1.1001).RoundCeil(2).String() // Saída: "1.11"
NewFromFloat(-1.454).RoundCeil(1).String() // Saída: "-1.5"

6.5. Arredondar para Baixo

A função RoundDown arredonda o decimal em direção a zero.

NewFromFloat(545).RoundDown(-2).String()   // Saída: "500"
NewFromFloat(-500).RoundDown(-2).String()   // Saída: "-500"
NewFromFloat(1.1001).RoundDown(2).String() // Saída: "1.1"
NewFromFloat(-1.454).RoundDown(1).String() // Saída: "-1.5"

6.6. Arredondar para Baixo (RoundFloor)

O RoundFloor arredonda o decimal em direção ao infinito negativo.

NewFromFloat(545).RoundFloor(-2).String()   // Saída: "500"
NewFromFloat(-500).RoundFloor(-2).String()   // Saída: "-500"
NewFromFloat(1.1001).RoundFloor(2).String() // Saída: "1.1"
NewFromFloat(-1.454).RoundFloor(1).String() // Saída: "-1.4"

6.7. Arredondar para Cima (RoundUp)

O RoundUp arredonda o decimal para longe de zero.

NewFromFloat(545).RoundUp(-2).String()   // Saída: "600"
NewFromFloat(500).RoundUp(-2).String()   // Saída: "500"
NewFromFloat(1.1001).RoundUp(2).String() // Saída: "1.11"
NewFromFloat(-1.454).RoundUp(1).String() // Saída: "-1.4"

7. Converter Tipo Decimal para String

A biblioteca Decimal do Go fornece métodos para converter valores decimais em representações de string. Aqui estão alguns métodos disponíveis:

  • String(): string: Retorna a representação de string do número decimal com um ponto decimal fixo.
  • StringFixed(places int32) string: Retorna a representação de string arredondada com um número especificado de casas decimais.
  • StringFixedBank(places int32) string: Retorna a representação de string arredondada (arredondamento bancário) com um número especificado de casas decimais.

Você pode escolher o método apropriado de acordo com suas necessidades. Abaixo está um exemplo de conversão de um número decimal para uma string:

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

fmt.Println("Representação de string do número decimal:", str) // Representação de string do número decimal: 5.45

// Exemplo de StringFixed
NewFromFloat(0).StringFixed(2) // Saída: "0.00"
NewFromFloat(0).StringFixed(0) // Saída: "0"
NewFromFloat(5.45).StringFixed(0) // Saída: "5"
NewFromFloat(5.45).StringFixed(1) // Saída: "5.5"
NewFromFloat(5.45).StringFixed(2) // Saída: "5.45"
NewFromFloat(5.45).StringFixed(3) // Saída: "5.450"
NewFromFloat(545).StringFixed(-1) // Saída: "550"

// Exemplo de StringFixedBank
NewFromFloat(0).StringFixedBank(2) // Saída: "0.00"
NewFromFloat(0).StringFixedBank(0) // Saída: "0"
NewFromFloat(5.45).StringFixedBank(0) // Saída: "5"
NewFromFloat(5.45).StringFixedBank(1) // Saída: "5.4"
NewFromFloat(5.45).StringFixedBank(2) // Saída: "5.45"
NewFromFloat(5.45).StringFixedBank(3) // Saída: "5.450"
NewFromFloat(545).StringFixedBank(-1) // Saída: "540"

8. Perguntas Comuns

P: Por que não usar float64 diretamente? R: float64 não pode representar com precisão números como 0.1, o que pode levar a pequenos erros. Em situações envolvendo cálculos financeiros, esses erros podem se acumular ao longo do tempo e causar problemas significativos.

Q: Por que não usar big.Rat diretamente? A: Embora big.Rat possa representar números racionais, não é adequado para representar moeda. Números decimais são melhores para cálculos financeiros, pois podem representar com precisão frações decimais sem perder a precisão.

Q: Por que a API não é semelhante à big.Int? A: A API da biblioteca Decimal prioriza a usabilidade e a correção em relação ao desempenho. Enquanto a API de big.Int reduz alocações de memória por razões de desempenho, isso pode resultar em código complexo e propenso a erros. A API da biblioteca Decimal foi projetada para ser simples e fácil de entender.