41 votes

Pourquoi le dernier numéro (1) est-il imprimé?

Le code:

 <?php
$start = 0;
$stop  = 1;
$step = ($stop - $start)/10;
$i = $start + $step;
while ($i < $stop) {
    echo($i . "<br/>");
    $i += $step;
}
?>
 

Le résultat:

 0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1 <-- notice the 1 printed when it shouldn't
 

Créé un violon

Un de plus: si vous définissez $start = 1 et $stop = 2 cela fonctionne bien.

Utilisation: php 5.3.27

Pourquoi les 1 imprimés?

53voto

raina77ow Points 45965

Parce que non seulement float mathématiques est imparfait, parfois, sa représentation est erronée, trop - ce qui est le cas ici.

Vous n'avez pas réellement obtenir 0.1, 0.2, ... - et c'est assez facile à vérifier:

$start = 0;
$stop  = 1;
$step = ($stop - $start)/10;
$i = $start + $step;
while ($i < $stop) {
   print(number_format($i, 32) . "<br />");
   $i += $step;
}

La seule différence ici, comme vous le voyez, est-ce que echo remplacé par number_format appel. Mais les résultats sont radicalement différents:

0.10000000000000000555111512312578
0.20000000000000001110223024625157
0.30000000000000004440892098500626
0.40000000000000002220446049250313
0.50000000000000000000000000000000
0.59999999999999997779553950749687
0.69999999999999995559107901499374
0.79999999999999993338661852249061
0.89999999999999991118215802998748
0.99999999999999988897769753748435

Voir? Une seule fois il a été 0.5 - ce parce que le nombre peut être stocké dans un flotteur conteneur. Tous les autres sont seulement des approximations.

Comment résoudre ce problème? Eh bien, une approche radicale est d'utiliser non pas les chars, mais des entiers dans des situations similaires. Il est facile de remarquer qu'avez-vous fait de cette manière...

$start = 0;
$stop  = 10;
$step = (int)(($stop - $start) / 10);
$i = $start + $step;
while ($i < $stop) {
   print(number_format($i, 32) . "<br />");
   $i += $step;
}

... il serait de travailler sur ok:

Alternativement, vous pouvez utiliser number_format pour convertir le flotteur dans une chaîne de caractères, puis de comparer cette chaîne avec préformaté flotteur. Comme ceci:

$start = 0;
$stop  = 1;
$step = ($stop - $start) / 10;
$i = $start + $step;
while (number_format($i, 1) !== number_format($stop, 1)) {
   print(number_format($i, 32) . "\n");
   $i += $step;
}

13voto

Kevin A. Naudé Points 2384

Le problème est que le nombre dans la variable $i n'est pas 1 (lors de l'impression). Son effectif un peu moins de 1. Si dans le test ($i < $stop) est vrai, le nombre est converti en décimal (cause de l'arrondi à 1), et affiche.

Maintenant, pourquoi est - $ - je pas 1 exactement? C'est parce que tu as là en disant 10 * 0.1 et 0.1 ne peut pas être représenté parfaitement en binaire. Seuls les nombres qui peut être exprimé comme une somme d'un nombre fini de puissances de 2 peut être parfaitement représentés.

Pourquoi, alors, est de $arrêter exactement 1? Car il n'est pas en format à virgule flottante. En d'autres termes, il est exact depuis le début, il n'est pas calculé dans le système à virgule flottante 10 * 0.1.

Mathématiquement, on peut écrire comme suit: enter image description here

Un 64 bit float peut uniquement contenir les 27 premiers termes non nuls de la somme, qui se rapproche de 0.1. Les 26 autres bits de la significande restent zéro pour indiquer zéro termes. La raison de 0,1 n'est pas physiquement représentable est que la séquence de termes est infini. D'autre part, les nombres comme 1 ne nécessitent qu'un petit nombre fini de termes et sont représentable. Nous aimerions que ce soit le cas pour tous les nombres. C'est pourquoi décimal à virgule flottante est une innovation importante (pas encore disponibles). Il peut représenter n'importe quel nombre que nous pouvons écrire, et de le faire parfaitement. Bien sûr, le nombre de chiffres disponibles reste finie.

Le retour à un problème donné, depuis 0.1 est l'incrément de la variable de boucle et n'est pas représentable, la valeur 1.0 (bien que représentable) n'est jamais précisément atteint dans la boucle.

2voto

SuperJer Points 531

Si votre pas sera toujours un facteur de 10, vous pouvez le faire rapidement avec ce qui suit:

 <?php
$start = 0;
$stop  = 1;
$step = ($stop - $start)/10;
$i = $start + $step;
while (round($i, 1) < $stop) { //Added round() to the while statement
    echo($i . "<br/>");
    $i += $step;
}
?>
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X