Pare de usar FLOAT pra dinheiro (antes que dê ruim no seu sistema)

Data:

!FLOAT
Compartilhar:
dev fullstack curso

Se você está usando float pra lidar com dinheiro em PHP, você está programando com uma bomba-relógio no seu sistema. Talvez tudo pareça funcionando bem agora, os valores batem, os testes passam, nada explode. Mas quando o volume de dados aumenta, quando somas acumuladas começam a acontecer, o erro aparece — e pior: aparece de forma silenciosa.

O problema é que float não é feito pra trabalhar com precisão. Ele trabalha com aproximações binárias, e isso é suficiente pra medidas, distâncias ou qualquer outra coisa onde uma margem de erro seja aceitável. Mas dinheiro não admite margem de erro. Um centavo de diferença em mil transações já é um rombo.

Esse tipo de erro passa batido por muita gente. No começo, parece uma diferença pequena, irrelevante. Mas em sistemas de produção, que movimentam valores reais, esses “detalhes” se acumulam. Quando você perceber, já está devolvendo troco errado, cobrando a mais ou registrando valores errados no banco de dados. E aí, meu amigo, não tem mais var_dump() que resolva.

Por que o float é traiçoeiro

O float engana fácil. Você olha pra ele somando 0.1 + 0.2 e espera ver 0.3. Parece óbvio, né? Mas não é isso que acontece. Na prática, você recebe algo como 0.30000000000000004. E aí você pensa: “mas é tão pouco, ninguém vai notar”. Só que esse pequeno erro é o começo do pesadelo.

O motivo disso está na forma como o computador armazena números de ponto flutuante. Números como 0.1 ou 0.2 não têm uma representação exata no sistema binário. Eles são aproximados. É como tentar escrever 1/3 em decimal: você vai acabar com 0.333... infinitamente. O computador faz algo parecido, só que em binário. E essas aproximações acumulam erro.

Quando você faz uma conta simples com esses valores, o resultado vem com um “lixo” que parece inofensivo, mas que quebra comparações, arredondamentos e pode corromper relatórios, balanços e registros financeiros. Se você está lidando com dinheiro, qualquer centavo fora do lugar já é motivo suficiente pra nunca confiar em float.

E não é só PHP. Essa limitação existe em praticamente todas as linguagens. A diferença é que algumas forçam você a usar soluções melhores. PHP, por padrão, deixa você usar float sem te dar aviso nenhum — e isso é o tipo de liberdade que cobra caro lá na frente.

Demonstração prática em PHP

Vamos sair da teoria e ver isso quebrando na prática. Olha esse código simples em PHP:

<?php

$produto1 = 0.1;
$produto2 = 0.2;

$total = $produto1 + $produto2;

echo "Total: R$ " . $total . PHP_EOL;

Você esperaria ver: Total: R$ 0.3
Mas o que aparece é: Total: R$ 0.30000000000000004

Agora imagina isso dentro de um loop somando vários produtos, aplicando impostos ou gerando boletos. A cada operação, você tá adicionando uma pequena imprecisão que, no final, vira uma diferença real no valor.

E o problema piora quando você tenta fazer uma comparação:

if ($total == 0.3) {
    echo "Tudo certo";
} else {
    echo "Deu ruim";
}

O resultado? Deu ruim. Mesmo o valor parecendo 0.3, na memória ele é outro número. Isso afeta condicionais, formatações, comparações de igualdade e até inserções no banco se você não tratar.

Esse tipo de erro é traiçoeiro justamente porque é sutil. Não dá erro de execução, não mostra warning, não quebra seu código na cara. Ele apenas entrega um resultado errado de forma silenciosa. E é por isso que confiar em float pra valores financeiros é pedir pra ser enganado pelo próprio código.

A forma certa de fazer

Agora que já mostramos o erro com float, é hora de ver como fazer a coisa certa. Existem duas abordagens bem mais seguras para lidar com dinheiro em PHP: uma usando a função bcadd e outra utilizando inteiros para representar centavos. Vamos ver as duas.

Usando bcadd para precisão

A função bcadd permite realizar cálculos com precisão decimal arbitrária, sem as imprecisões dos números de ponto flutuante. Veja como fica o código com bcadd:

<?php

$produto1 = "0.1";
$produto2 = "0.2";

// Usando bcadd para somar com precisão
$total = bcadd($produto1, $produto2, 2);

echo "Total com bcadd: R$ " . $total . PHP_EOL;

Saída:

Total com bcadd: R$ 0.30

Aqui, o valor é somado com a precisão que você espera: 0.30. Não há surpresa, não há erro.

Usando inteiros para centavos

Outra abordagem comum é trabalhar com centavos, ao invés de valores decimais. Isso significa transformar os valores monetários em inteiros, multiplicando por 100. Por exemplo, 0.1 vira 10 centavos, 0.2 vira 20 centavos, e assim por diante.

<?php

$produto1 = 10; // Representando 0.10 em centavos
$produto2 = 20; // Representando 0.20 em centavos

// Somando os centavos
$total = $produto1 + $produto2;

// Convertendo de volta para reais
$totalReais = $total / 100;

echo "Total com inteiros (centavos): R$ " . number_format($totalReais, 2) . PHP_EOL;

Saída:

Total com inteiros (centavos): R$ 0.30

Comparação clara das saídas

Agora, vamos comparar as duas abordagens:

  • Com bcadd, a precisão decimal é garantida. A função soma os valores como strings e dá o resultado exato.
  • Com inteiros (centavos), estamos evitando qualquer tipo de ponto flutuante. Todos os cálculos são feitos com inteiros, e ao final convertemos para o formato de reais, sem qualquer risco de erro de arredondamento.

Ambas as abordagens têm a vantagem de garantir a precisão dos cálculos. A escolha entre uma ou outra depende do contexto do seu sistema e da sua preferência, mas em sistemas de pagamento ou financeiros, é sempre bom garantir que você está lidando com a maior precisão possível.

Demonstração da diferença: float vs. bcadd

Agora vamos ver na prática como o uso de float pode gerar imprecisões ao trabalhar com valores monetários, enquanto bcadd garante a precisão necessária para cálculos financeiros.

O código abaixo mostra a diferença entre usar float e bcadd para somar os valores de 0.1 e 0.2. Com float, a precisão é comprometida, enquanto com bcadd, o cálculo é exato.

Código de demonstração

<?php
/**
 * Demonstração da diferença entre usar float e bcadd para representar dinheiro em PHP.
 * float => impreciso, não recomendado para valores monetários.
 * bcadd => preciso, ideal para cálculos financeiros.
 */

// --- Usando float (forma ERRADA para dinheiro) ---
$produtoFloat1 = 0.1;
$produtoFloat2 = 0.2;

// Soma usando float (ponto flutuante)
$calculoFloat = $produtoFloat1 + $produtoFloat2;

// Formatamos com 17 casas decimais para expor a imprecisão
$totalFloat = number_format($calculoFloat, 17);


// --- Usando bcadd (forma CORRETA para dinheiro) ---
$produtoBcadd1 = "0.1";
$produtoBcadd2 = "0.2";

// Soma usando bcadd, com precisão de 17 casas decimais
$totalBcadd = bcadd($produtoBcadd1, $produtoBcadd2, 17);


// --- Resultados ---
echo "Total Float (float):     R$ " . $totalFloat . PHP_EOL;
echo "Total Bcadd (preciso):   R$ " . $totalBcadd . PHP_EOL;

Explicação

  • Com float: O cálculo de 0.1 + 0.2 pode gerar um resultado impreciso, dependendo da forma como o número é armazenado na memória. No exemplo acima, ao formatar com 17 casas decimais, podemos observar o erro que surge com a soma de 0.1 e 0.2 usando float.
  • Com bcadd: Usando a função bcadd, o resultado da soma de 0.1 e 0.2 é exato. A precisão é mantida sem qualquer tipo de arredondamento ou erro imprevisto.

Saída esperada

Ao rodar o código acima, você verá a seguinte saída:

Total Float (float):     R$ 0.30000000000000004
Total Bcadd (preciso):   R$ 0.30000000000000000

O que podemos aprender?

  • Float: O valor com float é impreciso. Mesmo que o cálculo aparente ser simples, a representação interna do número não é exata. O erro, embora pequeno, é acumulativo e pode se tornar um problema sério em sistemas financeiros.
  • bcadd: A função bcadd resolve esse problema, garantindo que o cálculo seja feito com precisão. Não há erros de arredondamento, e os valores calculados são exatos.

Esse exemplo é um alerta para quem trabalha com valores monetários em PHP. Se você estiver lidando com dinheiro ou qualquer tipo de cálculo financeiro, evite float e utilize abordagens precisas como bcadd para garantir a integridade dos seus dados.

Quando usar cada abordagem

Agora que você já viu as duas formas de resolver o problema de precisão, é hora de entender quando usar cada uma delas. Ambas são eficientes, mas cada abordagem tem suas vantagens dependendo do tipo de sistema que você está desenvolvendo. Vamos ver os cenários ideais para cada uma.

bcadd: Ideal para precisão decimal sem conversão

A função bcadd é perfeita quando você precisa de precisão decimal e não quer se preocupar com conversões manuais de centavos. Se o seu sistema lida com valores que precisam ser somados, subtraídos ou multiplicados com exatidão — como em cálculos de juros compostos, descontos progressivos, ou qualquer operação matemática onde a precisão decimal seja fundamental — bcadd vai te atender muito bem.

A grande vantagem do bcadd é que você pode trabalhar diretamente com valores decimais, sem ter que multiplicar por 100 ou fazer transformações extras. É a abordagem mais direta quando você precisa de precisão decimal sem se complicar.

Quando usar bcadd:

  • Quando os valores são dados em formato decimal.
  • Quando você não quer se preocupar com conversões de centavos.
  • Quando a precisão do valor é fundamental (por exemplo, em juros, impostos ou cálculos de preço).

int: Ideal para sistemas que armazenam em centavos

Se o seu sistema armazena valores em centavos (o que é comum em sistemas de pagamento, e-commerce e bancos), trabalhar com inteiros é uma escolha sólida. Usando inteiros, você elimina qualquer risco de erro com ponto flutuante, já que os cálculos são feitos com números inteiros. A conversão de centavos para reais (dividindo por 100) é simples e eficiente, e o código se torna mais rápido, já que não há a necessidade de manipulação de strings ou operações de precisão decimal complexas.

Essa abordagem é ideal para sistemas que precisam armazenar valores monetários de maneira compacta e rápida, sem perder a precisão. Armazenar centavos como inteiros também facilita as operações de banco de dados, já que você não precisa lidar com números decimais ou com as limitações de armazenamento de tipos flutuantes.

Quando usar int:

  • Quando o sistema armazena os valores como centavos (inteiros).
  • Quando a performance é uma prioridade, pois operar com inteiros é mais rápido.
  • Quando a conversão para reais é simples e não há complexidade nas operações financeiras.

Resumo de bcadd x int

Em resumo, a escolha entre bcadd e inteiros depende do seu caso específico:

  • Se você está lidando com cálculos precisos em valores decimais, e não quer se preocupar com a conversão, bcadd é a escolha ideal.
  • Se você armazena valores como inteiros (centavos), e precisa de um sistema eficiente para cálculos rápidos e sem erros, a abordagem com inteiros é a melhor opção.

Ambas são soluções seguras, e o segredo é saber qual delas se encaixa melhor no seu modelo de dados e nas necessidades do seu sistema.

Conclusão

Nunca, sob nenhuma circunstância, use float para representar valores monetários. Embora pareça prático em um primeiro momento, a imprecisão dos números de ponto flutuante pode causar erros difíceis de detectar, mas com consequências reais em sistemas financeiros.

Em vez disso, use sempre inteiros ou a função bcadd, dependendo do contexto. Se o seu sistema trabalha com valores em centavos, o uso de inteiros é uma escolha simples e eficiente. Se você precisa de cálculos precisos com decimais, a função bcadd é a solução ideal.

Ambas as abordagens garantem que você tenha a precisão necessária e evita os problemas traiçoeiros do float. No fim das contas, a integridade dos seus cálculos financeiros depende de você escolher a forma correta de tratar os números.

foto de perfil brayan

Brayan

Bacharel em Sistemas de Informação pela Faculdade Maurício de Nassau e desenvolvedor de software. Produzo conteúdo e gerencio blogs. Sou especialista em desenvolvimento web e SEO de sites.

Deixe um comentário