2010-01-20 13 views
29

Come si spiega l'inesattezza di virgola mobile a programmatori e laici che pensano ancora che i computer siano infinitamente saggi e precisi?
Hai un esempio o un aneddoto preferito che sembra avere un'idea molto migliore di una spiegazione precisa, ma asciutta?
Come viene insegnato questo corso di informatica?Esempi di imprecisione di virgola mobile

+1

Puoi comprenderlo con questo semplice javascript: alert (0.1 * 0.1 * 10); –

+0

Dai un'occhiata a questo articolo: [Ciò che ogni scienziato informatico dovrebbe sapere sull'aritmetica a virgola mobile] (http://docs.sun.com/source/806-3568/ncg_goldberg.html) –

risposta

26

Ci sono fondamentalmente due trappole principali che le persone inciampano con numeri in virgola mobile.

  1. Il problema della scala. Ogni numero FP ha un esponente che determina la "scala" globale del numero in modo da poter rappresentare valori veramente piccoli o quelli veramente grandi, sebbene il numero di cifre che è possibile dedicare sia limitato. Aggiungendo due numeri di scala diversa a volte si rischia di "mangiare" quello più piccolo poiché non c'è modo di adattarlo alla scala più grande.

    PS> $a = 1; $b = 0.0000000000000000000000001 
    PS> Write-Host a=$a b=$b 
    a=1 b=1E-25 
    PS> $a + $b 
    1 
    

    Come un'analogia per questo caso è possibile immaginare una grande piscina e un cucchiaino di acqua. Entrambi sono di dimensioni molto diverse, ma individualmente è facile capire quanto sono approssimativamente. Versare il cucchiaino in piscina, tuttavia, ti lascerà ancora con circa una piscina piena d'acqua.

    (Se le persone che apprendono questo avere problemi con la notazione esponenziale, si possono anche utilizzare i valori 1 e 100000000000000000000 o giù di lì.)

  2. Poi c'è il problema del binario vs rappresentazione decimale. Un numero come 0.1 non può essere rappresentato esattamente con una quantità limitata di cifre binarie. Alcuni linguaggi mascherano questo, però:

    PS> "{0:N50}" -f 0.1 
    0.10000000000000000000000000000000000000000000000000 
    

    ma si può “amplificare” l'errore di rappresentanza ripetutamente aggiungendo insieme i numeri:

    PS> $sum = 0; for ($i = 0; $i -lt 100; $i++) { $sum += 0.1 }; $sum 
    9,99999999999998 
    

    non riesco a pensare ad una bella analogia per spiegare correttamente questo , anche se. È fondamentalmente lo stesso problema per cui è possibile rappresentare/solo approssimativamente in decimale perché per ottenere il valore esatto è necessario ripetere il 3 indefinitamente alla fine della frazione decimale.

    Analogamente, le frazioni binarie sono buone per rappresentare metà, quarti, ottavi, ecc. Ma cose come un decimo produrranno un flusso infinito di cifre binarie.

  3. Poi c'è un altro problema, anche se la maggior parte delle persone non inciampa, a meno che non stiano facendo enormi quantità di cose numeriche. Ma poi, quelli già sanno del problema. Poiché molti numeri in virgola mobile sono soltanto approssimazioni del valore esatto questo significa che per una data approssimazione f di un numero reale r ci possono essere infiniti numeri più reale r , r , ... che corrispondono esattamente alla stessa approssimazione. Quei numeri si trovano in un certo intervallo.Diciamo che r min è il valore minimo possibile di r che si traduce in f e r max il valore massimo possibile di r per i quali questo vale, allora si ha un intervallo [ r min, r max] in cui qualsiasi numero in questo intervallo può essere il vostro attuale numero r.

    Ora, se si eseguono calcoli su tale numero: aggiunta, sottrazione, moltiplicazione, ecc., Si perde precisione. Ogni numero è solo un'approssimazione, quindi stai effettivamente eseguendo calcoli con gli intervalli . Il risultato è anche un intervallo e l'errore di approssimazione diventa sempre più grande, allargando così l'intervallo. Puoi recuperare un singolo numero da quel calcolo. Ma questo è semplicemente un numero nell'intervallo possibili risultati, tenendo conto della precisione degli operandi originali e della perdita di precisione dovuta al calcolo.

    Questo genere di cose si chiama Interval arithmetic e almeno per me faceva parte del nostro corso di matematica all'università.

+1

Ciao Johannes, è sicuramente un buon esempio, ma in realtà non dice alla gente * perché * non funziona. Sto cercando di far capire a qualcuno il motivo del fallimento, non solo del fatto che ogni tanto fallisce. –

+1

Hm, oltre a spiegare il problema della scala e il problema della rappresentazione binaria rispetto alla rappresentazione decimale, penso di non aver trovato un modo migliore per dirlo alla gente: /. Si potrebbero usare aneddoti simili, come aggiungere un cucchiaino di acqua in una piscina non cambia la nostra percezione di quanto ci sia dentro. – Joey

+0

Per elaborare, molte delle persone che ottengo nei laboratori non sono nemmeno molto a proprio agio con la notazione scientifica, quindi richiedono già una buona dose di sforzo mentale per avvolgere la loro testa attorno alla differenza tra -4e200, -4e-200, 4e- 200 e 4e200. –

2

In pitone:

>>> 1.0/10 
0.10000000000000001 

Spiegate come alcune frazioni non possono essere riprodotti in modo preciso in binario. Proprio come alcune frazioni (come 1/3) non possono essere rappresentate precisamente nella base 10.

+0

codeape, sto cercando qualcosa di più profondo di un semplice esempio di errori di arrotondamento. Mi piacerebbe essere in grado di dire alla gente perché questi errori si insinuano e far capire loro il motivo alla base, senza dover comprendere la specifica IEEE 754. –

+1

@David: dai un esempio in cui i numeri in virgola mobile sono esatti, ad esempio aggiungendo 0,25 più volte. Il risultato sarà esatto fino al superamento della mantissa, perché 0.25 è '1/(2^2)'. Quindi prova la stessa cosa con 0.2 e otterrai i problemi, perché 0.2 non è rappresentabile in un numero di base-2 finito. –

6

Come si fa per un espianto al profano. I computer a senso unico rappresentano i numeri contando le unità discrete. Questi sono computer digitali. Per i numeri interi, quelli senza una parte frazionaria, i moderni computer digitali contano due potenze: 1, 2, 4, 8. ,,, Valore di posizione, cifre binarie, blah, blah, blah. Per le frazioni, i computer digitali contano le potenze inverse di due: 1/2, 1/4, 1/8, ... Il problema è che molti numeri non possono essere rappresentati da una somma di un numero finito di quei poteri inversi. L'uso di più valori di luogo (più bit) aumenterà la precisione della rappresentazione di quei numeri di "problema", ma non lo otterrà mai esattamente perché ha solo un numero limitato di bit. Alcuni numeri non possono essere rappresentati con un numero infinito di bit.

Snooze ...

OK, si vuole misurare il volume di acqua in un contenitore, e hai solo 3 misurini: Tazza in pieno, mezzo bicchiere, e quarto di tazza. Dopo aver contato l'ultima tazza piena, diciamo che è rimasto un terzo di una tazza. Eppure non puoi misurarlo perché non riempie esattamente nessuna combinazione di tazze disponibili. Non riempie la mezza tazza e il trabocco dal quarto di tazza è troppo piccolo per riempire qualsiasi cosa. Quindi hai un errore - la differenza tra 1/3 e 1/4. Questo errore è aggravato quando lo combini con errori di altre misurazioni.

8

Mostrare che il sistema di base 10 soffre di esattamente lo stesso problema.

Provare a rappresentare 1/3 come rappresentazione decimale nella base 10. Non sarà possibile farlo esattamente.

Quindi se si scrive "0.3333", si avrà una rappresentazione ragionevolmente esatta per molti casi d'uso.

Ma se si sposta di nuovo in una frazione, si otterrà "3333/10000", che è non uguale a uguale a "1/3".

Altre frazioni, come 1/2 possono essere facilmente rappresentato da una rappresentazione decimale finita in base 10: "0,5"

Ora a base 2 e base 10 soffrono essenzialmente lo stesso problema: entrambi hanno alcuni numeri che non possono rappresentare esattamente.

Mentre base 10 non ha problemi a rappresentare 1/10 come "0,1" in base-2, è necessaria una rappresentazione infinita che inizi con "0,000110011 ..".

2

altro esempio, in C

printf (" %.20f \n", 3.6); 

dà incredibilmente

3,60000000000000008882

0

Un pezzo di cute di stranezze numerica può osservare se si converte 9.999.999,4999,999999 millions ad un float e ritorno a un double. Il risultato viene segnalato come 10000000, anche se tale valore è ovviamente più vicino a 9999999 e anche se 9999999,499999999 viene arrotondato correttamente a 9999999.

1

Ecco la mia semplice comprensione.

Problema: Il valore 0,45 non può essere rappresentato con precisione da un galleggiante ed è arrotondato a 0,450000018. Perché?

Risposta: un valore int di 45 è rappresentato dal valore binario 101101. Al fine di rendere il valore 0,45 Sarebbe accurate se si potrebbe prendere 45 x 10^-2 (= 45/10^2 .) Ma questo è impossibile perché è necessario utilizzare la base 2, invece di 10.

Quindi più vicino a 10^2 = 100 sarebbero 128 = 2^7. Il numero totale di bit necessari è 9: 6 per il valore 45 (101101) + 3 bit per il valore 7 (111). Quindi il valore 45 x 2^-7 = 0,3515625. Ora hai un grave problema di inaccuratezza. 0,3515625 non è quasi vicino a 0,45.

Come migliorare questa inesattezza? Bene, potremmo cambiare il valore 45 e 7 in qualcos'altro.

Che ne dici di 460 x 2^-10 = 0,44921875. Ora stai utilizzando 9 bit per 460 e 4 bit per 10. Quindi è un po 'più vicino ma non ancora così vicino. Tuttavia, se il valore iniziale desiderato era 0.44921875, si otterrebbe una corrispondenza esatta senza alcuna approssimazione.

Quindi la formula per il valore sarà X = A x 2^B. Dove A e B sono valori interi positivi o negativi. Ovviamente più alto è il numero, maggiore sarà la precisione, tuttavia, sapendo che il numero di bit per rappresentare i valori A e B è limitato. Per float hai un numero totale di 32. Double ha 64 e Decimal ne ha 128.