Aller au contenu

Erreur de calcul PHP


Remi

Sujets conseillés

Bonjour,

Quelqu'un peut-il m'expliquer ce phénomène étrange qui m'a fait faire des heures supplémentaires :

Avec :

$var=0.71*15*100;

je devrais me retrouver avec une variable de type double qui contient 1065.

Et bien si je fais :

if ($var == 1065) print "OK";

ça ne marche pas....!

Explication :

print $var." ".intval($var);

donne

1065 1064

Quand on passe en integer simple, on a 1064 !!

En fait 0.71*15*100 semble donner 1064,99999

Ce n'est pas un peu bizarre....? :unsure:

Remi

Lien vers le commentaire
Partager sur d’autres sites

En effet, bizarre... Ceci-dit, PHP n'est pas un langage de calcul mathématique, va savoir comment il les fait ! :)

Au lieu d'utiliser intval, je pense qu'il serait mieux d'utiliser "round"... Comme ça, même s'il te met des ,9999 ou des ,0001 il te sortira la bonne valeur.

Mais si quelqu'un sait comment PHP gère les multiplications au niveau assembleur, ça m'intéresse, parce que j'avoue ne pas voir comment arriver à un résultat pareil. :|

Lien vers le commentaire
Partager sur d’autres sites

En fait 0.71*15*100 semble donner 1064,99999

Ce n'est pas un peu bizarre....? :unsure:

Oui et non. De nombreux nombres décimaux ne sont pas représentés exactement par PHP. C'est évident pour 0,333333333...333... (= 1/3), mais c'est aussi le cas de nombres qui paraissent faciles à représenter en binaire. A partir du moment où le calcul n'est pas fait exclusivement avec des entiers, on ne devrait plus tester qu'on a une égalité, mais seulement que la différence est négligeable pour l'application:

$variable1 - $variable2 < $EPSILON où $EPSILON est égal à 0.0000001 (par exemple).

Jean-Luc

Lien vers le commentaire
Partager sur d’autres sites

Pour compléter (et je viens de le comprendre en cherchant un peu): n'importe quel nombre décimal qui ne peut pas s'écrire sous la forme d'un entier (de taille raisonable) en le multipliant par 2^n (presque tous, donc) ne peut pas être représenté de façon exacte en flottant. C'est le cas de 0.71 (en binaire 0.101101011100001010001111010111000010100011110101110000101000 et plein d'autres chiffres, on voit bien la répétition). Il y a donc un écart et on ne fait qu'ajouter de l'imprécision quand on fait d'autres opérations, et donc au final tu as un écart (de l'ordre de 10^-13 dans ton calcul).

Au passage, pour faire la comparaison, il faut évidemment penser à mettre un coup de valeur absolue sur la différence! :-)

Jacques.

Modifié par jcaron
Lien vers le commentaire
Partager sur d’autres sites

Merci pour vos réponses.

Oui, je me suis aperçu que c'est effectivement courant.

Avec un

for ($i=1,$cpt=0;$i<1000;$i++) {
$var= 0.2 * $i * 10;
if ($var != intval($var)) {
print "$i ";
$cpt++;
}
}
print "soit $cpt cas ";

on voit qu'il y a un problème dans 25% des cas!

(en revanche, 0.2*10*$i est toujours OK donc si la première opération donne un nombre entier, on reste en variable double mais l'imprécision n'est plus là)

Merci pour l'idée de transformer les (test $var1 == $var2) par (abs($var1-$var2) < 0.001).

L'autre solution est de mettre des round, mais on risque d'en oublier...

Je suis tout de même très étonné par tout ça. Je pensais que les imprécisions des variables en type double étaient limitées à des nombres complexes. C'est tout de même étonnant qu'il ait du mal avec par exemple 0.2 * 3 :wacko:

Lien vers le commentaire
Partager sur d’autres sites

Mon conseil personnel: si tu as besoin d'un résultat précis (par exemple si tu manipules des montants en euros avec des centimes), toujours travailler en virgule fixe (i.e. avec des entiers qui représentent des centimes). Ca t'évitera énormément de soucis...

Jacques.

Lien vers le commentaire
Partager sur d’autres sites

Cette discussion me laisse vraiment pantois : c'est un probleme qui est connu depuis que les calculs binaires existent :nonono:

PHP n'a strictement rien a faire la dedans : ce sont les librairies mathematiques du systemes qui etablissent la precision de ces calculs.

Les solutions sont :

  • Augmenter les precisions internes (genre calculer sur 10 digits pour n'en afficher que 8).
  • utiliser du BCD plutot que du binaire pure
  • utiliser uniquement du calcul formel lorsque c'est possible.

Mais faut etre clair, hormis le calcul formelle, y'a rien de miraculeux, hein ...

Lien vers le commentaire
Partager sur d’autres sites

Veuillez vous connecter pour commenter

Vous pourrez laisser un commentaire après vous êtes connecté.



Connectez-vous maintenant
×
×
  • Créer...