Facebook LinkedIn SourceForge Twitter RSS LastFM
logologo

La comparaison de nombres à virgule flottante en PHP

Geoffray Warnants|30/07/2010|6 commentaires

Malgré les considérables évolutions qu'a subi PHP, on ne peut nier qu'il est encore aujourd'hui trahi par de petites imperfections qui salissent son blason. Les détracteurs s'en donnent à coeur joie pour critiquer l'amateurisme du langage, les autres, certainement un peu fatalistes, préfèrent dire qu'il a le mérite d'être une source intarissable de surprises ;)

Dernière curiosité en date : La comparaison de 2 nombres décimaux n'est pas fiable. Ainsi :

var_dump(0.2+0.3 == 0.5);   // affiche true
var_dump(0.2+0.4 == 0.6);   // affiche false

Le second résultat est pour le moins inattendu ! Il se comprend néanmoins en regardant de plus près la valeur réelle de chacune des valeurs jusqu'à leurs infimes décimales.

printf('%.20f', 0.2);       // 0.20000000000000001110
printf('%.20f', 0.3);       // 0.29999999999999998890
printf('%.20f', 0.2+0.3);   // 0.50000000000000000000
printf('%.20f', 0.5);       // 0.50000000000000000000

printf('%.20f', 0.2);       // 0.20000000000000001110
printf('%.20f', 0.4);       // 0.40000000000000002220
printf('%.20f', 0.2+0.4);   // 0.60000000000000008882
printf('%.20f', 0.6);       // 0.59999999999999997780

"Honteux" crierons les détracteurs, "normal" diront simplement les autres. En effet, un avertissement dans la doc PHP nous met en garde face à ce comportement hasardeux bien connu et l'explique par le fait que la représentation interne de certains nombres décimaux n'est pas possible sans une infime perte de précision. Une remarque qui n'est pas sans raviver mes lointaines notions d'ASM et les prises de tête pour comprendre la représentation des décimaux en binaire.

Bref, ce problème d'approximation est inhérent à nos ordinateurs. On le rencontre d'ailleurs dans d'autres langages de bas niveau, notamment en C, langage avec lequel est écrit PHP.

Que faut-il faire ?

Comme indiqué dans la doc, comparer des nombres décimaux de manière classique est à proscrire. Pour ce genre d'opération qui nécessite une précision importante, PHP fourni un ensemble de fonctions spécifiques, dont bccomp qui permet de comparer des nombres de grande taille.

var_dump(bccomp(0.2+0.4, 0.6)==0); // affiche true

Une autre solution plus artisanale consiste à caster les nombres en chaînes de caractères avant d'effectuer la comparaison.

var_dump((string)(0.2+0.4)==(string)0.6); // affiche true

Cette solution semble tout aussi acceptable puisque lors de la conversion, PHP prend soin de retirer les éventuels zéros non significatifs qui pourraient poser problème en cas de comparaison textuelle.

var_dump((string)(0.2+0.4)==(string)000.60000); // affiche true aussi

La suite n'est plus qu'une histoire de préférence...

<<< Retour

Vos commentaires

3 commentaires postés

Geoffray
04/08/2010 20:50Posté par Geoffray
Effectivement, le terme n'est pas des plus judicieux... Voilà ce qui arrive quand on recopie la doc comme un idiot ;)
Syndrael
04/08/2010 11:28Posté par Syndrael
"dont bccomp qui permet de comparer des nombres de grande taille."..quand on voit l'exemple ça laisse perplexe.. LOL !!
S.
OuT
31/07/2010 15:48Posté par OuT
j'ai aussi cet exemple, qui est assez traître ;)

$arrondi = round(1.4);
if ($arrondi == 1) {
// OK
}
if ($arrondi === 1) {
// comparaison stricte FAIL
}

var_dump($arrondi);

et oui, malgré le fait que round(), floor() et ceil() renvoient forcément un nombre entier, le résultat est de type "float" ;)

Réagir à cet article

*


(Ne sera pas publiée, servira uniquement à afficher votre gravatar)


(Lien en dur et dofollow)

zend framework