Perché i numeri in virgola mobile sono imprecisi?


198

Perché alcuni numeri perdono precisione quando memorizzati come numeri in virgola mobile?

Ad esempio, il numero decimale 9.2può essere espresso esattamente come un rapporto di due numeri decimali ( 92/10), entrambi i quali possono essere espressi esattamente in binario ( 0b1011100/0b1010). Tuttavia, lo stesso rapporto memorizzato come un numero in virgola mobile non è mai esattamente uguale a 9.2:

32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875

Come può un numero così apparentemente semplice essere "troppo grande" per essere espresso in 64 bit di memoria?




Risposte:


241

Nella maggior parte dei linguaggi di programmazione, i numeri in virgola mobile sono rappresentati in modo molto simile alla notazione scientifica : con un esponente e una mantissa (detto anche significato). Un numero molto semplice, diciamo 9.2, è in realtà questa frazione:

5179139571476070 * 2 -49

Dov'è l'esponente -49e la mantissa 5179139571476070. La ragione per cui è impossibile rappresentare alcuni numeri decimali in questo modo è che sia l'esponente che la mantissa devono essere numeri interi. In altre parole, tutti i float devono essere un numero intero moltiplicato per una potenza intera di 2 .

9.2può essere semplicemente 92/10, ma 10 non può essere espresso come 2 n se n è limitato a valori interi.


Vedere i dati

Innanzitutto, alcune funzioni per vedere i componenti che creano un 32 e 64 bit float. Rifletti su questi se ti preoccupi solo dell'output (esempio in Python):

def float_to_bin_parts(number, bits=64):
    if bits == 32:          # single precision
        int_pack      = 'I'
        float_pack    = 'f'
        exponent_bits = 8
        mantissa_bits = 23
        exponent_bias = 127
    elif bits == 64:        # double precision. all python floats are this
        int_pack      = 'Q'
        float_pack    = 'd'
        exponent_bits = 11
        mantissa_bits = 52
        exponent_bias = 1023
    else:
        raise ValueError, 'bits argument must be 32 or 64'
    bin_iter = iter(bin(struct.unpack(int_pack, struct.pack(float_pack, number))[0])[2:].rjust(bits, '0'))
    return [''.join(islice(bin_iter, x)) for x in (1, exponent_bits, mantissa_bits)]

C'è molta complessità dietro quella funzione, e sarebbe abbastanza tangente da spiegare, ma se sei interessato, la risorsa importante per i nostri scopi è la modulo struct .

Python's floatè un numero a 64 bit, doppia precisione. In altri linguaggi come C, C ++, Java e C #, la doppia precisione ha un tipo separatodouble , che viene spesso implementato come 64 bit.

Quando chiamiamo quella funzione con il nostro esempio 9.2, ecco cosa otteniamo:

>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']

Interpretazione dei dati

Vedrai che ho diviso il valore restituito in tre componenti. Questi componenti sono:

  • Cartello
  • Esponente
  • Mantissa (chiamato anche Significand, o Fraction)

Cartello

Il segno è memorizzato nel primo componente come un singolo bit. È facile da spiegare: 0significa che il float è un numero positivo; 1significa che è negativo. Perché 9.2è positivo, il nostro valore del segno è 0.

Esponente

L'esponente è memorizzato nel componente centrale come 11 bit. Nel nostro caso 0b10000000010,. In decimale, questo rappresenta il valore 1026. Una stranezza di questo componente è che è necessario sottrarre un numero uguale a 2 (# di bit) - 1 - 1 per ottenere l'esponente vero; nel nostro caso, ciò significa sottrarre 0b1111111111(numero decimale 1023) per ottenere l'esponente vero 0b00000000011(numero decimale 3).

mantissa

La mantissa è memorizzata nel terzo componente come 52 bit. Tuttavia, c'è una stranezza anche per questo componente. Per capire questa stranezza, considera un numero nella notazione scientifica, come questo:

6.0221413x10 23

La mantissa sarebbe la 6.0221413. Ricordiamo che la mantissa nella notazione scientifica inizia sempre con una singola cifra diversa da zero. Lo stesso vale per il binario, tranne per il fatto che il binario ha solo due cifre: 0e 1. Quindi la mantissa binaria inizia sempre con 1! Quando viene memorizzato un galleggiante, la 1parte anteriore della mantissa binaria viene omessa per risparmiare spazio; dobbiamo rimetterlo nella parte anteriore del nostro terzo elemento per ottenere la vera mantissa:

1,0010011001100110011001100110011001100110011001100110

Ciò implica più di una semplice aggiunta, poiché i bit memorizzati nel nostro terzo componente rappresentano in realtà la parte frazionaria della mantissa, a destra del punto radicale .

Quando si tratta di numeri decimali, "spostiamo il punto decimale" moltiplicando o dividendo per poteri di 10. In binario, possiamo fare la stessa cosa moltiplicando o dividendo per poteri di 2. Poiché il nostro terzo elemento ha 52 bit, dividiamo per 2 52 per spostarlo di 52 posizioni a destra:

0,0010011001100110011001100110011001100110011001100110

In notazione decimale, che è lo stesso di dividendo 675539944105574da 4503599627370496ottenere 0.1499999999999999. (Questo è un esempio di rapporto che può essere espresso esattamente in binario, ma solo approssimativamente in decimale; per maggiori dettagli, vedere: 675539944105574/4503599627370496 .)

Ora che abbiamo trasformato il terzo componente in un numero frazionario, l'aggiunta 1dà la vera mantissa.

Ricapitolazione dei componenti

  • Segno (primo componente): 0per positivo, 1per negativo
  • Esponente (componente centrale): Sottrai 2 (numero di bit) - 1 - 1 per ottenere l'esponente vero
  • Mantissa (ultimo componente): dividi per 2 (numero di bit) e aggiungi 1per ottenere la vera mantissa

Calcolo del numero

Mettendo insieme tutte e tre le parti, ci viene dato questo numero binario:

1.0010011001100110011001100110011001100110011001100110 x 10 11

Che possiamo quindi convertire da binario a decimale:

1.1499999999999999 x 2 3 (inesatto!)

E moltiplica per rivelare la rappresentazione finale del numero che abbiamo iniziato con ( 9.2) dopo essere stato memorizzato come valore in virgola mobile:

9,1999999999999993


Rappresentando come una frazione

9.2

Ora che abbiamo creato il numero, è possibile ricostruirlo in una semplice frazione:

1.0010011001100110011001100110011001100110011001100110 x 10 11

Spostare la mantissa su un numero intero:

10010011001100110011001100110011001100110011001100110 x 10 11-110100

Converti in decimale:

5179139571476070 x 2 3-52

Sottrai l'esponente:

5179139571476070 x 2 -49

Trasforma esponente negativo in divisione:

5179139571476070/2 49

Moltiplicare esponente:

5179139571476070/562949953421312

Che equivale a:

9,1999999999999993

9.5

>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']

Già puoi vedere che la mantissa ha solo 4 cifre seguite da molti zeri. Ma andiamo attraverso i passi.

Montare la notazione scientifica binaria:

1,0011 x 10 11

Spostare il punto decimale:

10011 x 10 11-100

Sottrai l'esponente:

10011 x 10 -1

Da binario a decimale:

19 x 2 -1

Esponente negativo alla divisione:

19/2 1

Moltiplicare esponente:

19/2

È uguale a:

9.5



Ulteriori letture


1
C'è anche un bel tutorial che mostra come andare dall'altra parte - data una rappresentazione decimale di un numero, come si costruisce l'equivalente in virgola mobile. L'approccio "divisione lunga" mostra chiaramente come si finisce con un "resto" dopo aver tentato di rappresentare il numero. Dovresti aggiungerlo se vuoi essere veramente "canonico" con la tua risposta.
Floris,

1
Se stai parlando di Python e in virgola mobile, suggerirei almeno di includere il tutorial di Python nei tuoi link: docs.python.org/3.4/tutorial/floatingpoint.html Questo dovrebbe essere il punto di partenza unico risorsa per problemi in virgola mobile per programmatori Python. Se in qualche modo manca (e quasi sicuramente lo è), si prega di aprire un problema sul tracker bug Python per aggiornamenti o modifiche.
Mark Dickinson,

@mhlester Se questo viene trasformato in wiki della community, sentiti libero di incorporare la mia risposta nella tua.
Nicu Stiurca,

5
Questa risposta dovrebbe sicuramente anche essere collegata a floating-point-gui.de , poiché è probabilmente la migliore introduzione per i principianti. IMO, dovrebbe anche andare oltre "Ciò che ogni scienziato informatico dovrebbe sapere ..." - oggigiorno, le persone che possono ragionevolmente comprendere il documento di Goldberg di solito ne sono già ben consapevoli.
Daniel Pryden,

1
"Questo è un esempio di un rapporto che può essere espresso esattamente in binario, ma solo approssimativamente in decimale". Questo non è vero. Tutti questi rapporti "numerici su una potenza di due" sono esatti in decimali. Qualsiasi approssimazione serve solo ad abbreviare il numero decimale - per comodità.
Rick Regan,

29

Questa non è una risposta completa ( mhlester già coperto molto del buon terreno che non duplicherò), ma vorrei sottolineare quanto la rappresentazione di un numero dipende dalla base su cui stai lavorando.

Considera la frazione 2/3

Nella buona base 10, in genere lo scriviamo come qualcosa del genere

  • 0.666 ...
  • 0.666
  • 0.667

Quando guardiamo quelle rappresentazioni, tendiamo ad associarle ognuna con la frazione 2/3, anche se solo la prima rappresentazione è matematicamente uguale alla frazione. La seconda e la terza rappresentazione / approssimazione presentano un errore dell'ordine di 0,001, che in realtà è molto peggio dell'errore tra 9,2 e 9,199999999999999993. In effetti, la seconda rappresentazione non è nemmeno arrotondata correttamente! Tuttavia, non abbiamo un problema con 0.666 come approssimazione del numero 2/3, quindi non dovremmo davvero avere problemi con l'approssimazione di 9.2 nella maggior parte dei programmi .(Sì, in alcuni programmi è importante.)

Basi numeriche

Quindi ecco dove le basi dei numeri sono fondamentali. Se stessimo cercando di rappresentare 2/3 nella base 3, allora

(2/3) 10 = 0,2 3

In altre parole, abbiamo una rappresentazione esatta e limitata per lo stesso numero cambiando le basi! Il take-away è che anche se è possibile convertire qualsiasi numero in qualsiasi base, tutti i numeri razionali hanno rappresentazioni finite esatte in alcune basi ma non in altre .

Per guidare questo punto verso casa, diamo un'occhiata a 1/2. Potrebbe sorprenderti che sebbene questo numero perfettamente semplice abbia una rappresentazione esatta in base 10 e 2, richiede una rappresentazione ripetuta in base 3.

(1/2) 10 = 0,5 10 = 0,1 2 = 0,1111 ... 3

Perché i numeri in virgola mobile sono imprecisi?

Perché spesso, sono razionali approssimativi che non possono essere rappresentati finitamente nella base 2 (le cifre si ripetono), e in generale si stanno approssimando numeri reali (possibilmente irrazionali) che potrebbero non essere rappresentabili in molte cifre in una base.


3
Quindi, in altre parole, base-3 sarebbe perfetto 1/3proprio come base-10 è perfetto per 1/10. Nessuna delle due parti funziona in base-2
mhlester,

2
@mhlester Sì. E in generale, la base-N è perfetta per qualsiasi frazione il cui denominatore è No un suo multiplo.
Nicu Stiurca,

2
E questo è uno dei motivi per cui alcune cassette degli strumenti numerici tengono traccia di "ciò che è stato diviso per cosa", e nel processo possono mantenere la "precisione infinita" per tutti i numeri razionali. Proprio come ai fisici piace mantenere simboliche le loro equazioni fino all'ultimo momento possibile, nel caso in cui i fattori di πecc. Si annullino.
Floris,

3
@Floris Ho anche visto casi in cui un algoritmo che esegue solo l'aritmetica di base (cioè preserva la razionalità dell'input), determina se l'input era (probabilmente) razionale, esegue la matematica usando l'aritmetica in virgola mobile normale, quindi rivaluta una razionale approssimazione alla fine per correggere eventuali errori di arrotondamento. In particolare l' algoritmo a forma di scaglione ridotto di Matlab lo fa e aiuta enormemente la stabilità numerica.
Nicu Stiurca,

@SchighSchagh - interessante, non lo sapevo. So che la stabilità numerica non è sufficientemente insegnata in questi giorni di doppia doppia precisione. Ciò significa che molti mancano di conoscere l'eleganza di molti splendidi algoritmi. Mi piacciono molto gli algoritmi che calcolano e correggono i propri errori.
Floris,

13

Mentre tutte le altre risposte sono buone, manca ancora una cosa:

E 'impossibile per rappresentare i numeri irrazionali (ad esempio π, sqrt(2), log(3), ecc) proprio!

Ed è proprio per questo che vengono chiamati irrazionali. Nessuna quantità di bit storage al mondo sarebbe sufficiente per contenere anche solo uno di essi. Solo simbolico aritmetica è in grado di preservare la loro precisione.

Sebbene se limitassi la tua matematica, hai bisogno di numeri razionali, solo il problema della precisione diventa gestibile. Dovresti memorizzare una coppia di numeri interi (possibilmente molto grandi) ae bcontenere il numero rappresentato dalla frazione a/b. Tutta la tua aritmetica dovrebbe essere fatta su frazioni proprio come nella matematica delle superiori (ad es a/b * c/d = ac/bd.).

Ma naturalmente si sarebbe ancora incontrare lo stesso tipo di problemi quando pi, sqrt, log, sin, ecc sono coinvolti.

TL; DR

Per l'aritmetica con accelerazione hardware è possibile rappresentare solo una quantità limitata di numeri razionali. Ogni numero non rappresentabile è approssimato. Alcuni numeri (cioè irrazionali) non possono mai essere rappresentati indipendentemente dal sistema.


4
È interessante notare che esistono basi irrazionali. Phinary , per esempio.
Veedrac,

5
i numeri irrazionali possono essere (solo) rappresentati nella loro base. Ad esempio pi è 10 in base pi
phuclv

4
Il punto rimane valido: alcuni numeri non possono mai essere rappresentati indipendentemente dal sistema. Non guadagni nulla cambiando la tua base perché quindi alcuni altri numeri non possono più essere rappresentati.
LumpN,

4

Ci sono infiniti numeri reali (così tanti che non puoi enumerarli) e ci sono infiniti numeri razionali (è possibile enumerarli).

La rappresentazione in virgola mobile è limitata (come qualsiasi cosa in un computer), quindi inevitabilmente molti molti molti numeri sono impossibili da rappresentare. In particolare, 64 bit consentono solo di distinguere tra solo 18.446.744.073.709.551.616 valori diversi (che è nulla rispetto all'infinito). Con la convenzione standard, 9.2 non è uno di questi. Quelli che possono essere della forma m.2 ^ e per alcuni numeri interi m ed e.


Potresti trovare un sistema di numerazione diverso, basato per esempio 10, in cui 9.2 avrebbe una rappresentazione esatta. Ma altri numeri, diciamo 1/3, sarebbero ancora impossibili da rappresentare.


Si noti inoltre che i numeri in virgola mobile a precisione doppia sono estremamente precisi. Possono rappresentare qualsiasi numero in un intervallo molto ampio con un massimo di 15 cifre esatte. Per i calcoli della vita quotidiana, 4 o 5 cifre sono più che sufficienti. Non avrai mai davvero bisogno di quei 15, a meno che tu non voglia contare ogni millisecondo della tua vita.


1

Perché non possiamo rappresentare 9.2 in virgola mobile binaria?

I numeri in virgola mobile sono (semplificando leggermente) un sistema di numerazione posizionale con un numero limitato di cifre e un punto radix mobile.

Una frazione può essere espressa esattamente usando un numero finito di cifre in un sistema di numerazione posizionale se i fattori primi del denominatore (quando la frazione è espressa nei suoi termini più bassi) sono fattori della base.

I fattori primi di 10 sono 5 e 2, quindi nella base 10 possiamo rappresentare qualsiasi frazione della forma a / (2 b 5 c ).

D'altra parte, l'unico fattore primo di 2 è 2, quindi nella base 2 possiamo rappresentare solo le frazioni della forma a / (2 b )

Perché i computer usano questa rappresentazione?

Perché è un formato semplice con cui lavorare ed è sufficientemente preciso per la maggior parte degli scopi. Fondamentalmente lo stesso motivo per cui gli scienziati usano la "notazione scientifica" e arrotondano i loro risultati a un numero ragionevole di cifre ad ogni passo.

Sarebbe certamente possibile definire un formato di frazione, con (ad esempio) un numeratore a 32 bit e un denominatore a 32 bit. Sarebbe in grado di rappresentare numeri che IEEE in virgola mobile a precisione doppia non poteva, ma ugualmente ci sarebbero molti numeri che possono essere rappresentati in virgola mobile a precisione doppia che non potrebbero essere rappresentati in un formato di frazione di dimensioni fisse.

Tuttavia, il grosso problema è che un tale formato è un dolore su cui fare calcoli. Per due motivi.

  1. Se si desidera avere esattamente una rappresentazione di ciascun numero, dopo ogni calcolo è necessario ridurre la frazione ai termini più bassi. Ciò significa che per ogni operazione è praticamente necessario eseguire un grande calcolo del divisore comune.
  2. Se dopo il calcolo si ottiene un risultato non rappresentabile perché il numeratore o il denominatore è necessario trovare il risultato rappresentabile più vicino. Questo non è banale.

Alcune lingue offrono tipi di frazioni, ma di solito lo fanno in combinazione con la precisione arbitraria, questo evita la necessità di preoccuparsi delle frazioni approssimative ma crea il proprio problema, quando un numero passa attraverso un gran numero di passaggi di calcolo delle dimensioni del denominatore e quindi la memoria necessaria per la frazione può esplodere.

Alcune lingue offrono anche tipi decimali in virgola mobile, questi sono usati principalmente in scenari in cui è importante che i risultati ottenuti dal computer corrispondano a regole di arrotondamento preesistenti scritte pensando agli umani (principalmente calcoli finanziari). Questi sono leggermente più difficili da lavorare rispetto al punto mobile binario, ma il problema più grande è che la maggior parte dei computer non offre supporto hardware per loro.


-4

Prova questo

DecimalFormat decimalFormat = new DecimalFormat("#.##");
String.valueOf(decimalFormat.format(decimalValue))));

' decimalValue' è il tuo valore da convertire.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.