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...
Vos commentaires
S.
$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" ;)