erreur de calcul

Petit nouveau ! | 1 Messages

13 déc. 2010, 00:48

Je suis tombé sur cette erreur de calcul par PHP :
<?php

$a = 0.7;
$b = 7;
$c = $a * $b;
$d = 4.9;
print $d - $c;
	
?>
et le résultat (qui devrait être 0 pour ceux qui n'auraient pas suivi)
8.881784197E-16

étonnant non ?
Quelqu'un pourrait-il m'expliquer cela ?

ViPHP
ViPHP | 5462 Messages

13 déc. 2010, 01:20

les floats c'est assez capricieux utilise bcsub
http://php.net/manual/fr/language.types.float.php

devlop78
Invité n'ayant pas de compte PHPfrance

13 déc. 2010, 01:22

Je viens d'essayer ... une bonne colle ! Il ne s'agit apparemment pas d'un problème de typage, ni d'arrondi ...

Pour avoir 0, essaie echo $c - $d - $c + $d; :p

devlop78
Invité n'ayant pas de compte PHPfrance

13 déc. 2010, 01:24

Ceci peut porter à confusion : par exemple, floor((0.1+0.7)*10) retournera 7 au lieu de 8 comme cela pourrait se prévoir, car la représentation interne serait quelque chose comme 7.9.
Je sais pas ce qu'ils fument chez php mais ils faudraient qu'ils arrêtent de jouer à la roulette russe avec les bits ^^

ViPHP
ViPHP | 5462 Messages

13 déc. 2010, 01:30

mais c'est bizarre quand même par que si on remplace $c par 4.9 ca marche...

devlop78
Invité n'ayant pas de compte PHPfrance

13 déc. 2010, 01:42

Oui. Et si on fait echo $c, ça affiche bien le truc. Il n'y a qu'une seule décimale, :shock:

devlop78
Invité n'ayant pas de compte PHPfrance

13 déc. 2010, 01:43

Par contre 0.5 et 10, ça marche.

Eléphant du PHP | 209 Messages

13 déc. 2010, 07:03

0.7 * 7 = 0.4899999999

0.49 - 0.4899999999 = epsilon

Le compte est bon !

Si tu veux savoir si la soustraction de deux flottants donne 0, il faut faire :
$x - $y < $epsilon

mais jamais
$x - $y == 0

Tous est dans la doc : http://php.net/manual/fr/language.types.float.php
--
Eric

devlop78
Invité n'ayant pas de compte PHPfrance

13 déc. 2010, 21:16

Hmmm ... Je crois avoir compris. Cela est dû à la restriction de la quantité de bits dans la représentation du nombre. Si par exemple, 4,9 s'écrit 10000111100000001 en binaire (c'est du gros pif), mais qu'il est limité en taille, il sera donc arrondi ou coupé, ce qui ne donnera pas 4,9.
* le signe est représenté par un seul bit, le bit de poids fort (celui le plus à gauche)
* l'exposant est codé sur les 8 bits consécutifs au signe
* la mantisse (les bits situés après la virgule) sur les 23 bits restants
Bref, pour une opération arithmétique, mieux vaut utiliser sa calculatrice ^^

ViPHP
xTG
ViPHP | 7331 Messages

13 déc. 2010, 23:47

Il n'y a pas de raison que 4.9 soit coupé. Il y aurait une infinité de chiffres après la virgule je dis pas mais là...
Le codage d'un nombre à virgule revient à mettre tout derrière la virgule (donc la mantisse) et de garder l'exposant pour revenir au nombre original.

Donc 4.9 se codera 0.49 avec un exposant de 1.
La mantisse acceuillera le chiffre 49 (ce qui se code aisément sur 23bits...) et l'exposant prendra un bit sur huit donc...

Question de codage du chiffre il n'y a pas de raison d'avoir de moins bons résultat avec PHP qu'avec la calculatrice, les deux sont codés sur 32bits.

devlop78
Invité n'ayant pas de compte PHPfrance

14 déc. 2010, 00:57

Ce qui me chiffonne aussi c'est que si 4.9 - $x != 0, c'est que $x != 4.9

Or un echo $x nous affiche pourtant bien 4.9. Il faudrait faire le test avec Java ou JavaScript pour voir ce que ça donne. Bientôt 5 - 5 fera 12.

Eléphant du PHP | 209 Messages

14 déc. 2010, 07:42

Mais non, c'est comme ça depuis que le monde est monde et le float flottant !

Je me souviens que c'était déjà le cas en C et fatalement, comme PHP est basé dessus, il utilise le même mode de calcul flottant.
Pour faire simple : la mantisse est écrite en base 2 (et pas 10), donc le nombre est écrit :
x0*2^-1 + x1*2^-2 + ...

et comme tous nombre réel (et c'est le cas de 0.49) ne peux s'écrire de cette manière, il est enregistré en 0.4899999999.

(voici la version longue et complexe de ce que j'avance : http://docs.sun.com/source/806-3568/ncg_goldberg.html )
--
Eric

ViPHP
xTG
ViPHP | 7331 Messages

14 déc. 2010, 10:40

J'ai pas eu le temps de tout lire mais il semblerait que je peux jeter mon cours sur la représentation des nombres flottants en binaire...
L'art de nous apprendre des aspects théoriques qui sont entièrement faux, j'adore...

Le pire dans tout ça c'est que j'ai déjà eu à faire des calcul flottants sur un ARM7TDMI et que j'ai jamais trouvé quoi que ce soit qui bug...
Alors que si ce que cet article avance il n'est pas possible d'obtenir un quelconque calcul correct non ? A moins que dans certains cas il soit capable de combler la marge d'erreur ? Ou bien que la calculette que j'utilisais pour vérifier affichait la même marge d'erreur. x)

devlop78
Invité n'ayant pas de compte PHPfrance

14 déc. 2010, 11:03

Il est clair que l'opération de base tel que le calcul pour un ordinateur, est déroutant à ce niveau là. Si quelqu'un a le temps d'essayer de tester la même chose sur un autre langage de programmation, ce serait avec joie ; On pourrait en conclure, comme le dit l'article sur les nombres floattant php, qu'une addition n'est pas $x + $y, mais doit passer par une fonction qui se charge de cette "marge" telle que SUM($x,$y).

Eléphant du PHP | 209 Messages

14 déc. 2010, 11:57

La seule chose qui ne marche pas c'est de tester l'égalité de deux flottants : l'opération devrait être interdite !
echo (0.49 == 0.7*0.7)?"égale":"pas égale";
echo "\n";
echo (bccomp(0.49,bcmul(0.7,0.7))==0)?"égale":"pas égale";
echo "\n";
--
Eric