Pergunta

Uma vez que o tipo de dados flutuante em PHP é impreciso, e uma bóia em MySQL ocupa mais espaço do que um INT (e é impreciso), eu sempre armazenar preços como INTs, multipling por 100 antes de armazenar para garantir que temos exatamente 2 decimal locais de precisão. No entanto, eu acredito que PHP está se comportando mal. Código Exemplo:

echo "<pre>";

$price = "1.15";
echo "Price = ";
var_dump($price);

$price_corrected = $price*100;
echo "Corrected price = ";
var_dump($price_corrected);


$price_int = intval(floor($price_corrected));
echo "Integer price = ";
var_dump($price_int);

echo "</pre>";

saída Produzido:

Price = string(4) "1.15"
Corrected price = float(115)
Integer price = int(114)

Eu fiquei surpreso. Quando o resultado final foi inferior ao esperado por 1, eu estava esperando a saída do meu teste para olhar mais como:

Price = string(4) "1.15"
Corrected price = float(114.999999999)
Integer price = int(114)

o que demonstraria a imprecisão do tipo float. Mas porque é andar (115) retornando 114 ??

Foi útil?

Solução

Tente isto como uma solução rápida:

$price_int = intval(floor($price_corrected + 0.5));

O problema que você está enfrentando não é culpa de PHP, todas as linguagens de programação que usam números reais com aritmética de ponto flutuante têm problemas semelhantes.

A regra geral para cálculos monetárias é nunca usar floats (nem no banco de dados nem em seu script). Você pode evitar todos os tipos de problemas, sempre armazenar os centavos em vez de dólares. Os centavos são inteiros, e você pode adicionar livremente los juntos, e multiplicar por outros inteiros. Sempre que você exibir o número, certifique-se de inserir um ponto na frente dos dois últimos dígitos.

A razão pela qual você está recebendo 114 em vez de 115 é que as rodadas floor para baixo, para o número inteiro mais próximo, assim andar (114,999999999) torna-se 114. A questão mais interessante é por que 1,15 * 100 é 114,999999999 em vez de 115. A razão para que é que 1,15 não é exatamente 115/100, mas é um pouco menos, então se você multiplicar por 100, você recebe um número um pouco menor do que 115.

Aqui está uma explicação mais detalhada que echo 1.15 * 100; faz:

  • Ele analisa 1.15 para um número de ponto flutuante binário. Isso envolve arredondamento, isso acontece para arredondar para baixo um pouco para obter o binário número de ponto flutuante mais próximo de 1,15. A razão pela qual você não pode obter um número exato (sem erro de arredondamento) é que 1.15 tem número infinito de algarismos na base 2.
  • Ele analisa 100 para um número de ponto flutuante binário. Trata-se de arredondamento, mas uma vez que 100 é um número inteiro pequeno, o erro de arredondamento é zero.
  • Ele calcula o produto dos dois números anteriores. Isso também envolve um pouco de arredondamento, para encontrar o número de ponto flutuante binário mais próximo. O erro de arredondamento passa a ser zero nesta operação.
  • Ele converte o binário número de ponto flutuante para um número decimal de base 10 com um ponto, e imprime essa representação. Isso também envolve um pouco de arredondamento.

A razão pela qual PHP imprime o Corrected price = float(115) surpreendente (em vez de 114,999 ...) é que var_dump não imprime o número exato (!), Mas ele imprime o número arredondado para n - 2 (ou n - 1) dígitos, onde n dígitos é a precisão do cálculo. Você pode facilmente verificar isto:

echo 1.15 * 100;  # this prints 115
printf("%.30f", 1.15 * 100);  # you 114.999....
echo 1.15 * 100 == 115.0 ? "same" : "different";  # this prints `different'
echo 1.15 * 100 < 115.0 ? "less" : "not-less";    # this prints `less'

Se você estiver imprimindo carros alegóricos, lembre-se:. você nem sempre ver todos os dígitos quando você imprime o flutuador

Veja também a grande aviso próximo ao início da docs do flutuador PHP .

Outras dicas

As outras respostas têm coberto a causa e uma boa solução para o problema, eu acredito.

Para visar a fixação do problema a partir de um ângulo diferente:

Para armazenar valores de preços em MySQL, você provavelmente deve olhar para o dECIMAL tipo , que permite armazenar valores exatos com casas decimais.

PHP está fazendo arredondamento baseado em dígitos significativos. Ele está escondendo a imprecisão (na linha 2). Claro que, quando piso vem junto, não sabe nada bem e lops-lo todo o caminho.

Talvez seja uma outra possível solução para este "problema":

intval(number_format($problematic_float, 0, '', ''));

Como afirmado isso não é um problema com o PHP, por si só, é mais uma questão de lidar com frações que não pode ser expressa como valores de ponto flutuante finitos, portanto, levando à perda do caráter quando o arredondamento para cima.

A solução é garantir que, quando você está trabalhando em valores de ponto flutuante e você precisa manter a precisão - usar as funções gmp ou as funções matemáticas BC - bcpow, bcmul et al. e que o problema será resolvido facilmente.

por exemplo, em vez de $ Price_corrected = $ preço * 100;

usar $ price_corrected = bcmul ($ preço, 100);

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top