Perché l'aggiunta di 0,1 volte multiple rimane senza perdita?


152

So che il 0.1numero decimale non può essere rappresentato esattamente con un numero binario finito ( spiegazione ), quindi double n = 0.1perderà una certa precisione e non sarà esattamente 0.1. D'altra parte 0.5può essere rappresentato esattamente perché lo è 0.5 = 1/2 = 0.1b.

Detto questo, è comprensibile che l'aggiunta di 0.1 tre volte non darà esattamente 0.3così il seguente codice stampa false:

double sum = 0, d = 0.1;
for (int i = 0; i < 3; i++)
    sum += d;
System.out.println(sum == 0.3); // Prints false, OK

Ma allora come è possibile aggiungere esattamente 0.1 cinque volte0.5 ? Viene stampato il seguente codice true:

double sum = 0, d = 0.1;
for (int i = 0; i < 5; i++)
    sum += d;
System.out.println(sum == 0.5); // Prints true, WHY?

Se 0.1non può essere rappresentato esattamente, com'è che aggiungerlo 5 volte dà esattamente 0.5quale può essere rappresentato con precisione?


7
Se lo cerchi davvero, sono sicuro che puoi capirlo, ma il virgola mobile è pieno di "sorprese", e talvolta è meglio guardare semplicemente con meraviglia.
Hot Licks,

3
Ci stai pensando in un modo math. L'aritmetica in virgola mobile non è matematica in alcun modo.
Jakob,

13
@HotLicks che è molto molto l'atteggiamento sbagliato avere.
Hobbs

2
@RussellBorogove anche se fosse stato ottimizzato, sarebbe un'ottimizzazione valida se sumavesse lo stesso valore finale come se il loop fosse realmente eseguito. Nello standard C ++ questa viene chiamata "regola as-if" o "stesso comportamento osservabile".
Hobbs

7
@Jakob non è affatto vero. L'aritmetica in virgola mobile è rigorosamente definita, con un buon trattamento matematico dei limiti di errore e simili. È solo che molti programmatori o non sono disposti a seguire l'analisi, oppure credono erroneamente che "il virgola mobile sia inesatto" è tutto ciò che c'è da sapere e che l'analisi non vale la pena preoccuparsi.
Hobbs

Risposte:


155

L'errore di arrotondamento non è casuale e il modo in cui viene implementato tenta di minimizzare l'errore. Ciò significa che a volte l'errore non è visibile o non c'è errore.

Ad esempio 0.1non è esattamente 0.1cioè new BigDecimal("0.1") < new BigDecimal(0.1)ma 0.5è esattamente1.0/2

Questo programma mostra i veri valori coinvolti.

BigDecimal _0_1 = new BigDecimal(0.1);
BigDecimal x = _0_1;
for(int i = 1; i <= 10; i ++) {
    System.out.println(i+" x 0.1 is "+x+", as double "+x.doubleValue());
    x = x.add(_0_1);
}

stampe

0.1000000000000000055511151231257827021181583404541015625, as double 0.1
0.2000000000000000111022302462515654042363166809082031250, as double 0.2
0.3000000000000000166533453693773481063544750213623046875, as double 0.30000000000000004
0.4000000000000000222044604925031308084726333618164062500, as double 0.4
0.5000000000000000277555756156289135105907917022705078125, as double 0.5
0.6000000000000000333066907387546962127089500427246093750, as double 0.6000000000000001
0.7000000000000000388578058618804789148271083831787109375, as double 0.7000000000000001
0.8000000000000000444089209850062616169452667236328125000, as double 0.8
0.9000000000000000499600361081320443190634250640869140625, as double 0.9
1.0000000000000000555111512312578270211815834045410156250, as double 1.0

Nota: 0.3è leggermente disattivato, ma quando si arriva ai 0.4bit è necessario spostarne uno in basso per adattarsi al limite di 53 bit e l'errore viene scartato. Ancora una volta, una striscia di errore indietro a 0.6e 0.7ma 0.8per 1.0l'errore viene scartata.

Aggiungendolo 5 volte si dovrebbe accumulare l'errore, non annullarlo.

Il motivo per cui si verifica un errore è dovuto alla precisione limitata. cioè 53 bit. Ciò significa che poiché il numero utilizza più bit man mano che aumenta, i bit devono essere eliminati alla fine. Ciò provoca arrotondamenti che in questo caso sono a tuo favore.
Puoi ottenere l'effetto opposto quando ottieni un numero più piccolo, ad es. 0.1-0.0999=> 1.0000000000000286E-4 E vedi più errori di prima.

Un esempio di questo è il motivo per cui in Java 6 Perché Math.round (0.49999999999999994) restituisce 1 In questo caso la perdita di un po 'nel calcolo comporta una grande differenza nella risposta.


1
Dove viene implementato?
EpicPandaForce,

16
@Zhuinden La CPU segue lo standard IEEE-754. Java ti dà accesso alle istruzioni della CPU sottostante e non ti coinvolge. en.wikipedia.org/wiki/IEEE_floating_point
Peter Lawrey

10
@PeterLawrey: non necessariamente la CPU. Su una macchina senza virgola mobile nella CPU (e nessuna FPU separata in uso), l'aritmetica IEEE verrà eseguita dal software. E se la CPU host ha virgola mobile ma non è conforme ai requisiti IEEE, penso che un'implementazione Java per quella CPU sarebbe obbligata a usare anche il float ...
R .. GitHub STOP HELPING ICE

1
@R .. nel qual caso non so cosa succederebbe se usassi strictfp Time per considerare numeri a virgola fissa penso. (o BigDecimal)
Peter Lawrey,

2
@eugene il problema chiave è che i valori limitati in virgola mobile possono rappresentare. Questa limitazione può comportare una perdita di informazioni e quando il numero aumenta una perdita di errore. Utilizza l'arrotondamento, ma in questo caso viene arrotondato per difetto, quindi quello che sarebbe stato un numero leggermente troppo grande in quanto 0,1 è leggermente troppo grande, si trasforma nel valore corretto. Esattamente 0,5
Peter Lawrey,

47

L'overflow di blocco, in virgola mobile, x + x + xè esattamente il numero in virgola mobile correttamente arrotondato (cioè il più vicino) al reale 3 * x, x + x + x + xè esattamente 4 * xed x + x + x + x + xè di nuovo l'approssimazione in virgola mobile correttamente arrotondata per 5 * x.

Il primo risultato, per x + x + x, deriva dal fatto che x + xè esatto. x + x + xè quindi il risultato di un solo arrotondamento.

Il secondo risultato è più difficile, una dimostrazione che è discusso qui (e Stephen Canon allude ad un'altra prova mediante analisi caso delle ultime 3 cifre x). Per riassumere, 3 * xè nello stesso binade di 2 * xo è nello stesso binade di 4 * x, e in ogni caso è possibile dedurre che l'errore sulla terza aggiunta annulla l'errore sulla seconda aggiunta (il la prima aggiunta è esatta, come abbiamo già detto).

Il terzo risultato, " x + x + x + x + xè arrotondato correttamente", deriva dal secondo nello stesso modo in cui il primo deriva dall'esattezza di x + x.


Il secondo risultato spiega perché 0.1 + 0.1 + 0.1 + 0.1è esattamente il numero in virgola mobile 0.4: i numeri razionali 1/10 e 4/10 vengono approssimati allo stesso modo, con lo stesso errore relativo, quando convertiti in virgola mobile. Questi numeri in virgola mobile hanno un rapporto esattamente di 4 tra di loro. Il primo e il terzo risultato mostrano che 0.1 + 0.1 + 0.1e ci si 0.1 + 0.1 + 0.1 + 0.1 + 0.1può aspettare che abbiano meno errori di quanti ne possano dedurre dall'analisi dell'errore ingenuo, ma, di per sé, mettono in relazione solo i risultati rispettivamente con 3 * 0.1e 5 * 0.1, che ci si può aspettare che siano vicini ma non necessariamente identici a 0.3e 0.5.

Se si mantiene l'aggiunta 0.1dopo il quarto Inoltre, si potrà finalmente osservare errori di arrotondamento che rendono “ 0.1aggiunto a se stesso n volte” divergono da n * 0.1, e divergono ancora di più da N / 10. Se dovessi tracciare i valori di "0.1 aggiunti a se stesso n volte" in funzione di n, osserveresti le linee di pendenza costante per binade (non appena il risultato dell'ennesima aggiunta è destinato a cadere in un binade particolare, ci si può aspettare che le proprietà dell'aggiunta siano simili alle aggiunte precedenti che hanno prodotto un risultato nello stesso binade). All'interno di uno stesso binade, l'errore aumenterà o diminuirà. Se dovessi guardare la sequenza delle pendenze da binata a binata, riconosceresti le cifre ripetitive di0.1in binario per un po '. Successivamente, l'assorbimento inizierebbe e la curva si appiattirebbe.


1
Nella prima riga stai dicendo che x + x + x è esattamente corretto, ma dall'esempio nella domanda non lo è.
Alboz,

2
@Alboz dico che x + x + xè esattamente il numero in virgola mobile correttamente arrotondato al reale 3 * x. "Arrotondato correttamente" significa "più vicino" in questo contesto.
Pascal Cuoq,

4
+1 Questa dovrebbe essere la risposta accettata. In realtà offre spiegazioni / prove di ciò che sta accadendo piuttosto che solo vaghe generalità.
R .. GitHub FERMA DI AIUTARE ICE

1
@Alboz (tutto previsto dalla domanda). Ma ciò che questa risposta spiega è come gli errori si annullano casualmente anziché sommarsi nel peggiore dei casi.
Hobbs

1
@chebus 0.1 è 0x1.999999999999999999999… p-4 in esadecimale (una sequenza infinita di cifre). Viene approssimato in doppia precisione come 0x1.99999ap-4. 0.2 è 0x1.999999999999999999999… p-3 in esadecimale. Per lo stesso motivo per cui 0,1 è approssimato come 0x1,99999ap-4, 0,2 è approssimato come 0x1,99999ap-3. Nel frattempo, 0x1.99999ap-3 è anche esattamente 0x1.99999ap-4 + 0x1.99999ap-4.
Pascal Cuoq,

-1

I sistemi a virgola mobile eseguono varie magie, tra cui la possibilità di arrotondare alcuni pezzi di precisione in più. Quindi l'errore molto piccolo dovuto alla rappresentazione inesatta di 0,1 finisce per arrotondarsi a 0,5.

Pensa al virgola mobile come un modo fantastico ma INESATTO di rappresentare i numeri. Non tutti i numeri possibili sono facilmente rappresentati in un computer. Numeri irrazionali come PI. O come SQRT (2). (I sistemi matematici simbolici possono rappresentarli, ma ho detto "facilmente".)

Il valore in virgola mobile può essere estremamente vicino, ma non esatto. Potrebbe essere così vicino che potresti navigare verso Plutone e partire per millimetri. Ma ancora non esatto in senso matematico.

Non utilizzare il virgola mobile quando è necessario essere esatti anziché approssimativi. Ad esempio, le applicazioni di contabilità vogliono tenere traccia esatta di un certo numero di penny in un account. I numeri interi vanno bene perché sono esatti. Il problema principale da tenere d'occhio con gli interi è l'overflow.

L'uso di BigDecimal per la valuta funziona bene perché la rappresentazione sottostante è un numero intero, sebbene grande.

Riconoscendo che i numeri in virgola mobile sono inesatti, hanno ancora molti usi. Sistemi di coordinate per la navigazione o coordinate nei sistemi grafici. Valori astronomici. Valori scientifici. (Probabilmente non puoi conoscere l'esatta massa di una palla da baseball all'interno di una massa di un elettrone, quindi l'inesattezza non ha molta importanza.)

Per il conteggio delle applicazioni (inclusa la contabilità) utilizzare numeri interi. Per contare il numero di persone che attraversano un cancello, usa int o long.


2
La domanda è taggata [java]. La definizione del linguaggio Java non prevede "pochi bit extra di precisione", solo pochi bit esponenti extra (e questo è solo se non lo si utilizza strictfp). Solo perché hai rinunciato a capire qualcosa non significa che sia insondabile né che altri debbano rinunciare a capirlo. Vedi stackoverflow.com/questions/18496560 come esempio delle lunghezze che verranno implementate da Java per implementare la definizione del linguaggio (che non include disposizioni per bit di precisione extra né, constrictfp , per bit di espansione extra)
Pascal Cuoq
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.