No PHP, 0.1 + 0.2 == 0.3 devolve false — e não é bug do PHP, é como todo computador guarda número decimal. Floats seguem o padrão IEEE 754, que armazena os números em binário, e alguns decimais simples (como 0.1) não têm representação exata em binário — do mesmo jeito que 1/3 não tem representação exata em decimal (0.3333… não termina nunca). O resultado é que 0.1 + 0.2 não é exatamente 0.3, e por isso a comparação direta falha. A regra que resolve: nunca compare floats com == direto — compare por tolerância, e para dinheiro nem use float. Vamos ao porquê e ao como.
O mecanismo: por que 0.1 não cabe em binário
Em decimal, você escreve frações como somas de potências de 10: 0.1 é 1/10. Em binário, o computador só tem potências de 2 (1/2, 1/4, 1/8, 1/16…). Alguns decimais caem certinho — 0.5 é 1/2, exato. Mas 0.1 em binário vira uma dízima infinita e periódica: 0.0001100110011001100… sem fim. Como o float (double de 64 bits) tem espaço finito, o PHP guarda a versão arredondada mais próxima — não o 0.1 exato.
Aí a conta acumula erro: o 0.1 guardado é um tiquinho maior, o 0.2 também, e a soma passa levemente de 0.3. O 0.3 guardado, por sua vez, é um tiquinho menor que 0.3. Dois valores minimamente diferentes → == retorna false. Não há bug: cada operação está fazendo exatamente o que o IEEE 754 manda.
Isso vale para qualquer linguagem que use IEEE 754 — JavaScript, Python, Java, C. Não é uma falha do PHP; é a física do ponto flutuante.
Diagnóstico: enxergue o valor de verdade
Aqui está a parte que mais confunde o iniciante — e o teste que esclarece tudo:
echo 0.1 + 0.2; // 0.3 ← parece certo!
var_dump(0.1 + 0.2 == 0.3); // bool(false) ← mas não é
Por que o echo mostra 0.3 se o valor não é 0.3? Porque o PHP tem uma configuração precision (padrão 14 dígitos) que arredonda o número só na hora de exibir. O valor guardado na memória continua "errado"; a tela é que mente para você. Para ver o número real, force mais casas com printf:
printf("%.17f\n", 0.1 + 0.2); // 0.30000000000000004
printf("%.17f\n", 0.3); // 0.29999999999999999
Agora dá para ver os dois valores lado a lado e entender o false. Sempre que um float "deveria bater" e não bate, imprima com %.17f — em segundos você confirma que é o erro de representação, não lógica sua.
A solução: comparar com tolerância (epsilon)
Como os dois valores diferem por uma migalha, a comparação correta não é "são idênticos?", e sim "a diferença entre eles é menor que uma margem desprezível?". Essa margem chama-se epsilon. O PHP já traz uma constante para isso, PHP_FLOAT_EPSILON (≈ 2.2e-16, o menor passo representável perto de 1.0):
$soma = 0.1 + 0.2;
if ($soma == 0.3) { // false — quase nunca é o que você quer
echo "igual";
}$soma = 0.1 + 0.2;
if (abs($soma - 0.3) < PHP_FLOAT_EPSILON) { // true
echo "igual (dentro da tolerância)";
}A ideia é sempre a mesma: abs($a - $b) < $tolerancia. O abs() (valor absoluto) garante que funciona independentemente de qual é maior.
Antecipando a próxima pergunta — e se os números forem grandes? PHP_FLOAT_EPSILON só serve para valores perto de 1.0. Floats perdem precisão conforme crescem, então para 1000000.1 + 1000000.2 a diferença real é bem maior que 2.2e-16 e a comparação daria false de novo. Nesses casos, escale a tolerância pela magnitude dos números (epsilon relativo):
function floatsIguais(float $a, float $b, float $epsilon = 1e-9): bool {
return abs($a - $b) <= $epsilon * max(1.0, abs($a), abs($b));
}
Para a maioria dos casos do dia a dia, um epsilon fixo pequeno como 1e-9 já resolve; use o relativo quando lidar com ordens de grandeza muito diferentes.
A armadilha central
Dinheiro: a exceção que não admite float nenhum
Para valores monetários, epsilon não basta — você não quer nenhum erro, nem de um centésimo de centavo, e ele se acumula a cada operação. A regra é: dinheiro nunca vai em float. Duas saídas:
1. Guarde em inteiros (centavos). Em vez de R$ 19,90 como 19.90, guarde 1990 (centavos). Inteiros são exatos e a aritmética não tem erro de representação; você só divide por 100 na hora de exibir. É a abordagem mais simples e usada em muito sistema de pagamento.
$precoEmCentavos = 1990; // R$ 19,90
$total = $precoEmCentavos * 3; // 5970 → exato
printf("R$ %.2f", $total / 100); // R$ 59,70 (divide só para mostrar)
2. Use BCMath para casas decimais arbitrárias. Quando precisa de frações (juros, câmbio, impostos), a extensão BCMath faz aritmética em base decimal, com a precisão que você pedir, operando sobre strings (para não passar pelo float em nenhum momento):
echo bcadd('0.1', '0.2', 1); // 0.3 ← exato, sem epsilon
echo bcmul('19.90', '3', 2); // 59.70
Onde vai cada abordagem
| Situação | Faça | Por quê |
|---|---|---|
| Comparar dois floats | abs($a - $b) < $epsilon |
== falha por erro de representação |
| Floats de magnitude grande/variada | Epsilon relativo (escala pelo maior) | Precisão do float cai quando o número cresce |
| Ver o valor "de verdade" | printf("%.17f", $x) |
O echo arredonda em precision e engana |
| Dinheiro (só somar/multiplicar) | Inteiros em centavos | Inteiro é exato, sem IEEE 754 |
| Dinheiro com frações (juros, câmbio) | BCMath sobre strings | Aritmética decimal com precisão definida |
O resumo cabe numa linha: float é ótimo para medidas e cálculos científicos onde uma migalha não importa; para igualdade use tolerância; para dinheiro, saia do float.
A página oficial dos números de ponto flutuante explica os limites de precisão do IEEE 754 e por que a comparação de igualdade exige cuidado — a base de tudo neste artigo.
As funções BCMath são o kit do PHP para aritmética decimal exata (dinheiro, juros, qualquer conta que não pode errar um centavo). Vale conhecer bcadd, bcmul, bccomp e a escala.