Tipo Float no PHP

Artigo que explica como funciona o tipo float (double ou real) na linguagem PHP e algumas utilidades comuns, como arredondamento, formatação de números, e conversão entre tipos de dados.

O tipo float é um tipo de dado usado para representar números reais. Em PHP, assim como em várias outras linguagens, o tipo float possui limites, portanto podem ocorrer perdas de precisão. Em todo caso, vou falar sobre isso a seguir.


Representação de Números Reais

O tipo real, em função de sua aplicação prática, normalmente é representado na forma decimal de forma semelhante como a de um inteiro. A diferença é que no caso de um número real é utilizado o símbolo "." para separar os digitos do valor inteiro dos dígitos do valor das casas decimais. Alguns exemplos a seguir:

$f = 0.0;
$f = 1.0;
$f = 0.1;
$f = 0.0455;
$f = 123.0838;
$f = -239.134;

Observação: é possível omitir um dos lados do ponto. Caso o valor inteiro seja omitido, ele é considerado como zero. Caso as casas decimais sejam omitidas, é considerada como zero. Alguns exemplos a seguir:

$f = .2;  // $f = 0.2;
$f = 2.;  // $f = 2.0;
$f = -.3; // $f = -0.3;
$f = -3.; // $f = -3.0;

Em PHP, quando um valor numérico ultrapassa o maior inteiro permitido (ele pode ser descoberto verificando o valor da constante PHP_INT_MAX), ele é automaticamente considerado um número real. Portanto, é possível criar um número real através da notação octal ou hexadecimal, mas é preciso que o valor seja superior a PHP_INT_MAX, conforme o exemplo:

$f = 0xFFFFFFFF; // $f = 4294967295.0;

PHP oferece, ainda, uma forma de representar valores reais através de uma notação com expoente 10. Para isso, é incluído um sufixo em uma representação de número real (com notação decimal). Este sufixo é formado pela letra "e" (sem as aspas e insensível a caixa) seguido por um número inteiro positivo ou negativo, que indica o valor do expoente de 10. Esta notação é util para representar valores muito altos, muito baixos ou muito próximos de zero. Veja alguns exemplos:

$f = 5e3;  // 5 * 103 = 5 * 1000 = 5000
$f = -5E3; // -5 * 103 = -5 * 1000 = -5000
$f = 5e-3; // 5 * 10-3 = 5 * (1 / 103) = 5 * (1 / 1000) = 5 * 0,001 = 0,005
$f = -5e-3; // -5 * 10-3 = -5 * (1 / 103) = -5 * (1 / 1000) = -5 * 0,001 = -0,005

Conversão para Float

Int para Float

Um valor inteiro convertido para real apenas continua com o mesmo valor numérico, mas com a representação real.

Bool para Float

O valor true é convertido para o real 1.0, enquanto o valor false é convertido para o real 0.0.

Array para Float

Array vazios são convertidos para o real 0.0, enquanto os arrays não vazios são convertidos para o real 1.0.

String para Float

A conversão de uma string para float é semelhante a que ocorre para o tipo integer. Tenta-se obter a maior porção da string (a partir do primeiro caractere da esquerda) que represente uma forma de criar um valor real na forma decimal. Isso também vale para a notação com expoente 10. Caso nenhuma porção possa ser convertida, então o valor é avaliado como zero. Veja alguns exemplos:

$f = floatval("1.1a"); // $f = 1.1;
$f = floatval("1.a"); // $f = 1.0;
$f = floatval(".1a"); // $f = 0.1;
$f = floatval("5e2"); // $f = 500.0;
$f = floatval("1.1e1"); // $f = 11;
$f = floatval("1.1e01"); // $f = 11;
$f = floatval("-23.4e-1x"); // $f = -0.23;
$f = floatval("3 4 5"); // $f = 3.0;
$f = floatval("x1"); // $f = 0;

Object para Float

Objetos não podem ser convertidos para real.


Exibição de Números Reais

Nem todos os países usam a mesma notação que PHP usa para criar valores reais (com a notação de "." para indicar o separador de casas decimais). Para isso é preciso tratar o valor para que seja apresentado adequadamente. Existem duas formas básicas para se fazer isso. A primeira, é utilizando a função number_format, onde o programador informa qual o símbolo de casas decimais, o símbolo de milhar e o número de casas decimais:

$f = 2500.7;
echo number_format($f, 1, ',', '.'); // Exibe: 2.500,7
echo number_format($f, 2, ',', '.'); // Exibe: 2.500,70
echo number_format($f, 0, ',', '.'); // Exibe: 2.501

A segunda forma, é especificando a localidade através da função setlocale. Esta função modifica a forma como alguns valores são recuperados ou impressos. No caso de valores reais, seria usado da seguinte forma:

$f = 2500.7;

setlocale(LC_NUMERIC, 'pt_BR');
echo $f; // Exibe: 2500,7

setlocale(LC_NUMERIC, 'en');
echo $f; // Exibe: 2500.7

// Muda a localidade para o padrao
setlocale(LC_NUMERIC, 'C');

Perda de precisão de Números Reais

Ao trabalhar com valores reais, é preciso entender que ele armazena internamente um valor com limitação de casas decimais. Não há como representar uma dizima periótica com um campo do tipo float. Portanto, dividir 1 por 3 (0,333...) não terá o valor exato, a não ser que seja considerado uma quantidade limitada de casas decimais.

Veja um exemplo onde ocorre a perda de precisão:

$f = (0.1 + 0.7) * 10;
echo (int)$f;

O código acima exibe 7 ao invés de 8. Isso acontece porque 0.7 é armazenado internamente em uma variável float como sendo 0.69999999999999995559. Quando o valor é somado com 0.1, passa a valer 0.799999999999999 e, multiplicado por 10, passa a valer 7.99999999999999, que, convertido para inteiro, perde as casas decimais.

Uma alternativa é: se você sabe o número de casas decimais que envolvem a conta, multiplique por 10 elevado ao número de casas decimais, converta para inteiro, faça a conta desejada, depois volte a dividir por 10 elevando ao número de casas decimais. Por exemplo, o código acima ficaria assim:


// Considerando uma casa decimal
$op1 = 0.7;
$op2 = 0.1;

// Multiplicar por 10 elevando a 1, que eh 10
$op1 *= 10;
$op2 *= 10;

// Converter para inteiro
$op1 = intval(round($op1));
$op2 = intval(round($op2));

// Realizar a conta desejada
$resultado = ($op1 + $op2) * 10;

// Dividir por 10 elevando a 1, que eh 10
$resultado = $resultado / 10;

echo $resultado; // Exibe 8

Note que é muito complexo fazer isso. Uma alternativa mais simples é fazer contas com strings através da extensão BC.

$precisao = 1;
$resultado = bcadd('0.7', '0.1', $precisao);
$resultado = bcmul($resultado, '10', $precisao);
echo (int)$resultado; // Exibe 8

2 comentários

Sandri disse...

Tenho $nro1 = 70.456000; $nro2 = 70.480000; (ambos com 6 casas decimais)
Eu gostaria de exibir os nros ignorandos os zeros finais (a direita).....
ou seja, nro1 70,456 e nro 70,48. Tem como?

Rubens Takiguti Ribeiro (autor do blog) disse...

Sandri, não conheço algo nativo que faça isso. Mas você pode fazer usando estas funções:

rtrim(preg_replace('/0+$/', '', number_format($nro1, 6, ',', '.')), ',')