Em Produção · Backend · Fundamentos
0.1 + 0.2 e o salário de alguém
Na maratona de programação, um épsilon faz o bug sumir. Na folha de pagamento, um épsilon é uma pessoa que recebeu um centavo a menos.
Aqui está um programa que deveria imprimir true:
double phi = (1 + sqrt(5)) / 2;
printf(1 / phi == phi - 1 ? "true" : "false"); /* false */
A razão áurea satisfaz 1/φ = φ − 1 exatamente. O computador discorda na décima sexta casa decimal, porque os dois lados chegam ao mesmo número por arredondamentos diferentes. Na maratona de programação a correção é reflexo: comparar com tolerância, fabs(x - y) < 1e-9, e ir para o próximo problema. Fiz isso por anos.
Para entender o porquê, veja o que um double realmente guarda. 0,1 não tem forma binária exata, então a máquina guarda o valor mais próximo que consegue.
Carregando a figura interativa…
Versão estática: 0,1 é guardado como 0,1000000000000000055511151231257827…
Aí os números viraram salários. Folha de pagamento entre países é soma, taxa e arredondamento, milhões de vezes por mês, em moedas que nem concordam sobre quantas casas decimais têm: o iene não tem nenhuma, o dinar kuwaitiano tem três. Cada arredondamento cai na conta de alguém, e os totais precisam fechar até a última unidade. O cenário abaixo é genérico, não é o sistema de nenhuma empresa. Some o mesmo valor a um total, contracheque após contracheque.
Carregando a figura interativa…
Versão estática: 0,10 somado 1.000.000 de vezes em float dá 100000.00000133288; em centavos inteiros, exatamente 100.000,00.
O desvio é uma fração minúscula de centavo. Arredonde o total e ele até parece certo, e essa é a armadilha. A checagem de igualdade falha, o job de conciliação dispara um alarme e alguém coloca um épsilon para silenciar. Daí em diante o épsilon vira política.
Para onde os centavos vão de verdade
O dinheiro que some de verdade vem de outro lugar: aplicar uma taxa, um câmbio ou um reajuste e arredondar linha a linha.
Carregando a figura interativa…
Versão estática: com taxa 1,15, 2.376 de 100.000 linhas erram um centavo em float; com centavos inteiros, nenhuma.
Com taxa 1,15, 2.376 de 100.000 linhas arredondam para o centavo errado com ponto flutuante. Com centavos inteiros e taxa decimal, nenhuma. Um épsilon não resolve isso, porque não existe “perto o bastante” para um centavo pago à pessoa errada.
A correção é uma representação, não uma comparação:
- Guarde dinheiro como uma contagem inteira de unidades menores mais uma moeda, e tire o número de casas decimais da moeda (ISO 4217), nunca de um “2” global.
- Faça a conta de taxa num tipo decimal, escolha uma regra de arredondamento (half-up ou half-even), escreva, aplique uma vez num passo definido e teste por propriedade contra aritmética exata.
- Trate conciliação como igualdade. Se os totais só batem com épsilon, o bug está antes.