2011-09-17 8 views
26

Ho un doppio "138630.78380386264" e voglio convertirlo in un decimale, tuttavia quando lo faccio lo faccio sia colando o usando Convert.ToDecimal() e perdo la precisione.C# da doppia a decimale perdita di precisione

Cosa sta succedendo? Sia decimale e doppia può contenere questo numero:

enter image description here

double doub = double.Parse("138630.78380386264"); 
decimal dec = decimal.Parse("138630.78380386264"); 
string decs = dec.ToString("F17"); 
string doubse =DoubleConverter.ToExactString(doub); 
string doubs = doub.ToString("F17"); 

decimal decC = (decimal) doub; 
string doudeccs = decC.ToString("F17"); 
decimal decConv = Convert.ToDecimal(doub); 
string doudecs = decConv.ToString("F17"); 

anche: come posso ottenere il ToString() sulla doppia stampare lo stesso risultato come mostra il debugger? per esempio. 138630.78380386264?

+0

possibile duplicato di [La conversione di un numero decimale in doppio in C# produce una differenza] (http://stackoverflow.com/questions/1584314/conversion-of-a-decimal-to-double-number-in- c-results-in-a-difference) –

+1

Well Convert.ToDecimal (somedouble) è esattamente uguale a (decimale) somedouble, quindi non ci sono sorprese. Non sono sicuro del motivo per cui quel cast arrotonda l'ultima cifra verso il basso. – harold

+0

@harold Sei stato ingannato. Fa circolare al più vicino. Il doppio valore termina con 386264 e il decimale con 15 sig fig termina 3863. –

risposta

23

138630.78380386264 non è esattamente rappresentabile per la precisione doppia. Il numero di precisione doppia più vicino (come si trova here) è 138630.783803862635977566242218017578125, che concorda con i risultati.

Si chiede perché la conversione in decimale non contenga più precisione. Il documentation for Convert.ToDecimal() ha la risposta:

il valore decimale restituito da questo metodo contiene un massimo di 15 cifre significative. Se il parametro value contiene più di 15 cifre significative, viene arrotondato con arrotondamento al più vicino. Nell'esempio seguente viene illustrato come il metodo Convert.ToDecimal (Double) utilizza l'arrotondamento al più vicino per restituire un valore decimale con 15 cifre significative.

Il doppio valore, arrotondato al più vicino a 15 cifre significative è 138630.783803863, esattamente come mostrate sopra.

+0

quindi perché la conversione in decimale non aumenta la precisione? – GreyCloud

+0

ok grazie per averlo spiegato. Esiste comunque la possibilità che il ToString stampi più di 15 sig fig? - Mi piacerebbe essere in grado di tornare alla stringa originale - in questo modo potrei ottenere Double per analizzare la stringa e fare la conversione senza perdere precisione – GreyCloud

+0

'doub.ToString (" G17 ")' ti darà la stessa stringa che il debugger produce. Ma non so per certo che è ciò che sta facendo il debugger. Dato che 'double' ha una precisione limitata, in generale non sarà possibile analizzare la stringa e recuperare la stringa originale. Se hai bisogno di conoscere la stringa originale, ricordala. –

4

È uno sfortunato, penso. Vicino a 139.000, uno Decimal ha una precisione molto migliore di uno Double. Tuttavia, a causa di questo problema, abbiamo diversiDouble s proiettati su lo stessoDecimal. Ad esempio

double doub1 = 138630.7838038626; 
double doub2 = 138630.7838038628; 
Console.WriteLine(doub1 < doub2);     // true, values differ as doubles 
Console.WriteLine((decimal)doub1 < (decimal)doub2); // false, values projected onto same decimal 

In realtà ci sono sei diversi rappresentabili Double valori tradoub1 e doub2 sopra, quindi non sono gli stessi.

Qui è un po 'stupido lavoro-aronud:

static decimal PreciseConvert(double doub) 
{ 
    // Handle infinities and NaN-s first (throw exception) 
    // Otherwise: 
    return Decimal.Parse(doub.ToString("R"), NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint); 
} 

La stringa di formato "R" assicura che le cifre in più sufficienti sono inclusi per rendere l'iniettiva mappatura (nel dominio in cui Decimal ha precisione superiore).


Si noti che in qualche intervallo, un long (Int64) ha una precisione che è superiore a quello di Double. Quindi ho controllato se le conversioni qui sono fatte nello stesso modo (prima arrotondando a 15 cifre decimali significative). Non sono! Quindi:

double doub3 = 1.386307838038626e18; 
double doub4 = 1.386307838038628e18; 

Console.WriteLine(doub3 < doub4);    // true, values differ as doubles 
Console.WriteLine((long)doub3 < (long)doub4); // true, full precision of double used when converting to long 

Sembra incoerente di utilizzare una "regola" diverso quando il bersaglio è decimal.

Si noti che a causa di ciò, (decimal)(long)doub3 produce un risultato più accurato di appena (decimal)doub3.

+0

Il problema non è con i numeri vicini a 139.000 perché la precisione del doppio è di 16 cifre, non importa se le cifre sono sul lato sinistro o destro del separatore, se si riduce questo numero a 13.86307838038626 avremo lo stesso problema.Il decimale ha una precisione di 32 cifre, ecco perché non hai questo problema. –

+0

@ GabrielVonlantenC.Lopes Hai ragione su questo problema non è specifico per numeri vicino a 139.000. Ho considerato solo quel numero come un esempio, scelto perché quello era il numero nella domanda originale. Infatti tutti i numeri tra (molto approssimativamente) '1e-12' e' 1e + 29' hanno esattamente lo stesso problema. Al di fuori di questo intervallo un 'decimale' non è definito o non ha più una precisione maggiore di un' doppio'. (Tuttavia, 'decimal' ha una precisione di 28-29 cifre.) –