Soluzioni per errori di arrotondamento in virgola mobile

16

Nella creazione di un'applicazione che si occupa di molti calcoli matematici, ho riscontrato il problema che alcuni numeri causano errori di arrotondamento.

Pur comprendendo che floating point non è esatto , il problema è come gestisco i numeri esatti per assicurarmi che quando i calcoli sono preformati su di essi l'arrotondamento in virgola mobile non causa problemi?

    
posta JNL 26.06.2013 - 18:38
fonte

5 risposte

21

Esistono tre approcci fondamentali per la creazione di tipi numerici alternativi privi di arrotondamento in virgola mobile. Il tema comune con questi è che usano la matematica dei numeri interi invece in vari modi.

Razionali

Rappresenta il numero come una parte intera e il numero razionale con un numeratore e un denominatore. Il numero 15.589 sarà rappresentato come w: 15; n: 589; d:1000 .

Se aggiunto a 0.25 (che è w: 0; n: 1; d: 4 ), ciò comporta il calcolo dell'LCM e quindi l'aggiunta dei due numeri. Funziona bene per molte situazioni, anche se può generare numeri molto grandi quando si lavora con molti numeri razionali che sono relativamente primi tra loro.

Punto fisso

Hai l'intera parte e la parte decimale. Tutti i numeri sono arrotondati (c'è quella parola - ma sai dov'è) a quella precisione. Ad esempio, potresti avere un punto fisso con 3 punti decimali. 15.589 + 0.250 diventa aggiungendo 589 + 250 % 1000 per la parte decimale (e quindi ogni carry per l'intera parte). Funziona molto bene con i database esistenti. Come già detto, c'è un arrotondamento, ma sai dove si trova e puoi specificarlo in modo che sia più preciso del necessario (stai misurando solo 3 punti decimali, quindi fallo fisso 4).

Virgola mobile

Memorizza un valore e la precisione. 15.589 è memorizzato come 15589 per il valore e 3 per la precisione, mentre 0.25 è memorizzato come 25 e 2 . Questo può gestire precisione arbitraria. Io credo questo è ciò che usano gli interni di BigDecimal di Java (non l'ho guardato di recente). Ad un certo punto, vorrai rimuoverlo da questo formato e visualizzarlo, e ciò potrebbe comportare l'arrotondamento (di nuovo, tu controlli dove si trova).

Una volta stabilita la scelta per la rappresentazione, puoi trovare le librerie di terze parti esistenti che la usano o scrivere la tua. Quando scrivi il tuo, assicurati di testarlo unitamente e assicurati di eseguire correttamente i calcoli.

    
risposta data 26.06.2013 - 20:30
fonte
10

Se i valori in virgola mobile hanno problemi di arrotondamento e non si desidera incorrere in problemi di arrotondamento, ne consegue logicamente che l'unica cosa da fare è non utilizzare valori in virgola mobile.

Ora la domanda diventa "come posso fare matematica che coinvolge valori non interi senza variabili in virgola mobile?" La risposta è con tipi di dati di precisione arbitraria . I calcoli sono più lenti perché devono essere implementati nel software anziché nell'hardware, ma sono precisi. Non hai detto quale lingua stai usando, quindi non posso raccomandare un pacchetto, ma ci sono librerie di precisione arbitrarie disponibili per i linguaggi di programmazione più diffusi.

    
risposta data 26.06.2013 - 20:15
fonte
6

L'aritmetica a virgola mobile è in genere piuttosto precisa (15 cifre decimali per un double ) e abbastanza flessibile. I problemi sorgono quando si sta facendo matematica che riduce significativamente la quantità di cifre di precisione. Ecco alcuni esempi:

  • Annullamento sulla sottrazione: 1234567890.12345 - 1234567890.12300 , il risultato 0.0045 ha solo due cifre decimali di precisione. Ciò colpisce ogni volta che sottrai due numeri di grandezza simile.

  • Inghiottire la precisione: 1234567890.12345 + 0.123456789012345 valuta 1234567890.24691 , le ultime dieci cifre del secondo operando vanno perse.

  • Moltiplicazioni: se si moltiplicano due numeri a 15 cifre, il risultato ha 30 cifre che devono essere memorizzate. Ma non puoi memorizzarli, quindi gli ultimi 15 bit vanno persi. Questo è particolarmente fastidioso quando combinato con sqrt() (come in sqrt(x*x + y*y) : il risultato avrà solo 7,5 cifre di precisione.

Queste sono le principali insidie di cui devi essere a conoscenza. E una volta che ne sei a conoscenza, puoi provare a formulare la tua matematica in modo da evitarli. Ad esempio, se è necessario incrementare un valore più e più volte in un ciclo, evitare di farlo:

for(double f = f0; f < f1; f += df) {

Dopo alcune iterazioni, il% più grande di co_de ingerirà parte della precisione di f . Peggio ancora, gli errori si sommano, portando alla situazione controintuitiva che un df più basso può portare a risultati complessivi peggiori. Meglio scrivere questo:

for(int i = 0; i < (f1 - f0)/df; i++) {
    double f = f0 + i*df;

Poiché stai combinando gli incrementi in una singola moltiplicazione, il df risultante sarà preciso a 15 cifre decimali.

Questo è solo un esempio, ci sono altri modi per evitare la perdita di precisione a causa di altri motivi. Ma aiuta già molto a pensare all'entità dei valori coinvolti e ad immaginare cosa succederebbe se tu facessi i tuoi calcoli con carta e penna, arrotondando ad un numero fisso di cifre dopo ogni passo.

    
risposta data 21.02.2016 - 09:59
fonte
2

Come accertarsi di non avere problemi: conoscere i problemi aritmetici a virgola mobile, o assumere qualcuno che lo fa, o usare un po 'di buon senso.

Il primo problema è la precisione. In molte lingue hai "float" e "double" (double standing per "double precision"), e in molti casi "float" ti dà una precisione di 7 cifre, mentre il doppio ti dà 15. Il senso comune è che se hai un situazione in cui la precisione potrebbe essere un problema, 15 cifre è molto meglio di 7 cifre. In molte situazioni un po 'problematiche, usare "double" significa farla franca, e "float" significa che non lo fai. Diciamo che la capitalizzazione di mercato di un'azienda è di 700 miliardi di dollari. Rappresenta questo in virgola mobile e il bit più basso è $ 65536. Rappresentalo usando il doppio, e il bit più basso è di circa 0,012 centesimi. Quindi, a meno che tu non sappia davvero cosa stai facendo, usi double, non float.

Il secondo problema è più una questione di principio. Se fai due calcoli diversi che dovrebbero dare lo stesso risultato, spesso non lo fanno a causa di errori di arrotondamento. Due risultati che dovrebbero essere uguali saranno "quasi uguali". Se due risultati sono vicini, i valori reali potrebbero essere uguali. Oppure potrebbero non esserlo. È necessario tenerlo a mente e scrivere e utilizzare funzioni che dicano "x è decisamente maggiore di y" o "x è decisamente minore di y" o "xey potrebbe essere uguale".

Questo problema si aggrava molto se si utilizza l'arrotondamento, ad esempio "arrotondare x al numero intero più vicino". Se moltiplichi 120 * 0,05, il risultato dovrebbe essere 6, ma quello che ottieni è "un numero molto vicino a 6". Se poi "arrotondate il numero intero più vicino", quel "numero molto vicino a 6" potrebbe essere "leggermente inferiore a 6" e arrotondare a 5. E notate che non importa quanto precisione avete. Non importa quanto vicino a 6 il tuo risultato è, a patto che sia inferiore a 6.

E in terzo luogo, alcuni problemi sono difficili . Ciò significa che non esiste una regola facile e veloce. Se il tuo compilatore supporta "long double" con maggiore precisione puoi usare "long double" e vedere se fa la differenza. Se non fa alcuna differenza, allora o sei Ok, o hai un vero problema complicato. Se fa il tipo di differenza che ti aspetteresti (come un cambiamento al dodicesimo decimale), allora probabilmente stai bene. Se cambia davvero i risultati, allora hai un problema. Chiedere aiuto.

    
risposta data 21.02.2016 - 21:37
fonte
0

La maggior parte delle persone commette l'errore quando vede il doppio urlare BigDecimal, quando in realtà hanno appena spostato il problema altrove. Double dà Sign bit: 1 bit, Larghezza esponenziale: 11 bit. Significand precisione: 53 bit (52 esplicitamente memorizzati). A causa della natura del doppio, il più grande nell'intero interger perdi precisione relativa. Per calcolare la precisione relativa che usiamo qui è sotto.

Precisione relativa del doppio nel calcolo usiamo il seguente foluma 2 ^ E < = abs (X) < 2 ^ (E + 1)

epsilon = 2 ^ (E-10)% Per un float a 16 bit (mezza precisione)

 Accuracy Power | Accuracy -/+| Maximum Power | Max Interger Value
 2^-1           | 0.5         | 2^51          | 2.2518E+15
 2^-5           | 0.03125     | 2^47          | 1.40737E+14
 2^-10          | 0.000976563 | 2^42          | 4.39805E+12
 2^-15          | 3.05176E-05 | 2^37          | 1.37439E+11
 2^-20          | 9.53674E-07 | 2^32          | 4294967296
 2^-25          | 2.98023E-08 | 2^27          | 134217728
 2^-30          | 9.31323E-10 | 2^22          | 4194304
 2^-35          | 2.91038E-11 | 2^17          | 131072
 2^-40          | 9.09495E-13 | 2^12          | 4096
 2^-45          | 2.84217E-14 | 2^7           | 128
 2^-50          | 8.88178E-16 | 2^2           | 4

In altre parole Se si desidera una precisione di +/- 0,5 (o 2 ^ -1), la dimensione massima che il numero può essere è 2 ^ 52. Qualsiasi più grande di questo e la distanza tra i numeri in virgola mobile è maggiore di 0,5.

Se si desidera una precisione di +/- 0.0005 (circa 2 ^ -11), la dimensione massima consentita dal numero è 2 ^ 42. Qualsiasi più grande di questo e la distanza tra i numeri in virgola mobile è maggiore di 0.0005.

Non posso davvero dare una risposta migliore di questa. L'utente dovrà capire quale precisione vogliono quando esegue il calcolo necessario e il loro valore unitario (Metri, Piedi, Pollici, mm, cm). Per la stragrande maggioranza dei casi, il float è sufficiente per simulazioni semplici a seconda della scala del mondo che stai mirando a simulare.

Anche se è qualcosa da dire, se stai solo mirando a simulare un mondo di 100 metri per 100, avrai un ordine nell'ordine di precisione vicino a 2 ^ -45. Questo non sta nemmeno andando nel modo in cui le FPU moderne all'interno della CPU faranno calcoli al di fuori della dimensione del tipo nativo e solo dopo che il calcolo sarà completo gireranno (a seconda della modalità di arrotondamento della FPU) alla dimensione del tipo nativo.

    
risposta data 22.02.2016 - 04:12
fonte

Leggi altre domande sui tag