2013-02-06 10 views
5

Ho un semplice ciclo for in PerlPerl per il ciclo che va in tilt

for ($i=0; $i <= 360; $i += 0.01) 
{ 
print "$i "; 
} 

Perché è che quando faccio funzionare questo codice ottengo il seguente output, dove non appena si arriva a 0,81 che improvvisamente inizia a aggiungere in un carico più posti decimali? So che potrei semplicemente arrotondare per evitare questo problema, ma mi chiedevo perché succede. Un incremento di 0,01 non sembra affatto folle da fare.

0.77 
0.78 
0.79 
0.8 
0.81 
0.820000000000001 
0.830000000000001 
0.840000000000001 
0.850000000000001 
0.860000000000001 
0.870000000000001 
+2

http://learn.perl.org/faq/perlfaq4.html#Why-am-I-getting-long-decimals-eg-19.9499999999999-instead-of-the- numeri-I-should-be-getting-eg-19.95- – mob

+0

http://stackoverflow.com/questions/10908825/, http://stackoverflow.com/questions/9790048, http://stackoverflow.com/questions/ 3916314, http://stackoverflow.com/questions/7066636, http://stackoverflow.com/questions/2080629, http://stackoverflow.com/questions/2056681, http://stackoverflow.com/questions/14204125 – mob

+0

8.1 sembra essere un numero notevolmente difficile da convertire. Anni fa, avevo un mini-progetto che cercava di trovare * qualche * modo di manipolare un valore particolare: '8.10' con precisione. Sia Java che Perl, stavano avendo problemi con questo numero. – Axeman

risposta

15

I computer utilizzano rappresentazioni binarie. Non tutti i numeri decimali in virgola mobile hanno rappresentazioni esatte nella notazione binaria, quindi può verificarsi un errore (è in realtà una differenza di arrotondamento). Questa è la stessa ragione per why you shouldn't use floating point numbers for monetary values:

messed up recepit http://img.thedailywtf.com/images/200902/errord/DSC00669.JPG

(Foto scattata da dailywtf)

modo più elegante per aggirare questo problema è utilizzare numeri interi per i calcoli, dividendoli per il numero corretto di decimali luoghi e utilizzando sprintf per limitare il numero di posizioni decimali stampate.In questo modo sia assicurarsi:

  • C'è sempre di correggere risultato stampato
  • L'errore di arrotondamento non si accumula

provare questo codice:

#!/usr/bin/perl 
for ($i=0; $i <= 360*100; $i += 1) { 
    printf "%.2f \n", $i/100; 
} 
+7

+1 per le prove documentali di pratiche di programmazione errate in natura! –

7

In sostanza, in quanto il numero decimale 0,01 non ha una rappresentazione esatta in virgola mobile binario, così nel corso del tempo, aggiungendo la migliore approssimazione a 0,01 devia dalla risposta che desideri.

Questa è la proprietà di base dell'aritmetica in virgola mobile (binaria) e non peculiare di Perl. What Every Computer Scientist Should Know About Floating-Point Arithmetic è il riferimento standard e puoi trovarlo molto facilmente con una ricerca su Google.

Vedere anche: C compiler bug (floating point arithmetic) e senza dubbio una miriade di altre domande.


Kernighan & Plauger dire, nel loro vecchio ma classico libro "The Elements of Programming Style", che:

  • Un vecchio programmatore saggio una volta disse "galleggianti numeri in virgola sono come piccoli mucchi di sabbia, ogni volta che si sposta un , perdi un po 'di sabbia e guadagni un po' di sporcizia ".

dicono anche:

  • 10 * 0,1 è quasi mai 1.0

Entrambi detti sottolineare che aritmetica in virgola mobile non è precisa.

Si noti che alcune CPU moderne (IBM PowerPC) hanno un decimale aritmetico decimale in virgola mobile decimale IEEE 754: 2008. Se Perl ha usato i tipi corretti (probabilmente non lo fa), allora il tuo calcolo sarebbe esatto.

2

1/10 è periodica in binario esattamente come 1/3 è periodico in decimale. In quanto tale, non può essere archiviato con precisione in un numero in virgola mobile.

>perl -E"say sprintf '%.17f', 0.1" 
0.10000000000000001 

o lavorare con gli interi

for (0*100..360*100) { 
    my $i = $_/100; 
    print "$i "; 
} 

o fare un sacco di arrotondamento

for (my $i=0; $i <= 360; $i = sprintf('%.2f', $i + 0.01)) { 
    print "$i "; 
} 
4

Per dimostrare la risposta di Jonathan, se si codifica la stessa struttura ad anello in C, si otterrà il stesso risultato. Sebbene C e Perl possano essere compilati in modo diverso e possano essere eseguiti su macchine diverse, le regole aritmetiche sottostanti in virgola mobile dovrebbero generare output coerenti. Nota: Perl usa il floating point a precisione doppia per la sua rappresentazione in virgola mobile mentre in C il coder sceglie esplicitamente float o double.

Loop in C:

#include <stdio.h> 

    int main() { 
     double i; 
     for(i=0;i<=1;i+=.01) { 
      printf("%.15f\n",i); 
     } 
     } 

uscita:

0.790000000000000 
    0.800000000000000 
    0.810000000000000 
    0.820000000000001 
    0.830000000000001 
    0.840000000000001 
    0.850000000000001 

Per dimostrare il punto ulteriormente, codice del ciclo in C ma ora usano singola precisione aritmetica in virgola mobile e vedere che l'uscita è meno preciso e anche più irregolare.

uscita:

0.000000000000000 
    0.009999999776483 
    0.019999999552965 
    0.029999999329448 
    0.039999999105930 
    0.050000000745058 
    0.060000002384186 
    0.070000000298023 
    0.079999998211861 
    0.089999996125698 
    0.099999994039536