Disuguaglianza causata dall'inesattezza del galleggiante


15

Almeno in Java, se scrivo questo codice:

float a = 1000.0F;
float b = 0.00004F;
float c = a + b + b;
float d = b + b + a;
boolean e = c == d;

il valore di sarebbe . Credo che ciò sia causato dal fatto che i float sono molto limitati nel modo di rappresentare con precisione i numeri. Ma non capisco perché cambiare la posizione di a possa causare questa disuguaglianza.efalsea

Ho ridotto la b s a una in entrambe le righe 3 e 4 come sotto, ma il valore di e diventa true :

float a = 1000.0F;
float b = 0.00004F;
float c = a + b;
float d = b + a;
boolean e = c == d;

Che cosa è successo esattamente nelle linee 3 e 4? Perché le operazioni di addizione con float non sono associative?

Grazie in anticipo.


16
Come mostra il tuo esempio, l'aggiunta in virgola mobile è commutativa. Ma non è associativo.
Yuval Filmus,

1
Ti incoraggio a cercare le definizioni di base. Si noti inoltre che il compilatore analizza r+s+t come (r+s)+t (addizione associata a sinistra).
Yuval Filmus,

2
Per un modo semplice per capire perché dovrebbe essere così, considera Xun numero molto grande e Yun numero molto piccolo, tale che X + Y = X. Qui, X + Y + -Xsarà zero. Ma X + -X + Ylo sarà Y.
David Schwartz,


Risposte:


20

Nelle tipiche implementazioni in virgola mobile, il risultato di una singola operazione viene prodotto come se l'operazione fosse eseguita con precisione infinita e quindi arrotondata al numero in virgola mobile più vicino.

Confronta e : il risultato di ogni operazione eseguita con precisione infinita è lo stesso, quindi questi risultati identici di precisione infinita sono arrotondati in modo identico. In altre parole, l'aggiunta in virgola mobile è commutativa.un'+BB+un'

Prendi : è un numero in virgola mobile. Con i numeri binari in virgola mobile, è anche un numero in virgola mobile (l'esponente è maggiore di uno), quindi viene aggiunto senza errori di arrotondamento. Quindi viene aggiunto il valore esatto . Il risultato è il valore esatto , arrotondato al numero in virgola mobile più vicino.B+B+un'B2BB+Bun'B+B2B+un'

Prendi : viene aggiunto a e si verificherà un errore di arrotondamento , quindi si ottiene il risultato . Aggiungi e il risultato è il valore esatto , arrotondato al numero in virgola mobile più vicino.un'+B+Bun'+Brun'+B+rB2B+un'+r

Quindi, in un caso, , arrotondato. Nell'altro caso, , arrotondato.2B+un'2B+un'+r

PS. Sia per due numeri particolari e entrambi calcoli danno lo stesso risultato o non dipende dai numeri, e l'errore di arrotondamento nel calcolo , e di solito è difficile da prevedere. L'uso della precisione singola o doppia in linea di principio non fa differenza per il problema, ma poiché gli errori di arrotondamento sono diversi, ci saranno valori di aeb dove in singola precisione i risultati sono uguali e in doppia precisione non lo sono, o viceversa. La precisione sarà molto più elevata, ma il problema che due espressioni sono matematicamente uguali ma non uguali nell'aritmetica in virgola mobile rimane lo stesso.un'Bun'+B

PPS. In alcune lingue, l'aritmetica in virgola mobile può essere eseguita con una precisione più elevata o un intervallo di numeri più elevato rispetto a quanto indicato dalle istruzioni effettive. In tal caso, sarebbe molto più probabile (ma ancora non garantito) che entrambe le somme diano lo stesso risultato.

PPPS. Un commento ci ha chiesto se dovremmo chiederci se i numeri in virgola mobile sono uguali o no. Assolutamente se sai cosa stai facendo. Ad esempio, se si ordina un array o si implementa un set, ci si mette in guai terribili se si desidera utilizzare una nozione di "approssimativamente uguale". In un'interfaccia utente grafica, potrebbe essere necessario ricalcolare le dimensioni degli oggetti se la dimensione di un oggetto è cambiata: si confronta oldSize == newSize per evitare tale ricalcolo, sapendo che in pratica non si hanno quasi dimensioni quasi identiche e il programma è corretto anche se c'è un ricalcolo inutile.


In questo caso particolare, b diventa periodico quando convertito in binario, quindi ci sono errori di arrotondamento ovunque.
André Souza Lemos,

1
@ AndréSouzaLemos bin questa risposta non è 0,00004, è quello che ottieni dopo la conversione e l'arrotondamento.
Alexey Romanov,

"Nelle tipiche implementazioni in virgola mobile, il risultato di una singola operazione viene prodotto come se l'operazione fosse eseguita con precisione infinita e quindi arrotondata al numero in virgola mobile più vicino". quando ho provato a implementarlo effettivamente in termini di porte logiche (il simulatore poteva gestire solo bus a 64 bit).
John Dvorak,

Domanda ingenua: il test per l'uguaglianza del galleggiante ha mai senso? Perché la maggior parte dei linguaggi di programmazione consente un test aa == b in cui entrambi o uno è un float?
curious_cat

Definizione pertinente da Wikipedia: " Machine Epsilon fornisce un limite superiore all'errore relativo dovuto all'arrotondamento nell'aritmetica in virgola mobile."
Blackhawk,

5

Il formato binario in virgola mobile supportato dai computer è essenzialmente simile alla notazione scientifica decimale utilizzata dall'uomo.

Un numero a virgola mobile è costituito da un segno, una mantissa (larghezza fissa) e un esponente (larghezza fissa), in questo modo:

+/-  1.0101010101 × 2^12345
sign   ^mantissa^     ^exp^

La notazione scientifica regolare ha un formato simile:

+/- 1.23456 × 10^99

Se eseguiamo l'aritmetica in notazione scientifica con precisione finita, arrotondando dopo ogni operazione, otteniamo tutti gli stessi effetti negativi del punto mobile binario.


Esempio

Per illustrare, supponiamo di usare esattamente 3 cifre dopo il punto decimale.

a = 99990 = 9.999 × 10^4
b =     3 = 3.000 × 10^0

(a + b) + b

Ora calcoliamo:

c = a + b
  = 99990 + 3      (exact)
  = 99993          (exact)
  = 9.9993 × 10^4  (exact)
  = 9.999 × 10^4.  (rounded to nearest)

Nel prossimo passaggio, ovviamente:

d = c + b
  = 99990 + 3 = ...
  = 9.999 × 10^4.  (rounded to nearest)

Quindi (a + b) + b = 9.999 × 10 4 .

(b + b) + a

Ma se eseguissimo le operazioni in un ordine diverso:

e = b + b
  = 3 + 3  (exact)
  = 6      (exact)
  = 6.000 × 10^0.  (rounded to nearest)

Quindi calcoliamo:

f = e + a
  = 6 + 99990      (exact)
  = 99996          (exact)
  = 9.9996 × 10^4  (exact)
  = 1.000 × 10^5.  (rounded to nearest)

Quindi (b + b) + a = 1.000 × 10 5 , che è diverso dall'altra nostra risposta.


5

Java utilizza la rappresentazione in virgola mobile binaria IEEE 754, che dedica 23 cifre binarie alla mantissa, che viene normalizzata per iniziare con la prima cifra significativa (omessa, per risparmiare spazio).

0.0000410=0,00000000000000101001111100010110101100010001110001101101000111 ...2=[1.]01001111100010110101100010001110001101101000111 ...2×2-15

100010+0.0000410=1111101000,00000000000000101001111100010110101100010001110001101101000111 ...2=[1.]11110100000000000000000101001111100010110101100010001110001101101000111 ...2×29

Le parti in rosso sono le mantisse, in quanto sono effettivamente rappresentate (prima dell'arrotondamento).

(100010+0.0000410)+0.0000410(0.0000410+0.0000410)+100010


0

Di recente abbiamo riscontrato un problema di arrotondamento simile. Le risposte sopra menzionate sono corrette, tuttavia abbastanza tecniche.

Ho trovato quanto segue una buona spiegazione del perché esistono errori di arrotondamento. http://csharpindepth.com/Articles/General/FloatingPoint.aspx

TLDR: i punti mobili binari non possono essere mappati accuratamente su punti decimali mobili. Ciò causa inesattezze che possono aggravarsi durante le operazioni matematiche.

Un esempio che utilizza numeri decimali mobili: 1/3 + 1/3 + 1/3 sarebbe normalmente uguale a 1. Tuttavia, in decimali: 0,333333 + 0,333333 + 0,333333 non è mai esattamente uguale a 1,000000

Lo stesso accade quando si eseguono operazioni matematiche su decimali binari.

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.