Soluzioni per errori di arrotondamento in virgola mobile


18

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

Mentre capisco che il virgola mobile non è esatto , il problema è come posso gestire i numeri esatti per assicurarmi che quando i calcoli sono preformati su di essi l'arrotondamento in virgola mobile non causi problemi?


2
C'è un problema specifico che stai affrontando? Esistono molti modi per eseguire i test, va bene per qualche problema. Le domande che possono avere risposte multiple non sono adatte al formato Domande e risposte. Sarebbe meglio se tu potessi definire il problema che stai riscontrando in modo tale da avere una risposta giusta piuttosto che creare una rete per idee e raccomandazioni.

Sto costruendo un'applicazione software con molte calcoli matematici. Capisco che i test NUNIT o JUNIT sarebbero buoni, ma mi piacerebbe avere un'idea su come affrontare i problemi con le Calcolazioni matematiche.
JNL,

1
Puoi dare un esempio di un calcolo che verresti testato? Uno in genere non sarebbe test unitari di matematica grezza (a meno che tu non stia testando i tuoi tipi numerici), ma testare qualcosa del genere distanceTraveled(startVel, duration, acceleration)verrebbe testato.

Un esempio riguarderà i punti decimali. Ad esempio, supponiamo che stiamo costruendo un muro con impostazioni speciali per dist x-0 a x = 14.589 e quindi alcune disposizioni da x = 14.589 a x = fine del muro. La distanza .589 convertita in binario non è la stessa .... Soprattutto se aggiungiamo alcune distanze ... come 14.589 + 0.25 non sarà uguale a 14.84 in binario .... Spero non sia confuso?
JNL,

1
@MichaelT grazie per aver modificato la domanda. Aiutato molto Dal momento che sono nuovo a questo, non troppo bravo su come inquadrare le domande. :) ... Ma andrà bene presto.
JNL,

Risposte:


22

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 intera invece in vari modi.

razionali

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

Se aggiunto a 0,25 (che è w: 0; n: 1; d: 4), questo comporta il calcolo dell'LCM e quindi l'aggiunta dei due numeri. Questo funziona bene per molte situazioni, sebbene possa risultare in 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 dove si trova) a quella precisione. Ad esempio, potresti avere un punto fisso con 3 punti decimali. 15.589+ 0.250diventa l'aggiunta 589 + 250 % 1000per la parte decimale (e quindi qualsiasi carry per l'intera parte). Funziona molto bene con i database esistenti. Come accennato, c'è arrotondamento ma sai dove si trova e puoi specificarlo in modo che sia più preciso del necessario (stai misurando solo con 3 punti decimali, quindi fissalo 4).

Punto fisso mobile

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


Una volta determinata la scelta per la rappresentazione, è possibile trovare librerie di terze parti esistenti che la utilizzano o scrivere la propria. Quando scrivi il tuo, assicurati di testarlo e assicurati di fare correttamente la matematica.


2
È un buon inizio, ma ovviamente non risolve completamente il problema dell'arrotondamento. I numeri irrazionali come π, e e √2 non hanno una rappresentazione strettamente numerica; è necessario rappresentarli simbolicamente se si desidera una rappresentazione esatta o valutarli il più tardi possibile se si desidera ridurre al minimo l'errore di arrotondamento.
Caleb,

@Caleb per irrazionali è necessario valutarli oltre dove qualsiasi arrotondamento potrebbe causare problemi. Ad esempio, 22/7 è preciso allo 0,1% di pi, 355/113 è preciso a 10 ^ -8. Se si lavora solo con numeri con 3 cifre decimali, con 3.141592653 si dovrebbe evitare qualsiasi errore di arrotondamento con 3 cifre decimali.

@MichaelT: Per l'aggiunta di numeri razionali non è necessario trovare LCM ed è più veloce non farlo (e più veloce per cancellare "zeri LSB" dopo, e semplificare sempre e solo quando assolutamente necessario). Per i numeri razionali in generale è in genere solo "numeratore / denominatore" o "numeratore / denominatore << esponente" (e non "parte intera + numeratore / denominatore"). Anche il tuo "punto fisso mobile" è una rappresentazione in virgola mobile, e sarebbe meglio descritto come "punto mobile di dimensione arbitraria" (per distinguerlo dal "punto mobile di dimensione fissa").
Brendan,

parte della tua terminologia è un po 'incerta - il punto fisso mobile non ha senso - penso che tu stia cercando di dire il decimale mobile.
jk.

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'unico modo di agire è di non utilizzare i valori in virgola mobile.

Ora la domanda diventa: "Come posso fare matematica che coinvolgono 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 accurati. 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ù popolari.


Sto usando VC ++ in questo momento ... Ma apprezzerei ulteriori informazioni anche riguardo ad altri linguaggi di programmazione.
JNL,

Anche senza valori in virgola mobile si incontrano comunque problemi rotondi.
Chad,

2
@Chad È vero, ma l'obiettivo non è quello di eliminare i problemi di arrotondamento (che esisteranno sempre, perché in ogni base che usi ci sono alcuni numeri che non hanno una rappresentazione esatta e non hai memoria infinita e potenza di elaborazione), è riducilo al punto che non ha alcun effetto nel calcolo che stai cercando di fare.
Iker,

@Iker Hai ragione. Sebbene tu, né la persona che ha posto la domanda, hai specificato quali sono esattamente i calcoli che stanno cercando di ottenere e la precisione che desiderano. Deve rispondere a questa domanda prima di saltare la pistola nella teoria dei numeri. Solo dire lot of mathematical calculationsnon è utile né le risposte fornite. Nella stragrande maggioranza dei casi (se non si ha a che fare con la valuta), il float dovrebbe davvero essere sufficiente.
Chad,

@Chad è un punto giusto, non ci sono certamente dati sufficienti dall'OP per dire esattamente qual è il livello di precisione di cui hanno bisogno.
Iker,

7

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

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

  • Ingestione della precisione: 1234567890.12345 + 0.123456789012345valuta 1234567890.24691, le ultime dieci cifre del secondo operando vengono perse.

  • Moltiplicazioni: se moltiplichi due numeri da 15 cifre, il risultato ha 30 cifre che devono essere memorizzate. Ma non puoi memorizzarli, quindi gli ultimi 15 bit vengono persi. Ciò è particolarmente fastidioso se combinato con un 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 consapevole. E una volta che ne sei consapevole, puoi provare a formulare la tua matematica in modo da evitarli. Ad esempio, se è necessario incrementare ripetutamente un valore in un ciclo, evitare di fare ciò:

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

Dopo alcune iterazioni, il più grande fingerirà parte della precisione di df. Peggio ancora, gli errori si sommeranno, portando alla situazione controintuitiva che un minore dfpuò portare a risultati complessivi peggiori. Meglio scrivere questo:

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

Poiché si stanno combinando gli incrementi in un'unica moltiplicazione, il risultato fsarà preciso a 15 cifre decimali.

Questo è solo un esempio, ci sono altri modi per evitare la perdita di precisione dovuta ad altri motivi. Ma aiuta già molto a pensare alla grandezza dei valori coinvolti e a immaginare cosa accadrebbe se si facesse la matematica con carta e penna, arrotondando ad un numero fisso di cifre dopo ogni passaggio.


2

Come assicurarti di non avere problemi: impara a conoscere i problemi aritmetici in virgola mobile o assumi qualcuno che lo fa o usa un po 'di buon senso.

Il primo problema è la precisione. In molte lingue hai "float" e "double" (doppia acronimo di "double precision"), e in molti casi "float" ti dà circa 7 cifre di precisione, mentre double ti dà 15. Il buon senso è che se hai un situazione in cui la precisione potrebbe essere un problema, 15 cifre sono molto meglio di 7 cifre. In molte situazioni leggermente problematiche, l'uso di "double" significa che te la cava, e "float" significa che non lo fai. Supponiamo che le capitalizzazioni di mercato di un'azienda siano di 700 miliardi di dollari. Rappresenta questo valore 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 il doppio, non il float.

Il secondo problema è più una questione di principio. Se esegui 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. O potrebbero non esserlo. Devi tenerlo a mente e scrivere e usare funzioni che dicono "x è decisamente maggiore di y" o "x è sicuramente minore di y" o "xey potrebbe essere uguale".

Questo problema peggiora molto se si utilizza l'arrotondamento, ad esempio "arrotondare x per difetto all'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 "arrotondi per difetto all'intero più vicino", quel "numero molto vicino a 6" potrebbe essere "leggermente inferiore a 6" e ottenere arrotondato a 5. E nota che non importa quanta precisione hai. Non importa quanto sia vicino a 6 il risultato, purché sia ​​inferiore a 6.

E in terzo luogo, alcuni problemi sono difficili . Ciò significa che non esiste una regola semplice 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 stai bene o hai un vero problema. Se fa il tipo di differenza che ti aspetteresti (come un cambiamento al dodicesimo decimale), allora probabilmente stai bene. Se cambia davvero i tuoi risultati, allora hai un problema. Chiedere aiuto.


1
Non c'è nulla di "buon senso" nella matematica in virgola mobile.
whatsisname

Ulteriori informazioni al riguardo.
gnasher729,

0

Molte persone commettono l'errore quando vedono il doppio urlano BigDecimal, quando in realtà hanno appena spostato il problema altrove. Il doppio dà bit di segno: 1 bit, larghezza esponente: 11 bit. Precisione significativa: 53 bit (52 memorizzati in modo esplicito). A causa della natura del doppio, maggiore è l'intero interger, si perde la precisione relativa. Per calcolare la precisione relativa che usiamo qui è 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 (metà 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 può essere il numero è 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 che può essere il 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 desidera durante l'esecuzione del calcolo necessario e il valore dell'unità (metri, piedi, pollici, mm, cm). Per la stragrande maggioranza dei casi, float sarà sufficiente per semplici simulazioni a seconda della scala del mondo che si intende simulare.

Anche se è qualcosa da dire, se stai solo cercando di simulare un mondo di 100 metri per 100 metri, avrai un posto nell'ordine della precisione vicino a 2 ^ -45. Questo non sta nemmeno esaminando il modo in cui la moderna FPU all'interno della CPU eseguirà i calcoli al di fuori della dimensione del tipo nativo e solo dopo aver completato il calcolo si arrotonderanno (a seconda della modalità di arrotondamento della FPU) alla dimensione del tipo nativo.

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.