2013-09-05 15 views
41

Il codice:Perché viene stampato l'ultimo numero (1)?

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

L'output:

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 

Creato un più fiddle

Uno: se si imposta $start = 1 e $stop = 2 funziona benissimo.

Usando: php 5.3.27

Perché la 1 stampato?

+1

sorprendentemente, quando si divide l'intervallo in 20: $ step = ($ fermata - $ start)/20; è anche ok – user4035

+3

Forse un errore a virgola mobile. – Blazemonger

+1

correlati a http://stackoverflow.com/questions/3726721/php-math-precision – j08691

risposta

53

Perché non solo la matematica mobile è imperfetta, a volte la sua rappresentazione is flawed too - e questo è il caso qui.

In realtà non ottiene 0.1, 0.2, ... - e questo è abbastanza facile da controllare:

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

L'unica differenza, come vedete, è che echo sostituito con number_format chiamata. Ma i risultati sono drasticamente diversi:

0.10000000000000000555111512312578 
0.20000000000000001110223024625157 
0.30000000000000004440892098500626 
0.40000000000000002220446049250313 
0.50000000000000000000000000000000 
0.59999999999999997779553950749687 
0.69999999999999995559107901499374 
0.79999999999999993338661852249061 
0.89999999999999991118215802998748 
0.99999999999999988897769753748435 

Vedere? Solo una volta era 0.5 - perché quel numero può essere memorizzato in un contenitore float. Tutti gli altri erano solo approssimazioni.

Come risolvere questo? Bene, un approccio radicale sta usando non float, ma interi in situazioni simili. E 'facile notare che hai fatto in questo modo ...

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

... che avrebbe funzionato bene:

In alternativa, è possibile utilizzare number_format per convertire il galleggiante in qualche stringa, quindi confrontare questo stringa con galleggiante preformattato. In questo modo:

$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; 
} 
+1

Qual è un buon modo per evitare che ciò accada? –

+4

Una soluzione sarebbe mantenere il tuo '$ step' un intero:' $ start = 0; $ Arresto = 10; $ step = 1; 'e fa la divisione all'interno del ciclo. – Blazemonger

+0

@RichBradshaw Il modo migliore per proteggere sarebbe quello di passare dall'aritmetica in virgola mobile a numeri interi moltiplicando stop per 10 e aumentando i per 1 invece di 0.1 – user4035

13

Il problema è che il numero nella variabile $ i non è 1 (se stampato). La sua attuale appena meno di 1. Quindi nel test ($ i < $ stop) è vero, il numero viene convertito in decimale (causando l'arrotondamento a 1) e visualizzato.

Ora, perché $ I non è esattamente 1? È perché ci sei arrivato dicendo 10 * 0.1, e 0.1 non può essere rappresentato perfettamente in binario. Solo i numeri che possono essere espressi come somma di un numero finito di potenze di 2 possono essere perfettamente rappresentati.

Perché allora $ stop esattamente 1? Perché non è in formato a virgola mobile. In altre parole, è esatto dall'inizio - non viene calcolato all'interno del sistema utilizzato in virgola mobile 10 * 0.1.

Matematicamente, possiamo scrivere ciò come segue: enter image description here

Un galleggiante binario a 64 bit può contenere solo i primi 27 termini diversi da zero della somma che approssima 0.1. Gli altri 26 bit del significato e rimangono zero per indicare i termini zero. Il motivo 0,1 non è fisicamente rappresentabile è che la sequenza richiesta di termini è infinita. D'altra parte, numeri come 1 richiedono solo un numero limitato di termini e sono rappresentabili.Ci piacerebbe che fosse il caso per tutti i numeri. Questo è il motivo per cui il virgola mobile decimale è un'innovazione così importante (non ancora ampiamente disponibile). Può rappresentare qualsiasi numero che possiamo scrivere e farlo perfettamente. Naturalmente, il numero di cifre disponibili rimane finito.

Tornando al problema dato, poiché 0.1 è l'incremento per la variabile di loop e non è effettivamente rappresentabile, il valore 1.0 (sebbene rappresentabile) non viene mai raggiunto con precisione nel loop.

2

Se il vostro passo sarà sempre un fattore 10, è possibile eseguire questa rapidamente con il seguente:

<?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; 
} 
?> 
Problemi correlati