2009-03-30 8 views
6

Ho una funzione getSlope che prende come parametri 4 doppie e restituisce un altro doppio calcolato utilizzando questi parametri dati nel modo seguente:perdita imprevisto di precisione quando si divide doppie

double QSweep::getSlope(double a, double b, double c, double d){ 
double slope; 
slope=(d-b)/(c-a); 
return slope; 
} 

Il problema è che quando si chiama questa funzione con argomenti per esempio:

getSlope(2.71156, -1.64161, 2.70413, -1.72219); 

il risultato restituito è:

10.8557 

e questo non è un buon risultato per i miei calcoli. Ho calcolato la pendenza utilizzando Mathematica e il risultato per la pista per gli stessi parametri è:

10.8452 

o più cifre per la precisione:

10.845222072678331. 

Il risultato restituito dal mio programma non è buono nei miei ulteriori calcoli. Inoltre, non ho capito come il programma restituisce 10.8557 a partire dal 10.845222072678331 (supponendo che questo sia il risultato approssimativo per la divisione)? Come posso ottenere il buon risultato per la mia divisione?

grazie in anticipo, Madalina


ho stampare il risultato utilizzando la riga di comando:

std::cout<<slope<<endl; 

Può darsi che i miei parametri non sono forse buoni, come li leggo da un altro programma (che calcola un grafico, dopo aver letto questi parametri dal suo grafico li ho appena mostrati per vedere il loro valore ma forse i vettori visualizzati non hanno la stessa precisione interna per il valore calcolato..Non so che è davvero strano . Alcuni numerici appare un errore ..)

Quando il grafico da cui sto leggendo i miei parametri è calcolato, vengono utilizzate alcune librerie numeriche scritte in C++ (con modelli). Nessun OpenGL è usato per questo calcolo.

ringraziamento, Madalina

+0

Verificare come il metodo è stato compilato in asm. Puoi farlo nel debugger, credo (almeno nello studio visivo). –

+0

Il mio calc di Windows dà un buon risultato come Mathematica: D – klew

+0

Quale compilatore, quale piattaforma? – peterchen

risposta

5

Potrebbe essere che si utilizza DirectX o OpenGL nel vostro progetto? Se è così, possono disattivare la doppia precisione e otterrai strani risultati.

È possibile controllare le impostazioni di precisione con

std::sqrt(x) * std::sqrt(x) 

Il risultato deve essere abbastanza vicino a x. Ho incontrato questo problema molto tempo fa e ho passato un mese a controllare tutte le formule. Ma poi ho trovato

D3DCREATE_FPU_PRESERVE 
+0

esattamente come fanno a farlo? –

+0

Ci sono opzioni per inizializzare il diretto 3d. Non ricordo il nome, ma ho perso un mese controllando tutte le formule Diploma e solo dopo ho fatto un semplice controllo con "sqrt (x) * sqrt (x)" e la precisione è stata interrotta molto a meno che non avessi disattivato l'opzione. –

+0

Quando è compilato con l'app per console Win32 standard in VS2008, fornisce la risposta corretta. Sono d'accordo e dico che è un'impostazione del compilatore. –

3

Il problema è che (c-a) è piccola, così gli errori di arrotondamento inerenti alle operazioni in virgola mobile è amplificato in questo esempio. Una soluzione generale è di rielaborare la tua equazione in modo che tu non stia dividendo per un piccolo numero, non sono sicuro di come lo faresti comunque qui.

EDIT:

Neil è proprio nel suo commento a questa domanda, ho calcolato la risposta in VB utilizzando doppie e ottenuto la stessa risposta di matematica.

+0

vedere il codice che ho postato - non è questo il problema –

5

Il seguente codice:

#include <iostream> 
using namespace std; 

double getSlope(double a, double b, double c, double d){ 
    double slope; 
    slope=(d-b)/(c-a); 
    return slope; 
} 

int main() { 
    double s = getSlope(2.71156, -1.64161, 2.70413, -1.72219); 
    cout << s << endl; 
} 

dà un risultato di 10,8452 con g ++. Come stai stampando il risultato nel tuo codice?

+0

Non importa come si stampa 10.845222072678331, non girerà o troncerà a 10.8557 –

1

Migliore Stampa anche gli argomenti. Quando si è, come immagino, trasferendo i parametri in notazione decimale, si perderà la precisione per ognuno di essi. Il problema è che 1/5 è una serie infinita in binario, quindi ad es. 0.2 diventa .001001001 .... Inoltre, i decimali vengono tagliati quando si converte un valore binario in una rappresentazione testuale in decimale.

Accanto a ciò, a volte il compilatore sceglie la velocità rispetto alla precisione. Questo dovrebbe essere un interruttore del compilatore documentato.

0

Patrick sembra essere ragione su (CA) è la causa principale:

db = -1,72219 - (-1,64161) = -0,08058

ca = 2, 70413 - 2,71156 = -0,00743

S = (db)/(ca) = -0,08058/-0,00743 = 10,845222

Si inizia con sei cifre di precisione , attraverso la sottrazione che ottieni una riduzione a 3 e quattro cifre. La mia ipotesi migliore è che perdi precisione addizionale perché il numero -0,00743 non può essere rappresentato in modo esatto in un doppio. Provare a utilizzare variabili intermedie con una precisione più grande, in questo modo:

double QSweep::getSlope(double a, double b, double c, double d) 
{ 
    double slope; 
    long double temp1, temp2; 

    temp1 = (d-b); 
    temp2 = (c-a); 
    slope = temp1/temp2; 

    return slope; 
} 
+0

hai guardato il codice che ho postato? –

+0

Sembra che tu abbia una precisione confusa (come viene rappresentato il numero) con precisione (quale sia la tolleranza sui valori). Indipendentemente dal fatto che si specifichi un doppio come 2.70413 o 2.7041300000, non si ottiene alcuna differenza in C++ –

+0

@Pete Kirkham: non è possibile rappresentare un valore di es. 0,1 * esattamente * in un doppio, quindi memorizzarlo in una variabile con uno scope più grande può dare risultati diversi. – Treb

7

Ho provato con galleggiante invece di doppio e ottengo 10,845,11 mila come risultato. Sembra ancora meglio del risultato di Madalina.

EDIT:

Credo di sapere il motivo per cui si ottiene questo risultato. Se si ottengono i parametri a, b, ced da un altro punto e lo si stampa, si ottengono valori arrotondati. Quindi se lo metti in Mathemtacia (o calc;)) ti darà un risultato diverso.

Ho provato a cambiare un po 'uno dei parametri. Quando ho fatto:

double c = 2.7041304; 

Ottengo 10.845806. Aggiungo solo 0,0000004 a c! Quindi penso che i tuoi "errori" non siano errori. Stampa a, b, c e d con maggiore precisione e quindi inseriscili in Mathematica.

2

I risultati ottenuti sono coerenti con l'aritmetica a 32 bit. Senza saperne di più sul proprio ambiente, non è possibile consigliare cosa fare.

Supponendo che il codice mostrato sia ciò che è in esecuzione, ovvero non si converte nulla in stringhe o float, quindi non c'è una correzione in C++. È al di fuori del codice che hai mostrato e dipende dall'ambiente.

Come Patrick McDonald e Treb hanno riportato la precisione dei dati e l'errore su a-c, ho pensato di dargli un'occhiata. Una tecnica per esaminare gli errori di arrotondamento è l'aritmetica a intervalli, che rende i limiti superiore e inferiore che il valore rappresenta esplicito (sono impliciti in numeri in virgola mobile e sono fissati alla precisione della rappresentazione). Trattando ogni valore come limite superiore e inferiore e estendendo i limiti dell'errore nella rappresentazione (approssimativamente x * 2^-53 per un doppio valore x), si ottiene un risultato che fornisce i limiti inferiore e superiore sul accuratezza di un valore, tenendo conto degli errori di precisione nel caso peggiore.

Ad esempio, se si ha un valore nell'intervallo [1.0, 2.0] e si sottrae da esso un valore nell'intervallo [0.0, 1.0], il risultato deve essere compreso nell'intervallo [sotto (0.0), sopra (2.0)] poiché il risultato minimo è 1.0-1.0 e il massimo è 2.0-0.0. below e above sono equivalenti a pavimento e soffitto, ma per il successivo valore rappresentabile anziché per i numeri interi.

Utilizzando intervalli che rappresentano caso peggiore doppio arrotondamento:

getSlope(
a = [2.7115599999999995262:2.7115600000000004144], 
b = [-1.6416099999999997916:-1.6416100000000002357], 
c = [2.7041299999999997006:2.7041300000000005888], 
d = [-1.7221899999999998876:-1.7221900000000003317]) 
(d-b) = [-0.080580000000000526206:-0.080579999999999665783] 
(c-a) = [-0.0074300000000007129439:-0.0074299999999989383218] 

to double precision [10.845222072677243474:10.845222072679954195] 

Quindi, anche se c-a è piccola rispetto alla c o a, è ancora grande rispetto al doppio arrotondamenti, quindi se si sta utilizzando il doppio peggiore che si possa immaginare Arrotondamento di precisione, quindi puoi fidarti di quel valore per essere precisi a 12 cifre - 10.8452220727. Hai perso alcune cifre con una precisione doppia, ma stai ancora lavorando a qualcosa di più del significato del tuo input.

Ma se gli ingressi erano accurate solo al numero di cifre significative, quindi piuttosto che essere il doppio valore di 2.71156 +/- eps, quindi la gamma di ingresso sarebbe [2.711555,2.711565], in modo da ottenere il risultato:

getSlope(
a = [2.711555:2.711565], 
b = [-1.641615:-1.641605], 
c = [2.704125:2.704135], 
d = [-1.722195:-1.722185]) 
(d-b) = [-0.08059:-0.08057] 
(c-a) = [-0.00744:-0.00742] 

to specified accuracy [10.82930108:10.86118598] 

che è una gamma molto più ampia.

Ma dovresti fare di tutto per tenere traccia dell'accuratezza dei calcoli e gli errori di arrotondamento inerenti al punto mobile non sono significativi in ​​questo esempio: è preciso a 12 cifre con l'arrotondamento di precisione doppio nel caso peggiore.

D'altra parte, se gli input sono noti solo con 6 cifre, non importa se ottieni 10.8557 o 10.8452. Entrambi sono all'interno di [10.82930108: 10.86118598].

-1

Mentre la discussione accademica in corso è ottima per conoscere i limiti dei linguaggi di programmazione, è possibile che la soluzione più semplice al problema sia una struttura dati per arbitrary precision arithmetic.

Questo avrà un sovraccarico, ma dovresti riuscire a trovare qualcosa con una precisione abbastanza garantita.

+1

La raccomandazione dell'aritmetica di precisione arbitraria, sebbene popolare su StackOverflow, non è la migliore risposta a tutte le domande sui calcoli in virgola mobile. –

+0

Ciò è vero, ma spesso la soluzione più semplice e praticabile. Ci sono spesso modi migliori, più veloci e più complessi per fare le cose, ma anche la semplicità da sola ha molto valore in un progetto software. – IanGilham