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.
Tabela de Conteúdo
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 de0.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 de0.1
e0.2
usandofloat
. - Com
bcadd
: Usando a funçãobcadd
, o resultado da soma de0.1
e0.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.