bug d'arrondi de php

Eléphant du PHP | 377 Messages

23 oct. 2008, 20:13

Hello
Je crois que je viens de trouver un bug de php :D
(j'ai l'air content comment ça mais j'ai passé l'après midi à me demander ce qui se passait ...)
donc, venons en au fait :
ma version de php : 5.1.6
recette : prenez une page vierge, et faite ça :
<?=var_dump(9.99 != 9.99); ?><br />
<?=var_dump(9.99 != 10 - 0.01); ?><br />
<?=var_dump(9.99 != 9 + 0.99); ?><br />
<?=var_dump(9.99 != 19.90 - 9.91); ?><br />
<!-- encore plus fort !!! -->
<?=var_dump(9.99 != round(19.90 - 9.91, 2)); ?><br />
etonnant non? :lol:
Petit scarabée deviendra grand

Mammouth du PHP | 2937 Messages

23 oct. 2008, 20:59

Et si tu balisais ton code PHP en évitant la notation courte ? ;) Au lieu de <?=, écris <?php echo.

Eléphant du PHP | 377 Messages

23 oct. 2008, 21:04

Bon, la même pour les intégristes :lol:
<?php echo var_dump(9.99 != 9.99); ?><br />
<?php echo var_dump(9.99 != 10 - 0.01); ?><br />
<?php echo var_dump(9.99 != 9 + 0.99); ?><br />
<?php echo var_dump(9.99 != 19.90 - 9.91); ?><br />
<!-- encore plus fort !!! -->
<?php echo var_dump(9.99 != round(19.90 - 9.91, 2)); ?><br />
mais ça change rien au biniou ;)
Petit scarabée deviendra grand

Avatar du membre
Administrateur PHPfrance
Administrateur PHPfrance | 9782 Messages

24 oct. 2008, 00:19

Pour ton dernier exemple, il s'agit d'une erreur de ta part de parenthèses :
<!-- encore plus fort !!! -->
<?php echo var_dump(9.99 != round((19.90 - 9.91), 2)); ?><br />
Quand tout le reste a échoué, lisez le mode d'emploi...

ViPHP
ViPHP | 4674 Messages

24 oct. 2008, 00:35

Hey :),

La première : 9,99 est égal à 9,99, normal.
La seconde : 9,99 est égal à 10 - 0,01, soit 9,99, normal (sachant que 10 est équivalent à 10,0 en PHP).
La troisième: 9,99 est égal à 9 + 0,99, normal (raisonnement analogue à précédemment).
La quatrième : 9,99 ne vaut pas 19,90 - 9,91, soit 9,99 … Bizarre :-k, voici mes tests :

Code : Tout sélectionner

$ php -a Interactive mode enabled <?php var_dump(9.99 == (19.90 - 9.91)); bool(false) var_dump(19.90 - 9.91); float(9.99) var_dump(9.99); float(9.99) $a = 9.99; $b = 19.90 - 9.91; var_dump($a == $b); bool(false) var_dump($a, $b); float(9.99) float(9.99)
J'avoue ne pas comprendre. J'ai re-testé avec 9,99 == (10,0 - 0,01), c'est vrai, mais pas avec cette opération, vraiment bizarre :-k
Le dernier exemple (selon arthur) est correct, normal.

Donc, seul l'exemple trois est incorrect et je ne me l'explique. Intéressant. Quelqu'un a une idée ?

Au passage : testé avec PHP 5.2.6.
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

Eléphant du PHP | 185 Messages

24 oct. 2008, 06:36

http://fr.php.net/manual/fr/language.ty ... uote]Ainsi, ne faite jamais confiance aux derniers chiffres d'un nombre décimal, mais aussi, ne comparez jamais l'égalité de 2 nombres décimaux. Si vous avez besoin d'une haute précision, les fonctions mathématiques de précision et les fonctions gmp sont disponibles.[/quote]

Eléphant du PHP | 377 Messages

24 oct. 2008, 10:29

ups, désolé pour la parenthèse :oops:
merci pour la lien savageman, je teste ça de suite ;)
EDIT :
Euh non en fait il n'y avait pas de problème de parenthèse, ça marchait bien
Petit scarabée deviendra grand

Eléphant du PHP | 422 Messages

24 oct. 2008, 14:35

J'avoue ne pas comprendre. J'ai re-testé avec 9,99 == (10,0 - 0,01), c'est vrai, mais pas avec cette opération, vraiment bizarre
La notation en virgule flottante représente un nombre décimal sous la forme nombre = M x 2^E (c'est un peu plus compliqué, mais c'est le principe qui importe).
voir http://babbage.cs.qc.edu/IEEE-754/Decimal.html

Sauf que cette représentation entraîne des approximations et que 9.99 est en fait représenté sous la forme une forme hexadécimale qui représente en fait le nombre 9.99000000000000021316 .
$r1 = 9.91;
printf("%01.20f<br>", $r1);
//9.91000000000000014211
$r2 = 19.90;
printf("%01.20f<br>", $r2);
//19.89999999999999857891
$r3 = 9.99;
printf("%01.20f<br>", $r3);
//9.99000000000000021316
$r4 = $r2 - $r1;
printf("%01.20f<br>", $r4);
//9.98999999999999843681
Les opérations arithmétiques ne se font pas à notre manière humaine en mettant les chiffres les uns en dessous des autres, mais avec des algorithmes qui travaillent directement sur les représentations hexadécimales des nombres en virgule flottante. Et ces algos introduisent eux aussi leur lot d'approximation.

Donc, en représentation interne, 19.90 - 9.91 est différent de 9.99. Par contre le programme echo chargé d'afficher un nombre comporte un mécanisme d'arrondi qui fait que 9.99000000000000021316 et 9.98999999999999843681 seront tous les deux affichés sous la forme 9.99.

D'où notre impression que c'est identique alors que ça ne l'est pas.

Ce mécanisme est bien connu de tous ceux qui bossent dans la finance. Si un article à 9.99€ est stocké sous la forme 9.9900000000000002€, on aura un décalage de 2 centimes quand on aura traité un stock de quelques millions de pièces. Et ça, pour un comptable, c'est inacceptable.
C'est pour ça que dans certains langages, on a la notion de nombre décimal différente de nombre à virgule flottante. Les nombres décimaux sont stockés avec leur valeur exacte (9.99) quasiment sous forme de chaîne de caractères et les opérations arithémétiques se font grosso-modo à la manière humaine.

ViPHP
ViPHP | 4674 Messages

24 oct. 2008, 14:50

Oui je connais le codage IEEE 754, quand même (et j'en ai suffisamment bouffer l'année dernière en architecture :P) … J'avais fait le calcul de tête pour voir s'il se terminait ou pas et je mettais arrêté à 2^-6, j'aurais du aller plus loin (mais la fatigue et tout et tout hein …).
Bref, c'est donc normal. QED ;-).
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).