Considera il seguente codice:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Perché si verificano queste inesattezze?
Considera il seguente codice:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Perché si verificano queste inesattezze?
Risposte:
La matematica binaria in virgola mobile è così. Nella maggior parte dei linguaggi di programmazione, si basa sullo standard IEEE 754 . Il nocciolo del problema è che i numeri sono rappresentati in questo formato come un numero intero per una potenza di due; numeri razionali (come 0.1
, che è 1/10
) il cui denominatore non è un potere di due non può essere rappresentato esattamente.
Per 0.1
nel binary64
formato standard , la rappresentazione può essere scritta esattamente come
0.1000000000000000055511151231257827021181583404541015625
in decimale o0x1.999999999999ap-4
nella notazione hexfloat C99 .Al contrario, il numero razionale 0.1
, che è 1/10
, può essere scritto esattamente come
0.1
in decimale o0x1.99999999999999...p-4
in un analogo della notazione hexfloat C99, dove ...
rappresenta una sequenza infinita di 9.Le costanti 0.2
e 0.3
nel tuo programma saranno anche approssimazioni ai loro veri valori. Succede che il più vicino double
a 0.2
è più grande del numero razionale 0.2
ma che il più vicino double
a 0.3
è più piccolo del numero razionale 0.3
. La somma di 0.1
e 0.2
finisce per essere più grande del numero razionale 0.3
e quindi in disaccordo con la costante nel tuo codice.
Un trattamento abbastanza completo delle questioni aritmetiche in virgola mobile è quello che ogni scienziato informatico dovrebbe sapere sull'aritmetica in virgola mobile . Per una spiegazione più facile da digerire, vedere floating-point-gui.de .
Nota a margine: tutti i sistemi numerici posizionali (base-N) condividono questo problema con precisione
I numeri decimali vecchi (base 10) normali hanno gli stessi problemi, motivo per cui numeri come 1/3 finiscono con 0,333333333 ...
Ti sei appena imbattuto in un numero (3/10) che sembra essere facile da rappresentare con il sistema decimale, ma non si adatta al sistema binario. Va anche in entrambi i modi (in una certa misura): 1/16 è un brutto numero in decimale (0,0625), ma in binario sembra pulito come un decimo in decimale (0,0001) ** - se fossimo in l'abitudine di usare un sistema numerico di base 2 nella nostra vita quotidiana, guarderesti anche quel numero e capiresti istintivamente che potresti arrivare lì dimezzando qualcosa, dimezzandolo ancora e ancora e ancora e ancora.
** Certo, non è esattamente come i numeri a virgola mobile sono memorizzati nella memoria (usano una forma di notazione scientifica). Tuttavia, illustra il punto in cui gli errori binari di precisione in virgola mobile tendono ad insorgere perché i numeri del "mondo reale" con cui siamo solitamente interessati a lavorare sono così spesso potenze di dieci - ma solo perché usiamo un sistema di numeri decimali giorno - oggi. Questo è anche il motivo per cui diremo cose come il 71% invece di "5 su 7" (il 71% è un'approssimazione, poiché il 5/7 non può essere rappresentato esattamente con nessun numero decimale).
Quindi no: i numeri binari in virgola mobile non sono rotti, ma sono semplicemente imperfetti come qualsiasi altro sistema di numeri di base-N :)
Nota laterale: lavorare con i galleggianti durante la programmazione
In pratica, questo problema di precisione significa che è necessario utilizzare le funzioni di arrotondamento per arrotondare i numeri in virgola mobile a qualsiasi numero decimale a cui si è interessati prima di visualizzarli.
È inoltre necessario sostituire i test di uguaglianza con confronti che consentano una certa tolleranza, il che significa:
Do Non fareif (x == y) { ... }
Invece farlo if (abs(x - y) < myToleranceValue) { ... }
.
dove abs
è il valore assoluto. myToleranceValue
deve essere scelto per la tua specifica applicazione - e avrà molto a che fare con la quantità di "spazio di manovra" che sei disposto a consentire e quale potrebbe essere il numero più grande che stai per confrontare (a causa della perdita di problemi di precisione ). Fai attenzione alle costanti di stile "epsilon" nella tua lingua preferita. Questi non devono essere usati come valori di tolleranza.
Credo che dovrei aggiungere la prospettiva di un progettista di hardware a questo poiché ho progettato e costruito hardware a virgola mobile. Conoscere l'origine dell'errore può aiutare a capire cosa sta succedendo nel software e, in definitiva, spero che questo aiuti a spiegare i motivi per cui si verificano e sembrano accumularsi errori in virgola mobile nel tempo.
Dal punto di vista ingegneristico, la maggior parte delle operazioni in virgola mobile presenterà qualche elemento di errore poiché all'hardware che esegue i calcoli in virgola mobile è richiesto solo un errore di meno della metà di un'unità all'ultimo posto. Pertanto, gran parte dell'hardware si fermerà con una precisione necessaria solo per produrre un errore di meno della metà di un'unità all'ultimo posto per una singola operazione che è particolarmente problematica nella divisione in virgola mobile. Ciò che costituisce una singola operazione dipende da quanti operandi prende l'unità. Per la maggior parte, sono due, ma alcune unità accettano 3 o più operandi. Per questo motivo, non vi è alcuna garanzia che operazioni ripetute comporteranno un errore desiderabile poiché gli errori si sommano nel tempo.
La maggior parte dei processori segue lo standard IEEE-754 ma alcuni usano denormalizzati o standard diversi. Ad esempio, esiste una modalità denormalizzata in IEEE-754 che consente la rappresentazione di numeri in virgola mobile molto piccoli a scapito della precisione. Quanto segue, tuttavia, coprirà la modalità normalizzata di IEEE-754, che è la modalità operativa tipica.
Nello standard IEEE-754, ai progettisti hardware è consentito qualsiasi valore di errore / epsilon purché sia inferiore alla metà di un'unità nell'ultimo posto e il risultato deve essere solo meno della metà di un'unità nell'ultimo posto per un'operazione. Questo spiega perché quando ci sono operazioni ripetute, gli errori si sommano. Per la doppia precisione IEEE-754, questo è il 54 ° bit, poiché 53 bit sono usati per rappresentare la parte numerica (normalizzata), chiamata anche mantissa, del numero in virgola mobile (es. 5.3 in 5.3e5). Le sezioni successive approfondiranno le cause dell'errore hardware su varie operazioni in virgola mobile.
La causa principale dell'errore nella divisione in virgola mobile sono gli algoritmi di divisione utilizzati per calcolare il quoziente. La maggior parte dei sistemi informatici calcola la divisione utilizzando la moltiplicazione per un inverso, principalmente in Z=X/Y
,Z = X * (1/Y)
. Una divisione viene calcolata iterativamente, ovvero ogni ciclo calcola alcuni bit del quoziente fino al raggiungimento della precisione desiderata, che per IEEE-754 è qualsiasi cosa con un errore di meno di un'unità all'ultimo posto. La tabella dei reciproci di Y (1 / Y) è nota come tabella di selezione del quoziente (QST) nella divisione lenta e la dimensione in bit della tabella di selezione del quoziente è in genere la larghezza della radice, o un numero di bit di il quoziente calcolato in ogni iterazione, più alcuni bit di guardia. Per lo standard IEEE-754, doppia precisione (64 bit), sarebbe la dimensione della radice del divisore, più alcuni bit di protezione k, dove k>=2
. Quindi, ad esempio, una tipica tabella di selezione del quoziente per un divisore che calcola 2 bit del quoziente alla volta (radix 4) sarebbe 2+2= 4
bit (più alcuni bit opzionali).
3.1 Errore di arrotondamento divisione: approssimazione del reciproco
I reciproci nella tabella di selezione del quoziente dipendono dal metodo di divisione : divisione lenta come divisione SRT o divisione rapida come divisione Goldschmidt; ogni voce viene modificata secondo l'algoritmo di divisione nel tentativo di produrre il minor errore possibile. In ogni caso, tuttavia, tutti i reciproci sono approssimazionidel reciproco reale e introdurre qualche elemento di errore. Sia i metodi di divisione lenta che quelli di divisione rapida calcolano il quoziente in modo iterativo, ovvero ogni numero di bit del quoziente viene calcolato ogni passaggio, quindi il risultato viene sottratto dal dividendo e il divisore ripete i passaggi fino a quando l'errore è inferiore alla metà di uno unità all'ultimo posto. I metodi di divisione lenti calcolano un numero fisso di cifre del quoziente in ogni passaggio e sono generalmente meno costosi da costruire, mentre i metodi di divisione veloce calcolano un numero variabile di cifre per passo e sono generalmente più costosi da costruire. La parte più importante dei metodi di divisione è che la maggior parte di essi si affida alla ripetuta moltiplicazione per approssimazione di un reciproco, quindi sono soggetti a errori.
Un'altra causa degli errori di arrotondamento in tutte le operazioni sono le diverse modalità di troncamento della risposta finale consentita da IEEE-754. C'è troncato, arrotondamento verso zero, arrotondamento al più vicino (impostazione predefinita), arrotondamento per difetto e arrotondamento per eccesso . Tutti i metodi introducono un elemento di errore inferiore a un'unità all'ultimo posto per una singola operazione. Con il passare del tempo e le operazioni ripetute, il troncamento aggiunge anche cumulativamente all'errore risultante. Questo errore di troncamento è particolarmente problematico nell'esponenziazione, che comporta una qualche forma di moltiplicazione ripetuta.
Poiché l'hardware che esegue i calcoli in virgola mobile deve solo produrre un risultato con un errore di meno della metà di un'unità nell'ultima posizione per una singola operazione, l'errore crescerà su operazioni ripetute se non osservato. Questo è il motivo per cui nei calcoli che richiedono un errore limitato, i matematici usano metodi come l'uso della cifra pari arrotondata al più vicino all'ultimo posto dell'IEEE-754, perché, nel tempo, è più probabile che gli errori si annullino a vicenda out, e Interval Arithmetic combinato con variazioni delle modalità di arrotondamento IEEE 754per prevedere gli errori di arrotondamento e correggerli. A causa del suo errore relativo basso rispetto ad altre modalità di arrotondamento, arrotondare alla cifra pari più vicina (all'ultimo posto), è la modalità di arrotondamento predefinita di IEEE-754.
Si noti che la modalità di arrotondamento predefinita, cifra arrotondata alla cifra pari più vicina all'ultimo posto , garantisce un errore di meno della metà di un'unità nell'ultima posizione per un'operazione. L'uso del troncamento, dell'arrotondamento e dell'arrotondamento da soli può comportare un errore maggiore di metà dell'unità nell'ultima posizione, ma meno di un'unità nell'ultima posizione, quindi queste modalità non sono consigliate a meno che non lo siano utilizzato in aritmetica a intervalli.
In breve, la ragione fondamentale degli errori nelle operazioni in virgola mobile è una combinazione del troncamento nell'hardware e del troncamento di un reciproco in caso di divisione. Poiché lo standard IEEE-754 richiede solo un errore di meno della metà di un'unità nell'ultima posizione per una singola operazione, gli errori in virgola mobile su operazioni ripetute si sommano se non vengono corretti.
Quando si converte .1 o 1/10 in base 2 (binario) si ottiene uno schema ripetuto dopo il punto decimale, proprio come provare a rappresentare 1/3 in base 10. Il valore non è esatto e quindi non è possibile farlo matematica esatta con esso usando i normali metodi a virgola mobile.
La maggior parte delle risposte qui si rivolge a questa domanda in termini molto secchi e tecnici. Vorrei affrontarlo in termini comprensibili agli esseri umani normali.
Immagina di provare a tagliare le pizze. Hai un taglia pizza robotico in grado di tagliare le fette di pizza esattamente a metà. Può dimezzare un'intera pizza o può dimezzare una fetta esistente, ma in ogni caso, la metà è sempre esatta.
Quel taglia pizza ha movimenti molto fini, e se inizi con una pizza intera, poi dimezzala e continua a dimezzare la fetta più piccola ogni volta, puoi fare la metà 53 volte prima che la fetta sia troppo piccola anche per le sue abilità di alta precisione . A quel punto, non puoi più dimezzare quella porzione molto sottile, ma devi includerla o escluderla così com'è.
Ora, come faresti a tagliare tutte le fette in modo tale da aggiungere fino a un decimo (0,1) o un quinto (0,2) di una pizza? Pensaci davvero e prova a risolverlo. Puoi anche provare a usare una vera pizza, se hai a portata di mano un mitico cutter per pizza di precisione. :-)
I programmatori più esperti, ovviamente, conoscono la vera risposta, che è che non c'è modo di mettere insieme un decimo o quinto esatto della pizza usando quelle fette, non importa quanto finemente le tagli. Puoi fare un'approssimazione abbastanza buona, e se sommi l'approssimazione di 0,1 con l'approssimazione di 0,2, ottieni un'approssimazione abbastanza buona di 0,3, ma è comunque solo un'approssimazione.
Per i numeri a doppia precisione (che è la precisione che consente di dimezzare la pizza 53 volte), i numeri immediatamente inferiori e maggiori di 0,1 sono 0,09999999999999999167332731531132594682276248931884765625 e 0,10000000000000000555511111212121257827021181583404541015625. Il secondo è un po 'più vicino allo 0,1 rispetto al primo, quindi un parser numerico, dato un input di 0,1, favorirà il secondo.
(La differenza tra questi due numeri è la "porzione più piccola" che dobbiamo decidere di includere, il che introduce un pregiudizio verso l'alto, o di escludere, che introduce un pregiudizio verso il basso. Il termine tecnico per quella fetta più piccola è un ulp .)
Nel caso di 0,2, i numeri sono tutti uguali, appena aumentati di un fattore 2. Ancora una volta, favoriamo il valore leggermente superiore a 0,2.
Si noti che in entrambi i casi, le approssimazioni per 0,1 e 0,2 presentano una leggera inclinazione verso l'alto. Se aggiungiamo abbastanza di questi pregiudizi, spingeranno il numero sempre più lontano da ciò che vogliamo, e in effetti, nel caso di 0,1 + 0,2, il bias è abbastanza alto che il numero risultante non è più il numero più vicino a 0,3.
In particolare, 0,1 + 0,2 è in realtà 0,1000000000000000055511151231257827021181583404541015625 + 0,200000000000000011102230246251565404236316680908203125 = 0,3000099999999999999
PS Alcuni linguaggi di programmazione forniscono anche taglierine per pizza che possono dividere le fette in decimi esatti . Sebbene tali taglia pizza non siano rari, se ne hai accesso a uno, dovresti usarlo quando è importante essere in grado di ottenere esattamente un decimo o un quinto di una fetta.
Errori di arrotondamento in virgola mobile. 0.1 non può essere rappresentato con precisione in base-2 come in base-10 a causa del fattore primo mancante di 5. Proprio come 1/3 impiega un numero infinito di cifre per rappresentare in decimale, ma è "0.1" in base-3, 0.1 prende un numero infinito di cifre in base-2 dove non in base-10. E i computer non hanno una quantità infinita di memoria.
Oltre alle altre risposte corrette, potresti considerare di ridimensionare i tuoi valori per evitare problemi con l'aritmetica in virgola mobile.
Per esempio:
var result = 1.0 + 2.0; // result === 3.0 returns true
... invece di:
var result = 0.1 + 0.2; // result === 0.3 returns false
L'espressione 0.1 + 0.2 === 0.3
ritorna false
in JavaScript, ma fortunatamente l'aritmetica dei numeri interi in virgola mobile è esatta, quindi gli errori di rappresentazione decimale possono essere evitati ridimensionando.
Come esempio pratico, per evitare problemi a virgola mobile in cui la precisione è fondamentale, si raccomanda 1 al denaro maniglia come un intero che rappresenta il numero di centesimi: 2550
centesimi invece di 25.50
dollari.
1 Douglas Crockford: JavaScript: le parti buone : appendice A - Parti orribili (pagina 105) .
La mia risposta è piuttosto lunga, quindi l'ho suddivisa in tre sezioni. Poiché la domanda riguarda la matematica in virgola mobile, ho posto l'accento su ciò che la macchina fa realmente. Ho anche reso specifico alla doppia precisione (64 bit), ma l'argomento si applica ugualmente a qualsiasi aritmetica in virgola mobile.
Preambolo
Un numero di formato a virgola mobile binario (binary64) IEEE 754 rappresenta un numero del modulo
valore = (-1) ^ s * (1.m 51 m 50 ... m 2 m 1 m 0 ) 2 * 2 e-1023
a 64 bit:
1
se il numero è negativo, 0
altrimenti 1 .1.
è sempre 2 omesso poiché il bit più significativo di qualsiasi valore binario è 1
.1 - IEEE 754 consente il concetto di zero con segno - +0
e -0
sono trattati in modo diverso: 1 / (+0)
è infinito positivo; 1 / (-0)
è l'infinito negativo. Per valori zero, i bit mantissa e esponente sono tutti zero. Nota: i valori zero (+0 e -0) non sono esplicitamente classificati come denormali 2 .
2 - Questo non è il caso dei numeri denormali , che hanno un esponente offset di zero (e un implicito 0.
). L'intervallo dei numeri denormali di doppia precisione è d min ≤ | x | ≤ d max , dove d min (il numero non zero rappresentabile più piccolo) è 2 -1023 - 51 (≈ 4,94 * 10 -324 ) e d max (il numero denormale più grande, per il quale la mantissa è costituita interamente da 1
s) è 2 -1023 + 1 - 2 -1023 - 51 (≈ 2.225 * 10 -308 ).
Trasformare un numero di precisione doppia in binario
Esistono molti convertitori online per convertire un numero in virgola mobile a doppia precisione in binario (ad esempio su binaryconvert.com ), ma ecco un codice C # di esempio per ottenere la rappresentazione IEEE 754 per un numero a doppia precisione (separo le tre parti con due punti ( :
) :
public static string BinaryRepresentation(double value)
{
long valueInLongType = BitConverter.DoubleToInt64Bits(value);
string bits = Convert.ToString(valueInLongType, 2);
string leadingZeros = new string('0', 64 - bits.Length);
string binaryRepresentation = leadingZeros + bits;
string sign = binaryRepresentation[0].ToString();
string exponent = binaryRepresentation.Substring(1, 11);
string mantissa = binaryRepresentation.Substring(12);
return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}
Arrivare al punto: la domanda originale
(Passa alla fine della versione TL; DR)
Cato Johnston (l'interrogativo della domanda) ha chiesto perché 0,1 + 0,2! = 0,3.
Scritte in binario (con due punti che separano le tre parti), le rappresentazioni IEEE 754 dei valori sono:
0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010
Si noti che la mantissa è composta da cifre ricorrenti di 0011
. Questo è fondamentale per spiegare perché non v'è alcun errore ai calcoli - 0.1, 0.2 e 0.3 non possono essere rappresentati in binario precisamente in un limitato numero di bit binari non più di 1/9, 1/3 o 1/7 può essere rappresentato appunto cifre decimali .
Si noti inoltre che possiamo ridurre la potenza dell'esponente di 52 e spostare il punto nella rappresentazione binaria a destra di 52 posizioni (in modo simile a 10 -3 * 1,23 == 10 -5 * 123). Questo ci consente quindi di rappresentare la rappresentazione binaria come il valore esatto che rappresenta nella forma a * 2 p . dove 'a' è un numero intero.
Convertendo gli esponenti in decimale, rimuovendo l'offset e aggiungendo nuovamente gli impliciti 1
(tra parentesi quadre), 0.1 e 0.2 sono:
0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125
Per aggiungere due numeri, l'esponente deve essere lo stesso, ovvero:
0.1 => 2^-3 * 0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 * 1.1001100110011001100110011001100110011001100110011010
sum = 2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125
sum = 2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875
Poiché la somma non è nella forma 2 n * 1. {bbb} aumentiamo l'esponente di uno e spostiamo il punto decimale ( binario ) per ottenere:
sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
= 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875
Ora ci sono 53 bit nella mantissa (il 53esimo è tra parentesi quadre nella riga sopra). La modalità di arrotondamento predefinita per IEEE 754 è " Arrotonda al più vicino ", ovvero se un numero x è compreso tra due valori a e b , viene scelto il valore in cui il bit meno significativo è zero.
a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
= 2^-2 * 1.0011001100110011001100110011001100110011001100110011
x = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
b = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
Si noti che un e b differiscono solo per l'ultimo bit; ...0011
+ 1
= ...0100
. In questo caso, il valore con il bit meno significativo di zero è b , quindi la somma è:
sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
mentre la rappresentazione binaria di 0,3 è:
0.3 => 2^-2 * 1.0011001100110011001100110011001100110011001100110011
= 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
che differisce dalla rappresentazione binaria della somma di 0,1 e 0,2 solo per 2-54 .
Le rappresentazioni binarie di 0,1 e 0,2 sono le rappresentazioni più accurate dei numeri consentiti da IEEE 754. L'aggiunta di queste rappresentazioni, a causa della modalità di arrotondamento predefinita, produce un valore che differisce solo nel bit meno significativo.
TL; DR
Scrivendo 0.1 + 0.2
in una rappresentazione binaria IEEE 754 (con due punti che separano le tre parti) e confrontandola 0.3
, questo è (ho messo i bit distinti tra parentesi quadre):
0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3 => 0:01111111101:0011001100110011001100110011001100110011001100110[011]
Convertiti in decimali, questi valori sono:
0.1 + 0.2 => 0.300000000000000044408920985006...
0.3 => 0.299999999999999988897769753748...
La differenza è esattamente 2 -54 , che è ~ 5,5511151231258 × 10 -17 - insignificante (per molte applicazioni) rispetto ai valori originali.
Confrontare gli ultimi bit di un numero in virgola mobile è intrinsecamente pericoloso, come lo saprà chiunque legga il famoso " Ciò che ogni scienziato informatico dovrebbe sapere sull'aritmetica in virgola mobile " (che copre tutte le parti principali di questa risposta).
La maggior parte dei calcolatori utilizza cifre di guardia aggiuntive per aggirare questo problema, che è come 0.1 + 0.2
darebbe 0.3
: gli ultimi bit sono arrotondati.
I numeri in virgola mobile memorizzati nel computer sono costituiti da due parti, un numero intero e un esponente in cui la base viene presa e moltiplicata per la parte intera.
Se il computer funzionasse nella base 10, 0.1
sarebbe 1 x 10⁻¹
, 0.2
sarebbe 2 x 10⁻¹
e 0.3
sarebbe 3 x 10⁻¹
. La matematica dei numeri interi è semplice ed esatta, quindi 0.1 + 0.2
ovviamente l' aggiunta comporterà 0.3
.
I computer di solito non funzionano nella base 10, funzionano nella base 2. È ancora possibile ottenere risultati esatti per alcuni valori, ad esempio 0.5
è 1 x 2⁻¹
ed 0.25
è 1 x 2⁻²
, e aggiungendo risultati in 3 x 2⁻²
o 0.75
. Esattamente.
Il problema si presenta con numeri che possono essere rappresentati esattamente nella base 10, ma non nella base 2. Questi numeri devono essere arrotondati al loro equivalente più vicino. Supponendo il formato IEEE a virgola mobile a 64 bit molto comune, il numero più vicino a 0.1
è 3602879701896397 x 2⁻⁵⁵
e il numero più vicino a 0.2
è 7205759403792794 x 2⁻⁵⁵
; sommandoli insieme si ottiene 10808639105689191 x 2⁻⁵⁵
un valore decimale esatto di 0.3000000000000000444089209850062616169452667236328125
. I numeri in virgola mobile sono generalmente arrotondati per la visualizzazione.
Errore di arrotondamento in virgola mobile. Da quello che ogni scienziato informatico dovrebbe sapere sull'aritmetica a virgola mobile :
La compressione infinita di molti numeri reali in un numero finito di bit richiede una rappresentazione approssimativa. Sebbene ci siano infiniti numeri interi, nella maggior parte dei programmi il risultato di calcoli di numeri interi può essere memorizzato in 32 bit. Al contrario, dato qualsiasi numero fisso di bit, la maggior parte dei calcoli con numeri reali produrrà quantità che non possono essere rappresentate esattamente usando quei tanti bit. Pertanto, il risultato di un calcolo in virgola mobile deve spesso essere arrotondato per rientrare nella sua rappresentazione finita. Questo errore di arrotondamento è la caratteristica del calcolo in virgola mobile.
Sono state pubblicate molte buone risposte, ma vorrei aggiungerne un'altra.
Non tutti i numeri possono essere rappresentati tramite float / doppi. Ad esempio, il numero "0,2" sarà rappresentato come "0,200000003" con precisione singola nello standard IEEE754 in virgola mobile.
Il modello per memorizzare i numeri reali sotto il cofano rappresenta i numeri float come
Anche se puoi digitare 0.2
facilmente, FLT_RADIX
ed DBL_RADIX
è 2; non 10 per un computer con FPU che utilizza "Standard IEEE per l'aritmetica binaria a virgola mobile (ISO / IEEE Std 754-1985)".
Quindi è un po 'difficile rappresentare esattamente tali numeri. Anche se si specifica esplicitamente questa variabile senza alcun calcolo intermedio.
Alcune statistiche relative a questa famosa domanda a doppia precisione.
Quando si aggiungono tutti i valori ( a + b ) utilizzando un passaggio di 0,1 (da 0,1 a 100), si ha ~ il 15% di probabilità di errore di precisione . Si noti che l'errore potrebbe comportare valori leggermente più grandi o più piccoli. Ecco alcuni esempi:
0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)
Quando si sottraggono tutti i valori ( a - b dove a> b ) usando un passo di 0,1 (da 100 a 0,1) abbiamo ~ 34% di probabilità di errore di precisione . Ecco alcuni esempi:
0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)
* Il 15% e il 34% sono davvero enormi, quindi usa sempre BigDecimal quando la precisione è di grande importanza. Con 2 cifre decimali (passaggio 0,01) la situazione peggiora un po 'di più (18% e 36%).
Sommario
L'aritmetica in virgola mobile è esatta, sfortunatamente, non si abbina bene con la nostra solita rappresentazione di numeri in base 10, quindi si scopre che spesso gli stiamo dando un input leggermente diverso da quello che abbiamo scritto.
Anche numeri semplici come 0,01, 0,02, 0,03, 0,04 ... 0,24 non sono rappresentabili esattamente come frazioni binarie. Se conti 0,01, 0,02, 0,03 ..., non fino a 0,25 otterrai la prima frazione rappresentabile nella base 2 . Se avessi provato a usare FP, il tuo 0,01 sarebbe stato leggermente spento, quindi l'unico modo per aggiungerne 25 fino a un bel 0,25 esatto avrebbe richiesto una lunga catena di causalità che includesse bit di guardia e arrotondamento. È difficile prevedere quindi alziamo le mani e diciamo "FP is inexact", ma non è proprio vero.
Diamo costantemente all'hardware FP qualcosa che sembra semplice nella base 10 ma è una frazione ripetitiva nella base 2.
Come è successo?
Quando scriviamo in decimale, ogni frazione (in particolare ogni decimale finale) è un numero razionale del modulo
a / (2 n x 5 m )
In binario, otteniamo solo il termine 2 n , ovvero:
a / 2 n
Quindi in decimale, non possiamo rappresentare 1 / 3 . Poiché la base 10 include 2 come fattore primo, ogni numero che possiamo scrivere come una frazione binaria può anche essere scritto come una frazione di base 10. Tuttavia, quasi nulla di ciò che scriviamo come frazione di base 10 è rappresentabile in binario. Nell'intervallo compreso tra 0,01, 0,02, 0,03 ... 0,99, nel nostro formato FP possono essere rappresentati solo tre numeri: 0,25, 0,50 e 0,75, poiché sono 1/4, 1/2 e 3/4, tutti i numeri con un fattore primo usando solo il termine 2 n .
In base di 10 non possiamo rappresentare 1 / 3 . Ma in binario, non possiamo farlo 1 / 10 o 1 / 3 .
Quindi, mentre ogni frazione binaria può essere scritta in decimale, non è vero il contrario. E infatti la maggior parte delle frazioni decimali si ripete in binario.
Trattare con esso
Di solito agli sviluppatori viene chiesto di fare < confronti di epsilon , un consiglio migliore potrebbe essere quello di arrotondare ai valori integrali (nella libreria C: round () e roundf (), cioè rimanere nel formato FP) e quindi confrontare. L'arrotondamento ad una lunghezza decimale specifica della frazione risolve la maggior parte dei problemi con l'output.
Inoltre, sui reali problemi di scricchiolio dei numeri (i problemi per i quali FP è stato inventato sui primi computer spaventosamente costosi) le costanti fisiche dell'universo e tutte le altre misurazioni sono note solo a un numero relativamente piccolo di cifre significative, quindi l'intero spazio del problema era "inesatto" comunque. "Precisione" FP non è un problema in questo tipo di applicazione.
L'intero problema sorge davvero quando le persone cercano di usare FP per il conteggio dei fagioli. Funziona per questo, ma solo se ti attieni ai valori integrali, che tipo di sconfigge il punto di usarlo. Questo è il motivo per cui abbiamo tutte quelle librerie di software con frazione decimale.
Adoro la risposta di Pizza di Chris , perché descrive il problema reale, non solo il solito giro di mani sull'imprecisione. Se FP fosse semplicemente "inaccurato", potremmo risolverlo e lo avremmo fatto decenni fa. La ragione per cui non l'abbiamo è perché il formato FP è compatto e veloce ed è il modo migliore per sgranocchiare molti numeri. Inoltre, è un'eredità dell'era spaziale e della corsa agli armamenti e dei primi tentativi di risolvere grossi problemi con computer molto lenti che utilizzano piccoli sistemi di memoria. (A volte, singoli nuclei magnetici per la memorizzazione a 1 bit, ma questo è un'altra storia. )
Conclusione
Se stai semplicemente contando i bean in una banca, le soluzioni software che utilizzano innanzitutto rappresentazioni di stringhe decimali funzionano perfettamente. Ma non puoi fare la cromodinamica quantistica o l'aerodinamica in quel modo.
nextafter()
con un incremento o un decremento di interi sulla rappresentazione binaria di un float IEEE. Inoltre, puoi confrontare i galleggianti come numeri interi e ottenere la risposta giusta, tranne quando sono entrambi negativi (a causa della grandezza del segno rispetto al complemento di 2).
Hai provato la soluzione del nastro adesivo?
Prova a determinare quando si verificano errori e correggili con brevi istruzioni if, non è carino ma per alcuni problemi è l'unica soluzione e questa è una di queste.
if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
else { return n * 0.1 + 0.000000000000001 ;}
Ho avuto lo stesso problema in un progetto di simulazione scientifica in c #, e posso dirti che se ignori l'effetto farfalla si trasformerà in un grosso drago grasso e ti morderà in un **
Per offrire la migliore soluzione posso dire di aver scoperto il seguente metodo:
parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3
Lasciami spiegare perché è la soluzione migliore. Come altri menzionati nelle risposte sopra, è una buona idea utilizzare la funzione Javascript toFixed () pronta all'uso per risolvere il problema. Ma molto probabilmente incontrerai alcuni problemi.
Immagina di aggiungere due numeri float come 0.2
ed 0.7
eccolo qui:0.2 + 0.7 = 0.8999999999999999
.
Il risultato atteso è stato 0.9
che significa che in questo caso è necessario un risultato con precisione a 1 cifra. Quindi avresti dovuto usare (0.2 + 0.7).tofixed(1)
ma non puoi semplicemente dare un determinato parametro a ToFixed () poiché dipende dal numero dato, ad esempio
`0.22 + 0.7 = 0.9199999999999999`
In questo esempio hai bisogno di una precisione di 2 cifre, quindi dovrebbe essere toFixed(2)
, quindi quale dovrebbe essere il parametro per adattarsi a ogni dato numero float?
Potresti dire che sia 10 in ogni situazione, quindi:
(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"
Dannazione! Che cosa hai intenzione di fare con quegli zeri indesiderati dopo le 9? È il momento di convertirlo in float per farlo come desideri:
parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9
Ora che hai trovato la soluzione, è meglio offrirla come una funzione come questa:
function floatify(number){
return parseFloat((number).toFixed(10));
}
Proviamo tu stesso:
function floatify(number){
return parseFloat((number).toFixed(10));
}
function addUp(){
var number1 = +$("#number1").val();
var number2 = +$("#number2").val();
var unexpectedResult = number1 + number2;
var expectedResult = floatify(number1 + number2);
$("#unexpectedResult").text(unexpectedResult);
$("#expectedResult").text(expectedResult);
}
addUp();
input{
width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>
Puoi usarlo in questo modo:
var x = 0.2 + 0.7;
floatify(x); => Result: 0.9
Come W3SCHOOLS suggerisce che esiste anche un'altra soluzione, puoi moltiplicare e dividere per risolvere il problema sopra:
var x = (0.2 * 10 + 0.1 * 10) / 10; // x will be 0.3
Tieni presente che (0.2 + 0.1) * 10 / 10
non funzionerà affatto, anche se sembra lo stesso! Preferisco la prima soluzione poiché posso applicarla come una funzione che converte il float di input in float di output accurato.
Questi numeri strani compaiono perché i computer usano il sistema numerico binario (base 2) a scopo di calcolo, mentre noi usiamo i decimali (base 10).
Esiste una maggioranza di numeri frazionari che non possono essere rappresentati precisamente in binario o in decimale o entrambi. Risultato: un numero arrotondato (ma preciso) risulta.
Molti dei numerosi duplicati di questa domanda chiedono gli effetti dell'arrotondamento in virgola mobile su numeri specifici. In pratica, è più facile avere un'idea di come funziona osservando i risultati esatti dei calcoli di interesse piuttosto che leggendoli. Alcune lingue forniscono modi per farlo, come convertire un float
o double
inBigDecimal
in Java.
Poiché si tratta di una domanda indipendente dalla lingua, necessita di strumenti indipendenti dalla lingua, come un convertitore da decimale a virgola mobile .
Applicandolo ai numeri nella domanda, trattati come doppi:
0,1 converte in 0.1000000000000000055511151231257827021181583404541015625,
0,2 viene convertito in 0,200000000000000011102230246251565404236316680908203125,
0,3 converte in 0,299999999999999988897769753748434595763683319091796875 e
0,30000000000000004 converte in 0,3000000000000000444089209850062616169452667236328125.
Aggiunta manuale dei primi due numeri o in una calcolatrice decimale come Calcolatrice di precisione completa , mostra che la somma esatta degli input effettivi è 0,3000000000000000166533453693773481063544750213623046875.
Se fosse arrotondato per difetto all'equivalente di 0,3, l'errore di arrotondamento sarebbe 0,0000000000000000277555756156289135105907917022705078125. L'arrotondamento per l'equivalente di 0,30000000000000004 dà anche un errore di arrotondamento 0,0000000000000000277555756156289135105907917022705078125. Si applica il pareggio tondo-uniforme.
Tornando al convertitore a virgola mobile, l'esadecimale grezzo per 0,30000000000000004 è 3fd333333333333334, che termina con una cifra pari e quindi è il risultato corretto.
Dato che nessuno ha menzionato questo ...
Alcuni linguaggi di alto livello come Python e Java sono dotati di strumenti per superare le limitazioni binarie in virgola mobile. Per esempio:
decimal
Modulo Python e BigDecimal
classe Java , che rappresentano numeri internamente con notazione decimale (in contrapposizione alla notazione binaria). Entrambi hanno una precisione limitata, quindi sono ancora soggetti a errori, tuttavia risolvono i problemi più comuni con l'aritmetica binaria in virgola mobile.
I decimali sono molto utili quando si tratta di denaro: dieci centesimi più venti centesimi sono sempre esattamente trenta centesimi:
>>> 0.1 + 0.2 == 0.3
False
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
Il decimal
modulo di Python si basa sullo standard IEEE 854-1987 .
fractions
Modulo di Python e BigFraction
classe di Apache Common . Entrambi rappresentano numeri razionali come (numerator, denominator)
coppie e possono dare risultati più precisi dell'aritmetica decimale in virgola mobile.
Nessuna di queste soluzioni è perfetta (soprattutto se guardiamo alle prestazioni o se richiediamo una precisione molto elevata), ma risolvono ancora un gran numero di problemi con l'aritmetica binaria in virgola mobile.
Posso solo aggiungere; la gente presume sempre che si tratti di un problema con il computer, ma se conti con le mani (base 10), non puoi ottenere a (1/3+1/3=2/3)=true
meno che tu non abbia l'infinito per aggiungere 0,333 ... a 0,333 ... così come con il (1/10+2/10)!==3/10
problema nella base 2, lo tronchi a 0,333 + 0,333 = 0,666 e probabilmente lo arrotondi a 0,667 che sarebbe anche tecnicamente impreciso.
Conta in ternario, e i terzi non sono un problema però - forse una corsa con 15 dita per mano chiederebbe perché la tua matematica decimale è stata rotta ...
Il tipo di matematica a virgola mobile che può essere implementata in un computer digitale utilizza necessariamente un'approssimazione dei numeri reali e delle operazioni su di essi. (La versione standard si estende su oltre cinquanta pagine di documentazione e ha un comitato per gestire i suoi errori e ulteriori perfezionamenti.)
Questa approssimazione è una miscela di approssimazioni di diverso tipo, ognuna delle quali può essere ignorata o spiegata con attenzione a causa del suo modo specifico di deviazione dall'esattezza. Implica anche una serie di casi eccezionali espliciti a livello sia hardware che software che la maggior parte delle persone passa davanti fingendo di non notare.
Se hai bisogno di una precisione infinita (usando il numero π, ad esempio, invece di uno dei suoi numerosi stand-in più brevi), dovresti invece scrivere o usare un programma matematico simbolico.
Ma se sei d'accordo con l'idea che a volte la matematica in virgola mobile è sfocata in termini di valore e la logica e gli errori possono accumularsi rapidamente e puoi scrivere i tuoi requisiti e test per consentirlo, allora il tuo codice può spesso cavarsela con ciò che è dentro la tua FPU.
Solo per divertimento, ho giocato con la rappresentazione dei float, seguendo le definizioni dello Standard C99 e ho scritto il codice qui sotto.
Il codice stampa la rappresentazione binaria dei float in 3 gruppi separati
SIGN EXPONENT FRACTION
e successivamente stampa una somma che, se sommata con sufficiente precisione, mostrerà il valore che esiste realmente nell'hardware.
Quindi, quando scrivi float x = 999...
, il compilatore trasformerà quel numero in una rappresentazione di bit stampata dalla funzione in modo xx
tale che la somma stampata dalla funzione yy
sia uguale al numero dato.
In realtà, questa somma è solo un'approssimazione. Per il numero 999.999.999 il compilatore inserirà nella rappresentazione in bit del float il numero 1.000.000.000
Dopo il codice allego una sessione di console, in cui calcolo la somma dei termini per entrambe le costanti (meno PI e 999999999) che esiste realmente nell'hardware, inserita lì dal compilatore.
#include <stdio.h>
#include <limits.h>
void
xx(float *x)
{
unsigned char i = sizeof(*x)*CHAR_BIT-1;
do {
switch (i) {
case 31:
printf("sign:");
break;
case 30:
printf("exponent:");
break;
case 23:
printf("fraction:");
break;
}
char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
printf("%d ", b);
} while (i--);
printf("\n");
}
void
yy(float a)
{
int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
int fraction = ((1<<23)-1)&(*(int*)&a);
int exponent = (255&((*(int*)&a)>>23))-127;
printf(sign?"positive" " ( 1+":"negative" " ( 1+");
unsigned int i = 1<<22;
unsigned int j = 1;
do {
char b=(fraction&i)!=0;
b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
} while (j++, i>>=1);
printf("*2^%d", exponent);
printf("\n");
}
void
main()
{
float x=-3.14;
float y=999999999;
printf("%lu\n", sizeof(x));
xx(&x);
xx(&y);
yy(x);
yy(y);
}
Ecco una sessione della console in cui computo il valore reale del float che esiste nell'hardware. Ho usato bc
per stampare la somma di termini emessi dal programma principale. Si può inserire quella somma in Python repl
o anche qualcosa di simile.
-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872
Questo è tutto. Il valore di 999999999 è in effetti
999999999.999999446351872
Puoi anche verificare bc
che -3.14 sia anche perturbato. Non dimenticare di impostare un scale
fattore bc
.
La somma visualizzata è ciò che è all'interno dell'hardware. Il valore che si ottiene calcolandolo dipende dalla scala impostata. Ho impostato il scale
fattore su 15. Matematicamente, con precisione infinita, sembra che sia 1.000.000.000.
Un altro modo di vedere questo: sono usati 64 bit per rappresentare i numeri. Di conseguenza non è possibile rappresentare con precisione più di 2 ** 64 = 18.446.744.073.709.551.616 numeri diversi.
Tuttavia, la matematica afferma che ci sono già infiniti decimali tra 0 e 1. IEE 754 definisce una codifica per utilizzare questi 64 bit in modo efficiente per uno spazio numerico molto più ampio più NaN e +/- Infinity, quindi ci sono spazi vuoti tra numeri rappresentati accuratamente riempiti con numeri solo approssimativi.
Sfortunatamente lo 0,3 si trova in una lacuna.
Immagina di lavorare nella base dieci con, diciamo, 8 cifre di precisione. Tu controlli se
1/3 + 2 / 3 == 1
e scopri che questo ritorna false
. Perché? Bene, come numeri reali abbiamo
1/3 = 0.333 .... e 2/3 = 0.666 ....
Troncando con otto cifre decimali, otteniamo
0.33333333 + 0.66666666 = 0.99999999
che è, ovviamente, diverso 1.00000000
da esattamente 0.00000001
.
La situazione per i numeri binari con un numero fisso di bit è esattamente analoga. Come numeri reali, abbiamo
1/10 = 0.0001100110011001100 ... (base 2)
e
1/5 = 0,0011001100110011001 ... (base 2)
Se li troncassimo a, diciamo, a sette bit, otterremmo
0.0001100 + 0.0011001 = 0.0100101
mentre d'altra parte,
3/10 = 0.01001100110011 ... (base 2)
che, troncato a sette bit, è 0.0100110
e questi differiscono esattamente 0.0000001
.
La situazione esatta è leggermente più sottile perché questi numeri sono in genere memorizzati in notazione scientifica. Quindi, ad esempio, invece di memorizzare 1/10 in quanto 0.0001100
potremmo memorizzarlo come qualcosa di simile 1.10011 * 2^-4
, a seconda di quanti bit abbiamo allocato per l'esponente e la mantissa. Ciò influisce sul numero di cifre di precisione ottenute per i calcoli.
Il risultato è che, a causa di questi errori di arrotondamento, in pratica non si vuole mai usare == su numeri in virgola mobile. Invece, puoi verificare se il valore assoluto della loro differenza è minore di un piccolo numero fisso.
Da Python 3.5 puoi usare la math.isclose()
funzione per testare l'uguaglianza approssimativa:
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
Poiché questa discussione si è ramificata un po 'in una discussione generale sulle attuali implementazioni in virgola mobile, aggiungerei che ci sono progetti per risolvere i loro problemi.
Dai un'occhiata a https://posithub.org/ per esempio, che mostra un tipo di numero chiamato posit (e il suo unum precedente) che promette di offrire una migliore precisione con meno bit. Se la mia comprensione è corretta, risolve anche il tipo di problemi nella domanda. Un progetto abbastanza interessante, la persona dietro di esso è un matematico, Dr. John Gustafson . Il tutto è open source, con molte implementazioni attuali in C / C ++, Python, Julia e C # ( https://hastlayer.com/arithmetics ).
In realtà è piuttosto semplice. Quando hai un sistema base 10 (come il nostro), può solo esprimere frazioni che usano un fattore primo della base. I fattori primi di 10 sono 2 e 5. Quindi 1/2, 1/4, 1/5, 1/8 e 1/10 possono tutti essere espressi in modo chiaro perché tutti i denominatori usano fattori primi di 10. Al contrario, 1 / 3, 1/6 e 1/7 sono tutti decimali ripetuti perché i loro denominatori usano un fattore primo di 3 o 7. In binario (o base 2), l'unico fattore primo è 2. Quindi puoi esprimere solo le frazioni in modo pulito che contiene solo 2 come fattore primo. In binario, 1/2, 1/4, 1/8 sarebbero tutti espressi in modo pulito come decimali. Mentre 1/5 o 1/10 ripetono i decimali. Quindi 0.1 e 0.2 (1/10 e 1/5) mentre i decimali puliti in un sistema di base 10, stanno ripetendo i decimali nel sistema di base 2 in cui il computer sta operando. Quando fai matematica su questi decimali ripetuti,
Numeri decimali come 0.1
, 0.2
e, 0.3
non sono rappresentati esattamente in tipi binari in virgola mobile codificati. La somma delle approssimazioni per 0.1
e 0.2
differisce dall'approssimazione utilizzata per 0.3
, quindi la falsità di 0.1 + 0.2 == 0.3
come può essere vista più chiaramente qui:
#include <stdio.h>
int main() {
printf("0.1 + 0.2 == 0.3 is %s\n", 0.1 + 0.2 == 0.3 ? "true" : "false");
printf("0.1 is %.23f\n", 0.1);
printf("0.2 is %.23f\n", 0.2);
printf("0.1 + 0.2 is %.23f\n", 0.1 + 0.2);
printf("0.3 is %.23f\n", 0.3);
printf("0.3 - (0.1 + 0.2) is %g\n", 0.3 - (0.1 + 0.2));
return 0;
}
Produzione:
0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17
Affinché questi calcoli siano valutati in modo più affidabile, è necessario utilizzare una rappresentazione basata su decimali per i valori in virgola mobile. Lo standard C non specifica tali tipi per impostazione predefinita ma come estensione descritta in un rapporto tecnico .
I _Decimal32
, _Decimal64
e le _Decimal128
tipologie potrebbero essere disponibili sul vostro sistema (per esempio, GCC li sostiene sul target selezionati , ma Clang non sono supportati su OS X ).
Math.sum (javascript) .... tipo di sostituzione dell'operatore
.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001
Object.defineProperties(Math, {
sign: {
value: function (x) {
return x ? x < 0 ? -1 : 1 : 0;
}
},
precision: {
value: function (value, precision, type) {
var v = parseFloat(value),
p = Math.max(precision, 0) || 0,
t = type || 'round';
return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
}
},
scientific_to_num: { // this is from https://gist.github.com/jiggzson
value: function (num) {
//if the number is in scientific notation remove it
if (/e/i.test(num)) {
var zero = '0',
parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
e = parts.pop(), //store the exponential part
l = Math.abs(e), //get the number of zeros
sign = e / l,
coeff_array = parts[0].split('.');
if (sign === -1) {
num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
} else {
var dec = coeff_array[1];
if (dec)
l = l - dec.length;
num = coeff_array.join('') + new Array(l + 1).join(zero);
}
}
return num;
}
}
get_precision: {
value: function (number) {
var arr = Math.scientific_to_num((number + "")).split(".");
return arr[1] ? arr[1].length : 0;
}
},
sum: {
value: function () {
var prec = 0, sum = 0;
for (var i = 0; i < arguments.length; i++) {
prec = this.max(prec, this.get_precision(arguments[i]));
sum += +arguments[i]; // force float to convert strings to number
}
return Math.precision(sum, prec);
}
}
});
l'idea è di usare la matematica invece degli operatori per evitare errori float
Math.sum rileva automaticamente la precisione da utilizzare
Math.sum accetta un numero qualsiasi di argomenti
Considera i seguenti risultati:
error = (2**53+1) - int(float(2**53+1))
>>> (2**53+1) - int(float(2**53+1))
1
Possiamo chiaramente vedere un punto di interruzione quando 2**53+1
- tutto funziona bene fino a quando 2**53
.
>>> (2**53) - int(float(2**53))
0
Ciò accade a causa del binario a doppia precisione: IEEE 754 formato binario a virgola mobile a doppia precisione: binary64
Dalla pagina Wikipedia per il formato a virgola mobile a precisione doppia :
Il virgola mobile binaria a doppia precisione è un formato comunemente usato sui PC, grazie alla sua gamma più ampia rispetto alla virgola mobile a precisione singola, nonostante le sue prestazioni e il costo della larghezza di banda. Come nel formato a virgola mobile a precisione singola, manca di precisione sui numeri interi se confrontato con un formato intero della stessa dimensione. È comunemente noto semplicemente come doppio. Lo standard IEEE 754 specifica un binary64 come:
- Bit di segno: 1 bit
- Esponente: 11 bit
- Precisione significativa: 53 bit (52 memorizzati in modo esplicito)
Il valore reale assunto da un dato riferimento a doppia precisione a 64 bit con un dato esponente distorto e una frazione di 52 bit è
o
Grazie a @a_guest per avermelo segnalato.
Una domanda diversa è stata nominata come duplicata di questa:
In C ++, perché il risultato è cout << x
diverso dal valore mostrato da un debugger x
?
L' x
nella questione è una float
variabile.
Un esempio sarebbe
float x = 9.9F;
Il debugger mostra 9.89999962
, l'output cout
dell'operazione è 9.9
.
La risposta risulta essere cout
la precisione predefinita per float
6, quindi arrotonda a 6 cifre decimali.
Vedi qui per riferimento