PHP int e float

A lojinha da Ana vive de números: quantos cadernos tem no estoque, quanto custa cada um, quanto o cliente vai pagar. O PHP tem dois tipos de número: int, para inteiros (3 cadernos), e float, para números com casas decimais (R$ 12,90). Nesta aula você aprende a escrever os dois, o que acontece quando um número fica grande demais, por que 0.1 + 0.2 não dá exatamente 0.3 e o que o PHP faz quando alguém tenta dividir por zero.

Você vai aprender
  • A diferença entre int e float, e quando cada um aparece.
  • O maior inteiro do PHP (PHP_INT_MAX) e o que acontece depois dele.
  • Por que floats são "quase exatos" e como lidar com isso (round).
  • Outras formas de escrever números: hexadecimal, octal, binário e 1_000_000.
  • As três divisões: /, intdiv e % (o resto), e o fdiv.
  • Como testar se algo é número: is_int, is_float, is_numeric.
  • O erro DivisionByZeroError.

Os programas desta aula rodaram com o PHP 8.5 (64 bits) na máquina fictícia da Ana, na pasta ~/site. As contas em si (+, -, *, **, incremento) estão em Operadores Aritméticos.


A ideia: contar e medir

Pense em duas perguntas do dia a dia: "quantos ovos tem na caixa?" e "quanto pesa esse queijo?". Ovos você conta: 1, 2, 12, nunca 2,7 ovos. O queijo você mede: 0,350 kg, 1,2 kg.

  • int (de integer, "inteiro") é para contar: quantidade, idade, ano, número de páginas. Sem casas decimais.
  • float é para medir: peso, altura, média, porcentagem. Tem casas decimais.
<?php
$ovos = 12;
$queijo = 0.35;
$inteiroComPonto = 10.0;
var_dump($ovos, $queijo, $inteiroComPonto, 5 + 2.0);
$ php tipos.php
int(12)
float(0.35)
float(10)               « 10.0 tem ponto: é float, mesmo sem parte decimal
float(7)                « int + float dá float

Duas regras importantes: o separador decimal é o ponto (0.35, nunca 0,35), e basta um float na conta para o resultado ser float. Para mostrar 0,35 com vírgula ao usuário, use number_format (aula Formatar Números).


1. int: números inteiros

Inteiros podem ser positivos, negativos ou zero: 29, -5, 0. Mas não são infinitos: o PHP guarda cada inteiro em 8 bytes (64 bits), o que dá um limite. O PHP oferece constantes (valores prontos, com nome em maiúsculas; aula Constantes) para você consultar esse limite:

<?php
var_dump(PHP_INT_MAX);
var_dump(PHP_INT_MIN);
var_dump(PHP_INT_SIZE);
$ php limites.php
int(9223372036854775807)        « o maior int: uns 9,2 quintilhões
int(-9223372036854775808)       « o menor int
int(8)                          « cada int ocupa 8 bytes

É um número enorme: dá para contar os segundos de quase 300 bilhões de anos. Na prática, você raramente encosta nele. Mas o que acontece se passar?

Passou do limite: o int vira float

<?php
var_dump(PHP_INT_MAX + 1);
var_dump(PHP_INT_MAX * 2);
var_dump(is_float(PHP_INT_MAX + 1));
$ php estouro.php
float(9.223372036854776E+18)    « não deu erro: virou float
float(1.8446744073709552E+19)
bool(true)

Em muitas linguagens, passar do maior inteiro faz o número "dar a volta" e virar negativo (o overflow, "transbordamento"). O PHP faz diferente: converte o resultado para float, sem aviso. O E+18 é a notação científica: 9.22...E+18 quer dizer 9,22 × 1018.

O preço disso: um float tem cerca de 15 a 17 dígitos significativos, então os últimos algarismos do número se perdem. Para contas com números gigantes e exatos, o PHP tem as extensões bcmath e gmp, assunto avançado.


2. Outras formas de escrever um inteiro

Além do jeito normal (decimal), o PHP aceita outras bases numéricas. Você vai encontrá-las em cores (0xFF), permissões de arquivo (0o755) e configurações:

NotaçãoComeça comExemploVale (decimal)
Decimal1–92626
Hexadecimal (base 16)0x0x1A26
Octal (base 8) PHP 8.1+0o0o1715
Octal (forma antiga)001715
Binário (base 2)0b0b101010
Com separador PHP 7.4+—1_000_0001000000
Notação científica (float)—1.5e3, 2E-31500.0, 0.002
<?php
var_dump(0x1A, 0o17, 017, 0b1010, 1_000_000, 1.5e3, 2E-3);
$ php notacoes.php
int(26)
int(15)
int(15)
int(10)
int(1000000)
float(1500)
float(0.002)

Repare: o PHP guarda e mostra tudo em decimal. A notação é só o jeito de escrever no código. O sublinhado em 1_000_000 serve apenas para os olhos (como o ponto em "1.000.000" no português) e some.

Zero à esquerda muda o número!

Por causa da forma antiga de octal, 017 não é dezessete: é quinze. Nunca escreva zeros à esquerda em números (como num CEP ou num código de produto). Se o "número" tem zeros que importam, ele é texto: "01310-100". Um 08 ou 09 nem é aceito, porque 8 e 9 não existem na base 8:

$ cat zero-esquerda.php
<?php
var_dump(08);
$ php zero-esquerda.php

Parse error: Invalid numeric literal in /home/ana/site/zero-esquerda.php on line 2

Invalid numeric literal = "número escrito de forma inválida". Prefira sempre 0o17 quando quiser octal: é impossível confundir.


3. float: números com casas decimais

Qualquer número com ponto ou com e (notação científica) é um float: 4.5, -0.25, 10.0, 1.5e3. O nome vem de floating point ("ponto flutuante"): o computador guarda os algarismos e, separado, a posição do ponto, que pode "flutuar". Isso permite guardar números muito grandes e muito pequenos, mas com uma limitação.

A surpresa: 0.1 + 0.2

<?php
var_dump(0.1 + 0.2);
var_dump(0.1 + 0.2 == 0.3);
echo 0.1 + 0.2, "\n";
$ php precisao.php
float(0.30000000000000004)     « não é exatamente 0.3!
bool(false)                    « por isso a comparação dá falso
0.3                            « o echo arredonda para mostrar e esconde o problema

Não é um defeito do PHP: acontece em JavaScript, Python, Java, em praticamente toda linguagem. O computador guarda os floats em binário (base 2), e alguns números simples em decimal, como 0,1, viram dízimas infinitas em binário, do mesmo jeito que 1/3 vira 0,3333… em decimal. O computador guarda um valor muito próximo, mas não exato. Somando dois "quase", sobra um errinho na 17ª casa.

Repare também na diferença entre var_dump e echo: o var_dump mostra o valor com todos os dígitos necessários; o echo mostra no máximo 14 algarismos significativos, e por isso exibe 0.3. A configuração precision do PHP controla isso. Não confie no echo para investigar floats.

O errinho pode virar um erro grande quando o float é cortado para inteiro:

<?php
var_dump((0.1 + 0.7) * 10);
var_dump((int) ((0.1 + 0.7) * 10));
$ php corte.php
float(7.999999999999999)
int(7)                         « era para ser 8! o (int) corta, não arredonda

(O (int) converte para inteiro jogando fora a parte decimal; veja Conversão de Tipos.)

Como conviver com isso

SituaçãoO que fazer
Mostrar um resultadoArredonde: round($x, 2) ou formate com number_format.
Comparar dois floatsArredonde os dois antes (round($a, 2) == round($b, 2)) ou veja se a diferença é minúscula: abs($a - $b) < PHP_FLOAT_EPSILON.
DinheiroGuarde em centavos, como int: R$ 12,90 vira 1290. Some e multiplique inteiros e só divida por 100 na hora de mostrar.
Converter para inteiroArredonde primeiro: (int) round($x).

Arredondar: round, floor, ceil

<?php
var_dump(round(0.1 + 0.2, 2));
var_dump(round(0.1 + 0.2, 2) == 0.3);
var_dump(round(3.14159, 2));
var_dump(round(2.5));
var_dump(round(-2.5));
var_dump(round(1234.5678, -2));
var_dump(floor(4.9));
var_dump(ceil(4.1));
$ php arredondar.php
float(0.3)
bool(true)                     « depois de arredondar, a comparação funciona
float(3.14)                    « 2 casas decimais
float(3)                       « .5 arredonda para longe do zero…
float(-3)                      « …nos dois sentidos
float(1200)                    « casas negativas: arredonda para centenas
float(4)                       « floor ("chão"): sempre para baixo
float(5)                       « ceil ("teto"): sempre para cima
round($x, casas)
Arredonda para o número de casas pedido (o padrão é 0). O meio (.5) vai para longe do zero, como na escola.
floor($x)
"Chão": o maior inteiro menor ou igual (4.9 → 4).
ceil($x)
"Teto" (de ceiling): o menor inteiro maior ou igual (4.1 → 5). Útil para "quantas caixas preciso?".

As três devolvem float, mesmo quando o resultado é redondo. Se precisar de int, converta: (int) round($x).

Experimente: troque 0.1 e 0.2 por outros valores (0.5 + 0.25, 1.1 + 2.2, 19.99 * 3). Quais dão um resultado "torto" no var_dump? O round conserta?

<?php
$a = 0.1;
$b = 0.2;
$soma = $a + $b;
var_dump($soma);
echo $soma, "\n";
var_dump(round($soma, 2));

4. Dividir: /, intdiv, % e fdiv

A Ana tem 17 balas para dividir entre 5 crianças. Dependendo da pergunta, a resposta é diferente:

  • "Quanto dá 17 dividido por 5?" → 3,4 (a divisão comum, /).
  • "Quantas balas inteiras cada criança recebe?" → 3 (a divisão inteira, intdiv).
  • "Quantas sobram?" → 2 (o resto, %).
<?php
var_dump(17 / 5);
var_dump(intdiv(17, 5));
var_dump(17 % 5);
var_dump(10 / 2);
var_dump(10 / 3);
echo 10 / 3, "\n";
$ php dividir.php
float(3.4)
int(3)                         « intdiv: só a parte inteira
int(2)                         « %: o resto (17 = 5 × 3 + 2)
int(5)                         « divisão exata entre ints dá int…
float(3.3333333333333335)      « …senão, float
3.3333333333333                « o echo mostra só 14 algarismos
$a / $b
Divisão comum. Dá int se a conta for exata entre inteiros; senão, float.
intdiv($a, $b)
"Divisão inteira": só a parte inteira do resultado, sempre int. Só aceita inteiros.
$a % $b
O "módulo": o resto da divisão inteira. Clássico para saber se um número é par ($n % 2 == 0). Para floats, use fmod(10.5, 3) (dá 1.5).

O sinal do resto segue o primeiro número: -10 % 3 dá -1, e 10 % -3 dá 1. Mais detalhes em Operadores Aritméticos.

Missão: complete o programa usando intdiv e % com as variáveis (não digite 3 e 2 à mão) para mostrar Cada criança: 3 e Sobram: 2. Depois teste com outros valores de $balas.

<?php
$balas = 17;
$criancas = 5;
$cada = 0;    // calcule aqui
$sobra = 0;   // e aqui
echo "Cada criança: $cada\n";
echo "Sobram: $sobra\n";
<?php
$balas = 17;
$criancas = 5;
$cada = intdiv($balas, $criancas);
$sobra = $balas % $criancas;
echo "Cada criança: $cada\n";
echo "Sobram: $sobra\n";

5. Dividir por zero

Na matemática, dividir por zero não tem resposta. No PHP 8, é um erro que para o programa:

<?php
echo "Antes\n";
echo 10 / 0;
echo "Depois\n";
$ php zero.php
Antes

Fatal error: Uncaught DivisionByZeroError: Division by zero in /home/ana/site/zero.php:3
Stack trace:
#0 {main}
  thrown in /home/ana/site/zero.php on line 3
$ echo $?
255                            « o "Depois" nunca apareceu
Fatal error
Erro fatal: o programa parou aqui.
Uncaught
"Não capturado". O PHP lançou um erro e ninguém o tratou. É possível tratar com try/catch (aula Exceções).
DivisionByZeroError
O nome do erro: "erro de divisão por zero".
Division by zero
A mensagem.
zero.php:3 … on line 3
Arquivo e linha.
Stack trace
"Rastro da pilha": o caminho de chamadas até o erro. Aqui, só o programa principal ({main}).

O resto e a divisão inteira também dão o mesmo erro, com mensagens um pouco diferentes:

$ php resto-zero.php           « echo 10 % 0;

Fatal error: Uncaught DivisionByZeroError: Modulo by zero in /home/ana/site/resto-zero.php:2
Stack trace:
#0 {main}
  thrown in /home/ana/site/resto-zero.php on line 2
$ php intdiv-zero.php          « echo intdiv(10, 0);

Fatal error: Uncaught DivisionByZeroError: Division by zero in /home/ana/site/intdiv-zero.php:2
Stack trace:
#0 /home/ana/site/intdiv-zero.php(2): intdiv(10, 0)
#1 {main}
  thrown in /home/ana/site/intdiv-zero.php on line 2

A forma de evitar é conferir o divisor antes, com um if: "se o número de crianças for zero, avise; senão, divida".

fdiv: a divisão que não dá erro PHP 8.0+

Para cálculos científicos, o PHP tem fdiv (de float division), que segue a regra dos floats: dividir por zero dá "infinito" (INF) ou "não é um número" (NAN):

<?php
var_dump(fdiv(10, 0));
var_dump(fdiv(-1, 0));
var_dump(fdiv(0, 0));
$ php fdiv.php
float(INF)                     « infinito
float(-INF)                    « menos infinito
float(NAN)                     « Not A Number: "não é um número"

Num sistema de loja, você quase nunca quer INF como preço. Prefira conferir o divisor.


6. É número? is_int, is_float, is_numeric

<?php
var_dump(is_int(5));
var_dump(is_int(5.0));
var_dump(is_float(5.0));
var_dump(is_numeric("12"));
var_dump(is_numeric("12.5"));
var_dump(is_numeric("1e3"));
var_dump(is_numeric(" 12"));
var_dump(is_numeric("12 "));
var_dump(is_numeric("12abc"));
var_dump(is_numeric("0x1A"));
var_dump(is_numeric(""));
var_dump(is_numeric("1_000"));
$ php testar.php
bool(true)
bool(false)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(false)
bool(false)
bool(false)
bool(false)
TesteResultadoPor quê
is_int(5)true5 é int
is_int(5.0)false5.0 é float, mesmo "redondo"
is_float(5.0)true
is_numeric("12"), ("12.5"), ("1e3")truetexto com cara de número
is_numeric(" 12"), ("12 ")trueespaços nas pontas são tolerados
is_numeric("12abc")falsetem letras depois
is_numeric("0x1A"), ("1_000")falseas notações do código não valem em texto
is_numeric("")falsetexto vazio não é número

O is_numeric é o teste certo para o que vem de formulários, porque tudo chega como texto. Para validar com mais rigor ("um inteiro entre 1 e 120"), existe o filter_var, em Validar Dados.


Quando dá errado

Fatal error: Uncaught DivisionByZeroError: Division by zero

O que significa: o divisor (o número depois do /, do % ou o segundo do intdiv) valia zero. Muitas vezes é uma variável vazia: uma Undefined variable ou um campo de formulário em branco vira zero numa conta.

Como consertar: mostre o divisor com var_dump antes da linha. Confira com if antes de dividir.

Parse error: syntax error, unexpected token ","
$ cat virgula.php
<?php
$preco = 4,5;
$ php virgula.php

Parse error: syntax error, unexpected token "," in /home/ana/site/virgula.php on line 2

O que significa: você escreveu um número com vírgula. Para o PHP, a vírgula separa coisas, não casas decimais.

Como consertar: use ponto: $preco = 4.5;.

A comparação de floats dá false sem motivo

O que significa: é a imprecisão dos floats: 0.1 + 0.2 == 0.3 é false.

Como consertar: arredonde antes de comparar (round($x, 2) == 0.3) ou trabalhe com inteiros (centavos).

O número "perdeu" uma unidade ao virar int

O que significa: (int) e intval cortam a parte decimal. Se a conta deu 7.999999999999999, o corte dá 7.

Como consertar: (int) round($x).


Referência rápida

NomeO que é / fazExemplo → resultado
PHP_INT_MAX, PHP_INT_MINMaior e menor int9223372036854775807
PHP_FLOAT_EPSILONMenor diferença entre floats perto de 12.220446049250313E-16
round($x, $casas)Arredondaround(3.14159, 2) → 3.14
floor($x) / ceil($x)Para baixo / para cimafloor(4.9) → 4
$a / $bDivisão17 / 5 → 3.4
intdiv($a, $b)Divisão inteiraintdiv(17, 5) → 3
$a % $bResto (inteiros)17 % 5 → 2
fmod($a, $b)Resto (floats)fmod(10.5, 3) → 1.5
fdiv($a, $b)Divisão sem erro no zerofdiv(10, 0) → INF
is_int, is_floatTesta o tipois_int(5.0) → false
is_numeric($x)Número ou texto numéricois_numeric("12.5") → true

Exercícios

1. O resto

Complete para mostrar o resto de 20 dividido por 6 (que é 2):

echo 20 6;

2. Duas casas

Complete para arredondar $media com 2 casas decimais:

echo ($media, 2);

3. Qual é o tipo de 10 / 4?

4. Quanto vale 017 no PHP?

5. O que acontece com PHP_INT_MAX + 1?

Missão final: a média da turma

Missão: o programa calcula a média de três notas, mas mostra Média: 8.2333333333333. Use round para mostrar a média com uma casa decimal: Média: 8.2.

<?php
$nota1 = 7.5;
$nota2 = 8;
$nota3 = 9.2;
$media = ($nota1 + $nota2 + $nota3) / 3;
echo "Média: $media\n";
<?php
$nota1 = 7.5;
$nota2 = 8;
$nota3 = 9.2;
$media = round(($nota1 + $nota2 + $nota3) / 3, 1);
echo "Média: $media\n";

Resumo

  • int conta (sem casas decimais); float mede (com ponto). Um float na conta torna o resultado float.
  • O maior int é PHP_INT_MAX; passar dele transforma o resultado em float, sem aviso.
  • Inteiros podem ser escritos em hexadecimal (0x), octal (0o), binário (0b) e com _ como separador. Cuidado com zeros à esquerda.
  • Floats são aproximados: 0.1 + 0.2 dá 0.30000000000000004. Arredonde com round, compare com cuidado e guarde dinheiro em centavos.
  • / divide; intdiv dá a parte inteira; % dá o resto; fdiv divide sem erro no zero.
  • Dividir por zero com /, % ou intdiv dá DivisionByZeroError e para o programa.
  • is_int e is_float testam o tipo; is_numeric aceita texto numérico.