O tipo Float
Perda de precisão
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ódica 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:
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 e passa a valer apenas 7.
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 com inteiros, depois volte a dividir por 10 elevando ao número de casas decimais. Por exemplo, o código acima ficaria assim:
Note que é muito complexo fazer isso. Uma alternativa mais simples é fazer contas com strings através da extensão BC. Esta extensão não utiliza o tipo float internamente para fazer as contas, ela trabalha diretamente com os números armazenados em uma string.
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ódica 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 e passa a valer apenas 7.
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 com inteiros, 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. Esta extensão não utiliza o tipo float internamente para fazer as contas, ela trabalha diretamente com os números armazenados em uma string.
$precisao = 1; $resultado = bcadd('0.7', '0.1', $precisao); $resultado = bcmul($resultado, '10', $precisao); echo (int)$resultado; // Exibe 8
Artigo muito didático. Excelente para iniciantes em programação entenderem sobre os tipos primitivos da linguagem.
Complementando as infos:
O tamanho máximo de um número de ponto flutuante é dependente de plataforma (S.0. e arquitetura), sendo o máximo de 1.8e308 com uma precisão de 14 decimais (número de 64 bits no formato IEEE).
Isto o torna quase que obrigatório quando se trabalha com números inteiros muito grandes.
Por exemplo, alguns desenvolvedores utilizam um campo Inteiro do MySQL para armazenar CNPJ (eu não aconselho) porém ele ultrapassa o maior valor para o Inteiro no PHP em sistemas de 32 bits. Nesse caso o Float pose ser usado tranquilamente pois seu limite é superior.
Notem que é extremamente importante estar ciente dos problemas de precisão, como foi salientado, principalmente para sistema que calculem taxa de juros por exemplo.
Além da extensão BC (que no Windows é ativada por padrão mas precisa ser ativada no Linux) tem também a extensão GMP que usa a biblioteca GNU MP. Esta última precisa ser ativada tanto em Windos quanto em Linux.
Complementando as infos:
O tamanho máximo de um número de ponto flutuante é dependente de plataforma (S.0. e arquitetura), sendo o máximo de 1.8e308 com uma precisão de 14 decimais (número de 64 bits no formato IEEE).
Isto o torna quase que obrigatório quando se trabalha com números inteiros muito grandes.
Por exemplo, alguns desenvolvedores utilizam um campo Inteiro do MySQL para armazenar CNPJ (eu não aconselho) porém ele ultrapassa o maior valor para o Inteiro no PHP em sistemas de 32 bits. Nesse caso o Float pose ser usado tranquilamente pois seu limite é superior.
echo sprintf("%f",99999999999999 + 9);
Além da extensão BC (que no Windows é ativada por padrão mas precisa ser ativada no Linux) tem também a extensão GMP que usa a biblioteca GNU MP. Esta última precisa ser ativada tanto em Windos quanto em Linux.
08/03/2010 2:49pm
(~14 anos atrás)
Muito bom artigo. Mostra como é importante conhecermos a fundo a linguagem PHP afim de desenvolvermos aplicações consistentes. A conversão automática de um tipo de variável para outro pode ocasionar erros de cálculos difíceis de identificar. Ficar a par destes detalhes nos ajuda a desenvolver com mais qualidade.
18/01/2010 9:19pm
(~15 anos atrás)
$x=14 ou 1110
$y=12 ou 1100
RESULTADOS
EXPRESSÃO BASE 2 BASE 10
~$x 0001 1 (Não entendi porque deu 1)
$x&$y 1100 12 (Não entendi porque deu 12)
$x|$y 1110 14 (Não entendi porque deu 14)
$x^$y 0010 2 (Não entendi porque deu 2)
$x>>2 0011 3 (Não entendi porque deu 3)
$x<<2 111000 56 (Não entendi porque deu 56)
Alguém pode me ajudar?