DEV Community

Cover image for Garantindo confiabilidade do calculo valores monetários
Jefferson Rodrigues for Lerian

Posted on

8 4 5 4 5

Garantindo confiabilidade do calculo valores monetários

Continuando a nossa saga de entender como são realizadas as transações financeiras, hoje vamos explorar um aspecto crítico no desenvolvimento de sistemas financeiros: como garantir a precisão nos cálculos de valores monetários.

Entendendo o que é precisão arbitrária

Um dos conceitos importantes quando falamos de transações financeiras é a precisão arbitrária, que permite que os cálculos matemáticos sejam realizados com exatidão absoluta, sem limitações de tamanho ou escala. Isso é especialmente crucial em sistemas financeiros, onde até mesmo pequenos erros de arredondamento podem resultar em discrepâncias significativas ao longo do tempo.

O problema da precisão

Em Go, se usarmos float64 para somar valores monetários, podemos obter resultados imprecisos devido à forma como números de ponto flutuante são representados na memória.

package main

import (
    "fmt"
)

func main() {
    a := 0.1
    b := 0.2
    fmt.Println(a + b) // 0.30000000000000004 ❌ (Erro de precisão)
}
Enter fullscreen mode Exit fullscreen mode

Por que esse erro acontece?

Números de ponto flutuante (float64) são representados internamente usando o padrão IEEE 754, que usa uma aproximação binária para armazenar valores decimais. No entanto, números como 0.1 e 0.2 não têm uma representação exata em binário, resultando em pequenas imprecisões ao somar.

Isso pode ser um grande problema em aplicações financeiras, pois pequenos erros podem se acumular ao longo de muitas transações.

Solução: Usando math/big para Precisão Arbitrária

Em Go, a biblioteca math/big oferece o tipo big.Float, que permite cálculos com precisão arbitrária:

package main

import (
    "fmt"
    "math/big"
)

func main() {
    a := big.NewFloat(0.1)
    b := big.NewFloat(0.2)
    result := new(big.Float).Add(a, b)

    fmt.Println(result.Text('f', 10)) // "0.3" ✅ (Precisão exata)
}
Enter fullscreen mode Exit fullscreen mode

Aqui, big.NewFloat(0.1) armazena o número com precisão arbitrária, evitando erros de arredondamento.

Armazenando centavos como inteiros

No nosso caso, dentro do Midaz, armazenamos todos os valores monetários como uma estrutura que combina um valor inteiro (Amount) e uma escala (Scale).

type Amount struct {
    Amount *int64 `json:"amount" example:"1500"`
    Scale  *int64 `json:"scale" example:"2"`
} 
Enter fullscreen mode Exit fullscreen mode

No código acima, o Amount representa o valor monetário multiplicado pela escala (10^n), e o Scale define o número de casas decimais. Por exemplo, para representar R$ 15,00, teríamos Amount = 1500 e Scale = 2. Esta estrutura nos permite trabalhar com valores monetários de forma precisa e flexível, adaptando-se a diferentes moedas e requisitos de precisão.

Esta abordagem é particularmente interessante para valores cuja precisão varia, como é o caso do Bitcoin, em que é necessário trabalhar com até 8 casas decimais. Para entender mais sobre o Amount e Scale, recomendo a leitura da nossa documentação.

E você, já teve que lidar com cálculos monetários com esse nível de precisão? Como foi sua experiência? Compartilhe suas experiências nos comentários abaixo!

AWS Q Developer image

Your AI Code Assistant

Implement features, document your code, or refactor your projects.
Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (0)