2012-06-05 10 views
10

Confrontando qualche codice C e F # con cui sto cercando di sostituirlo, ho notato che c'erano alcune differenze nel risultato finale.Le doppie C sono diverse dai doppi .NET?

Ripercorrendo il codice, ho scoperto che anche in quel caso c'erano differenze, anche se minuscole.

Il codice inizia leggendo i dati da un file. e il primo numero esce in modo diverso. Per esempio, in F # (più facile da copione):

let a = 71.9497985840 
printfn "%.20f" a 

Ho l'aspettavo (a me) Uscita 71.94979858400000000000.

Ma in C:

a = 71.9497985840; 
fprintf (stderr, "%.20f\n", a); 

stampe fuori 71.94979858400000700000.

Da dove viene 7?

La differenza è solo piccola, ma mi dà fastidio perché non so perché. (Mi disturba anche perché rende più difficile rintracciare dove le mie due versioni di codice sono divergenti)

+0

Sei sicuro che entrambi i valori sono doppi? Sei sicuro che l'errore non sia in stampa? – Euphoric

+1

aritmetica in virgola mobile in .net non ha un risultato esatto ben definito. È solo un'approssimazione. Le cifre più basse possono cambiare in base al capriccio del compilatore e di JITer, e osservarle con qualcosa come 'printfn' può influenzarle. – CodesInChaos

+0

Utilizzare un debugger o qualcosa per vedere il modello di bit esatto delle variabili. Se entrambe le implementazioni F # e C hanno lo stesso formato in virgola mobile e le variabili hanno lo stesso schema di bit, allora la differenza è o nella stampa o come il letterale in virgola mobile viene analizzato dal compilatore. In entrambi i casi, poiché i punti fluttuanti sono solo approssimazioni, potrebbe non essere nulla di cui preoccuparsi, poiché la differenza è piuttosto piccola. –

risposta

7

È una differenza di stampa. Conversione di tale valore ad un IEEE754 double produce

Prelude Text.FShow.RealFloat> FD 71.9497985840 
71.94979858400000694018672220408916473388671875 

ma la rappresentazione 71.949798584 è sufficiente per distinguere il numero dai suoi vicini. C, quando viene chiesto di stampare con una precisione di 20 cifre dopo che il punto decimale converte il valore correttamente arrotondato al numero desiderato di cifre, apparentemente F # usa la rappresentazione di determinazione univoca più breve e lo riempie con il numero desiderato di 0, proprio come Haskell .

+0

Quindi, se eseguo più operazioni su elenchi di questi numeri, queste differenze inizieranno a diventare significative? Sto cercando di capire come posso testare per vedere se questo è il caso o meno ... – Benjol

+0

Dipende. Penso che .NET dia al JITter un certo margine di manovra, quindi i risultati non sarebbero necessariamente riproducibili tra diverse esecuzioni del codice .NET. Se l'implementazione C non pretende di seguire la IEC 60559, ha anche molto margine di manovra. La domanda è se C e .NET producano gli stessi risultati per le stesse funzioni con gli stessi input. Se lo fanno, osserverai sempre la differenza della rappresentazione della stringa, che appare solo dietro la parte necessaria per determinare il valore 'double', nulla può accumularsi. Se non si riscontrano differenze di 1 o 2 ULP nei risultati ... è possibile accumulare –

+0

. Ma a meno che tu non faccia un ** lotto ** di operazioni (o alcune operazioni mal condizionate), le differenze rimarranno piccole. –

3

È solo un arrotondamento diverso. I numeri sono gli stessi (secondo CPython, almeno):

>>> '%.44f' % 71.94979858400000000000 
'71.94979858400000694018672220408916473388671875' 
>>> '%.44f' % 71.94979858400000700000 
'71.94979858400000694018672220408916473388671875' 
0

altre risposte spiegano in modo adeguato la fonte del problema (doppia precisione e di arrotondamento).

Se i numeri sono generalmente di magnitudine moderata e la precisione decimale è molto importante (più della velocità di calcolo), allora forse prendere in considerazione l'utilizzo del formato .NET decimal. Ciò fornisce 28-29 precisioni decimali precise senza errori di arrotondamento binari frazionari come il doppio. La restrizione è che l'intervallo è più piccolo (senza grandi esponenti!).

http://msdn.microsoft.com/en-us/library/364x0z75%28v=vs.100%29.aspx

3

È il metodo NET System.Double.ToString() che è la differenza, il metodo che converte un letto ad una stringa. Puoi dare un'occhiata al codice rilevante scaricando la sorgente CLR come fornita in SSCLI20. La conversione viene eseguita dalla funzione clr/src/vm/comnumber.cpp, COMNumber :: FormatDouble(). Che sembra like this, il commento nel codice è il più descrittivo di ciò che sta succedendo: libreria di runtime

//In order to give numbers that are both friendly to display and round-trippable, 
//we parse the number using 15 digits and then determine if it round trips to the same 
//value. If it does, we convert that NUMBER to a string, otherwise we reparse using 17 digits 
//and display that. 

La C non ha questa caratteristica.

+0

Grazie Hans, questa è una risposta abbastanza conclusiva. Sicuramente sto ricevendo errori cumulativi mentre procedo attraverso i miei algoritmi, ora devo solo cercare di rintracciare esattamente dove e perché ... Attualmente non riesco a capire se si tratta di questa roba a virgola mobile, o di semplici errori nel mio codice. – Benjol

0

Ulteriori informazioni per chiunque inciampi su questo.

Utilizzando bit di codice trovato here, ho confermato (credo) l'affermazione che la rappresentazione binaria sottostante (almeno per questo particolare numero) è la stessa.

Ecco alcuni esempi di codice: si noti che "moltiplicando lo zero per zero" per eliminare lo zero negativo, che è brutto quando convertito in lungo.

//(C# this time) 
var d = 71.9497985840; //or other incoming double value 
if(d == 0) d = d * d; //for negative zero 
var longval = System.BitConverter.DoubleToInt64Bits(d); // = 4634763433907061836 

In C:

double d; 
long long a; 
d = 71.9497985840;  //or other incoming double value 
if(d == 0) d = d * d; //for negative zero 
a = *(long long*)&d; //= 4634763433907061836 

aggiornamento - Ho seguito attraverso, e ha scoperto che la discrepanza veniva introdotto durante inversione di matrice, perché ogni sistema chiamato ad una libreria diversa, attuazione inversione un modo diverso ...

Problemi correlati