[Go e erros de arredondamento]

Quando criamos programas que vão lidar com dinheiro o ideal é sempre usar centavos, assim podemos ter variáveis do tipo inteiro e é bem mais simples prevenir erros causados pela forma como computadores armazenam números de ponto flutuante.

Mas nem sempre podemos escolher, quando consumimos APIs de terceiros, por exemplo, nem sempre as melhores praticas são observadas. Aliás, no dia a dia trabalhando condições ideais são uma raridade.

A pouco tempo passei por um problema como esse, uma API que meu sistema precisava consumir retornava um JSON e o campo com o valor além de ser string era também ponto flutuante.

Então, fiz a conversão mais simples e esqueci do ponto flutuante.

vf, _ := strconv.ParseFloat(value, 64)
vi := int(vf * 100.0)

O problema desse pequeno descuido é que acaba introduzindo um bug difícil de detectar porque não acontece para todos os valores. No meu código a string “1.15” após convertido acaba virando o inteiro 114 quando devia virar 115.

Felizmente o pacote math tem funções prontas para tratar esse tipo de problema de arredondamento, no caso usei math.Round e o problema foi resolvido.

vf, _ := strconv.ParseFloat(value, 64)
vi := int(math.Round(valueFloat * 100.0))

Este é um exemplo mais completo.

Exemplo

package main

import (
	"fmt"
	"math"
	"strconv"
)

func main() {
	value := "1.15"
	valueFloat, _ := strconv.ParseFloat(value, 64)

	valueCentsError := int(valueFloat * 100.0)
	valueCentsCorrect := int(math.Round(valueFloat * 100.0))

	fmt.Println("Valor original string......:", value)
	fmt.Println("Valor convertido para float:", valueFloat)
	fmt.Println("Valor em centavos (errado).:", valueCentsError)
	fmt.Println("Valor em centavos (correto):", valueCentsCorrect)

}

Código de exemplo

Os tratamentos de erro de parse foram removidos para não poluir o exemplo, mas quando é para valer nunca deixe de tratar nenhum erro.

Outra coisa que pode ser importante é que Go não garante necessariamente que o int será de 64 bits, no manual da linguagem diz que ele garante pelo menos 32 bits. Então em alguns casos pode ser interessante usar int64 ou mesmo uint64 se você tem certeza que o valor nunca será negativo.


Cesar Gimenes