Quando dovrei usare il doppio anziché il decimale?


265

Posso citare tre vantaggi nell'uso double(o float) invece di decimal:

  1. Usa meno memoria.
  2. Più veloce perché le operazioni matematiche in virgola mobile sono supportate nativamente dai processori.
  3. Può rappresentare un intervallo più ampio di numeri.

Ma questi vantaggi sembrano applicarsi solo alle operazioni di calcolo intensivo, come quelle che si trovano nel software di modellazione. Naturalmente, i doppi non dovrebbero essere usati quando è richiesta la precisione, come i calcoli finanziari. Quindi ci sono ragioni pratiche per scegliere double(o float) invece che decimalnelle applicazioni "normali"?

Modificato per aggiungere: grazie per tutte le ottime risposte, ho imparato da loro.

Un'ulteriore domanda: alcune persone hanno sottolineato che i doppi possono rappresentare più precisamente i numeri reali. Quando dichiarato, penso che di solito li rappresentano anche in modo più accurato. Ma è una vera affermazione che l'accuratezza può diminuire (a volte in modo significativo) quando vengono eseguite operazioni in virgola mobile?



5
Questo viene valutato abbastanza regolarmente e continuo a lottare con esso. Ad esempio, sto lavorando a un'applicazione che esegue calcoli finanziari, quindi sto utilizzando il numero decimale. Ma le funzioni Math e VisualBasic.Financial usano il doppio, quindi c'è molta conversione che mi fa costantemente indovinare l'uso del decimale.
Jamie Ide,

@JamieIde È pazzesco che le funzioni finanziarie usino il doppio, il denaro dovrebbe sempre essere in decimale.
Chris Marisic,

@ChrisMarisic Ma cosa può fare Jamie Ide lavorando con le cazzate legacy usando il doppio? Quindi dovresti usare anche il doppio altrimenti le molte conversioni causeranno errori di arrotondamento ... non c'è da meravigliarsi che abbia menzionato VisualBasic pfffhh .....
Elisabeth,

@Elisabeth probabilmente userei una libreria diversa che supporta correttamente i decimali. Qualunque cosa fornisca VisualBasic.Financial probabilmente esiste in molte altre biblioteche oggi
Chris Marisic,

Risposte:


306

Penso che tu abbia riassunto abbastanza bene i vantaggi. Ti manca comunque un punto. Il decimaltipo è solo più preciso nel rappresentare i numeri di base 10 (ad esempio quelli utilizzati nei calcoli valutari / finanziari). In generale, il doubletipo offrirà almeno una grande precisione (qualcuno mi correggerà se sbaglio) e una velocità decisamente maggiore per numeri reali arbitrari. La semplice conclusione è: quando si considera quale utilizzare, utilizzare sempre a doublemeno che non sia necessaria la base 10precisione che decimaloffre.

Modificare:

Per quanto riguarda la tua ulteriore domanda sulla diminuzione della precisione dei numeri in virgola mobile dopo le operazioni, questo è un problema leggermente più sottile. In effetti, la precisione (uso il termine in modo intercambiabile per accuratezza qui) diminuirà costantemente dopo l'esecuzione di ogni operazione. Ciò è dovuto a due motivi:

  1. il fatto che alcuni numeri (ovviamente i decimali) non possono essere realmente rappresentati in forma a virgola mobile
  2. si verificano errori di arrotondamento, proprio come se si eseguisse il calcolo a mano. Dipende molto dal contesto (quante operazioni stai eseguendo) se questi errori sono abbastanza significativi da giustificare molta riflessione.

In tutti i casi, se si desidera confrontare due numeri in virgola mobile che in teoria dovrebbero essere equivalenti (ma sono stati raggiunti usando calcoli diversi), è necessario consentire un certo grado di tolleranza (quanto varia, ma in genere è molto piccolo) .

Per una panoramica più dettagliata dei casi particolari in cui è possibile introdurre errori di precisione, consultare la sezione Precisione dell'articolo di Wikipedia . Infine, se vuoi una discussione seriamente approfondita (e matematica) dei numeri / operazioni in virgola mobile a livello di macchina, prova a leggere l'articolo spesso citato Cosa ogni scienziato informatico dovrebbe sapere sull'aritmetica in virgola mobile .


1
Potete fornire un esempio, e di un numero di base 10 con cui si perde la precisione durante la conversione in base 2?
Mark Cidade,

@Mark: 1.000001 è un esempio, almeno secondo Jon Skeet. (Vedi la domanda 3 di questa pagina: yoda.arachsys.com/csharp/teasers-answers.html )
Noldorin,

25
@Mark: esempio molto semplice: 0.1 è una frazione periodica in base 2, quindi non può essere espressa con precisione in a double. I computer moderni continueranno a stampare il valore corretto, ma solo perché "indovinano" il risultato, non perché sia ​​realmente espresso correttamente.
Konrad Rudolph,

1
Il Decimaltipo ha una precisione di 93 bit nella mantissa, rispetto a circa 52 per double. Vorrei che Microsoft supportasse il formato IEEE a 80 bit, anche se doveva essere riempito di 16 byte; avrebbe consentito una gamma più ampia di doubleo Decimal, una velocità molto migliore rispetto a Decimal, supporto per operazioni trascendentali (ad es. sin (x), log (x), ecc.) e precisione che, sebbene non abbastanza buona come Decimalsarebbe molto meglio di double.
Supercat,

@charlotte: se leggi il mio post completo, vedrai che è spiegato.
Noldorin,

59

Sembri perfetto con i vantaggi dell'utilizzo di un tipo a virgola mobile. Tendo a progettare decimali in tutti i casi e mi affido a un profiler per farmi sapere se le operazioni sui decimali stanno causando colli di bottiglia o rallentamenti. In questi casi, "eseguirò il down cast" per raddoppiare o fluttuare, ma lo farò solo internamente e cercherò attentamente di gestire la perdita di precisione limitando il numero di cifre significative nell'operazione matematica eseguita.

In generale, se il tuo valore è temporaneo (non riutilizzato), sei sicuro di usare un tipo a virgola mobile. Il vero problema con i tipi a virgola mobile sono i seguenti tre scenari.

  1. Stai aggregando valori in virgola mobile (nel qual caso gli errori di precisione sono composti)
  2. Si creano valori in base al valore in virgola mobile (ad esempio in un algoritmo ricorsivo)
  3. Stai facendo matematica con un numero molto ampio di cifre significative (ad esempio 123456789.1 * .000000000000000987654321)

MODIFICARE

Secondo la documentazione di riferimento sui decimali C # :

La parola chiave decimale indica un tipo di dati a 128 bit. Rispetto ai tipi a virgola mobile, il tipo decimale ha una maggiore precisione e un intervallo più piccolo, che lo rende adatto per calcoli finanziari e monetari.

Quindi, per chiarire la mia affermazione di cui sopra:

Tendo a progettare decimali in tutti i casi e mi affido a un profiler per farmi sapere se le operazioni sui decimali stanno causando colli di bottiglia o rallentamenti.

Ho sempre lavorato in settori in cui i decimali sono favorevoli. Se stai lavorando su motori fisici o grafici, probabilmente è molto più vantaggioso progettare per un tipo a virgola mobile (float o double).

Il decimale non è infinitamente preciso (è impossibile rappresentare una precisione infinita per un non integrale in un tipo di dati primitivo), ma è molto più preciso del doppio:

  • decimale = 28-29 cifre significative
  • doppio = 15-16 cifre significative
  • float = 7 cifre significative

MODIFICA 2

In risposta al commento di Konrad Rudolph , l'articolo n. 1 (sopra) è decisamente corretto. L'aggregazione delle imprecisioni in effetti si aggrava. Vedi il codice qui sotto per un esempio:

private const float THREE_FIFTHS = 3f / 5f;
private const int ONE_MILLION = 1000000;

public static void Main(string[] args)
{
    Console.WriteLine("Three Fifths: {0}", THREE_FIFTHS.ToString("F10"));
    float asSingle = 0f;
    double asDouble = 0d;
    decimal asDecimal = 0M;

    for (int i = 0; i < ONE_MILLION; i++)
    {
        asSingle += THREE_FIFTHS;
        asDouble += THREE_FIFTHS;
        asDecimal += (decimal) THREE_FIFTHS;
    }
    Console.WriteLine("Six Hundred Thousand: {0:F10}", THREE_FIFTHS * ONE_MILLION);
    Console.WriteLine("Single: {0}", asSingle.ToString("F10"));
    Console.WriteLine("Double: {0}", asDouble.ToString("F10"));
    Console.WriteLine("Decimal: {0}", asDecimal.ToString("F10"));
    Console.ReadLine();
}

Ciò genera quanto segue:

Three Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000
Double: 599999.9999886850
Decimal: 600000.0000000000

Come puoi vedere, anche se stiamo aggiungendo dalla stessa costante sorgente, i risultati del doppio sono meno precisi (anche se probabilmente si arrotonderanno correttamente) e il float è molto meno preciso, al punto in cui è stato ridotto a solo due cifre significative.


1
Il punto 1 non è corretto. Gli errori di precisione / arrotondamento si verificano solo nella fusione, non nei calcoli. Si tratta , naturalmente, corretto che la maggior parte operazioni matematiche sono instabili, moltiplicando così l'errore. Ma questo è un altro problema e si applica allo stesso per tutti i tipi di dati di precisione limitata, quindi in particolare per i decimali.
Konrad Rudolph,

1
@Konrad Rudolph, vedi l'esempio in "EDIT 2" come prova del punto che stavo cercando di chiarire nell'articolo # 1. Spesso, questo problema non si manifesta perché l'imprecisione positiva si equilibra con l'imprecisione negativa, e si lavano dentro l'aggregato, ma aggregando lo stesso numero (come ho fatto nell'esempio) evidenzia il problema.
Michael Meadows,

Ottimo esempio L'ho appena mostrato ai miei sviluppatori junior, i bambini sono rimasti sbalorditi.
Machado,

Ora puoi fare la stessa cosa con 2/3 anziché 3/5 ... Dovresti conoscere il sistema di numeri sessagesimale che gestisce i 2/3 perfettamente bene.
gnasher729,

1
@ gnasher729, l'utilizzo di 2/3 anziché 3/5 non è stato gestito perfettamente per i diversi tipi. È interessante notare che il valore float ha prodotto Single: 667660.400000000000mentre il valore decimale ha prodotto Decimal: 666666.7000000000. Il valore float è leggermente inferiore a mille rispetto al valore corretto.
Jhenninger,

25

Utilizzare i decimali per i valori di base 10, ad esempio calcoli finanziari, come altri hanno suggerito.

Ma il doppio è generalmente più preciso per valori calcolati arbitrari.

Ad esempio, se si desidera calcolare il peso di ciascuna linea in un portafoglio, utilizzare il doppio poiché il risultato si sommerà quasi fino al 100%.

Nel seguente esempio, doubleResult è più vicino a 1 di decimalResult:

// Add one third + one third + one third with decimal
decimal decimalValue = 1M / 3M;
decimal decimalResult = decimalValue + decimalValue + decimalValue;
// Add one third + one third + one third with double
double doubleValue = 1D / 3D;
double doubleResult = doubleValue + doubleValue + doubleValue;

Quindi, ancora una volta prendendo l'esempio di un portafoglio:

  • Il valore di mercato di ciascuna linea del portafoglio è un valore monetario e probabilmente sarebbe meglio rappresentato come decimale.

  • Il peso di ciascuna linea del portafoglio (= valore di mercato / SUM (valore di mercato)) è di solito meglio rappresentato come doppio.


6

Usa un double o un float quando non hai bisogno di precisione, ad esempio, in un gioco platform che ho scritto, ho usato un float per memorizzare le velocità del giocatore. Ovviamente non ho bisogno di super precisione qui perché alla fine mi giro a un Int per disegnare sullo schermo.


3
La precisione è l'UNICO vantaggio dei decimali, questo è giusto. Non dovresti chiedere quando utilizzare i numeri in virgola mobile rispetto ai decimali. Questo dovrebbe essere il tuo primo pensiero. La domanda allora è quando dovresti usare i decimali (e la risposta è proprio qui ... quando la precisione conta).
Istanza Hunter,

3
@Daniel Straight, è divertente, ma ho l'opinione opposta. Penso che usare un tipo meno preciso a causa delle sue caratteristiche prestazionali equivalga a una preottimizzazione. Potenzialmente dovrai pagare per quella preottimizzazione molte volte prima di rendertene conto.
Michael Meadows,

3
@Michael Meadows, posso capire questo argomento. Una cosa da notare è che una delle principali lamentele riguardo l'ottimizzazione precoce è che i programmatori non tendono a sapere cosa sarà lento. Sappiamo senza dubbio, tuttavia, che i decimali sono più lenti dei doppi. Tuttavia, suppongo che nella maggior parte dei casi, il miglioramento delle prestazioni non sarà comunque evidente per l'utente. Naturalmente, nella maggior parte dei casi, neanche la precisione è necessaria. Eh.
Istanza Hunter,

Il virgola mobile decimale è in realtà MENO preciso del virgola mobile binaria utilizzando lo stesso numero di bit. Il vantaggio di Decimal è riuscire a rappresentare esattamente le frazioni DECIMAL come 0,01 che sono comuni nel calcolo finanziario.
dan04

Bene, questo non è del tutto corretto :) - in molti giochi i numeri in virgola mobile possono essere indesiderabili, a causa del fatto che non sono coerenti. Vedi qui
BlueRaja - Danny Pflughoeft il

4

In alcuni Contabilità, considerare la possibilità di utilizzare tipi integrali invece o in combinazione. Ad esempio, supponiamo che le regole in base alle quali operi richiedano ogni risultato di calcolo riportato con almeno 6 decimali e il risultato finale verrà arrotondato al penny più vicino.

Un calcolo di 1/6 di $ 100 produce $ 16.66666666666666 ..., quindi il valore effettuato in un foglio di lavoro sarà $ 16.666667. Sia il doppio che il decimale dovrebbero produrre quel risultato con precisione a 6 decimali. Tuttavia, possiamo evitare qualsiasi errore cumulativo portando avanti il ​​risultato come un numero intero 16666667. Ogni successivo calcolo può essere effettuato con la stessa precisione e portato avanti in modo simile. Continuando l'esempio, calcolo l'imposta sulle vendite in Texas su tale importo (16666667 * .0825 = 1375000). Aggiungendo i due (è un foglio di lavoro breve) 1666667 + 1375000 = 18041667. Spostare indietro il punto decimale ci dà 18,041667, o $ 18,04.

Mentre questo breve esempio non produrrebbe un errore cumulativo usando doppio o decimale, è abbastanza facile mostrare casi in cui il semplice calcolo del doppio o decimale e il proseguimento accumulerebbe un errore significativo. Se le regole in base alle quali operi richiedono un numero limitato di posizioni decimali, memorizzando ogni valore come numero intero moltiplicando per 10 ^ (numero richiesto di posizione decimale) e quindi dividendo per 10 ^ (richiesto numero di posizioni decimali) per ottenere l'effettivo il valore eviterà qualsiasi errore cumulativo.

In situazioni in cui non si verificano frazioni di centesimi (ad esempio un distributore automatico), non vi è alcun motivo per utilizzare tipi non integrali. Pensa semplicemente a contare i penny, non i dollari. Ho visto codice in cui ogni calcolo riguardava solo interi centesimi, ma l'uso del doppio ha portato a errori! Integer solo la matematica ha rimosso il problema. Quindi la mia risposta non convenzionale è, quando possibile, rinunciare sia al doppio che al decimale.


3

Se è necessario eseguire l'interrop binaria con altre lingue o piattaforme, potrebbe essere necessario utilizzare float o double, che sono standardizzati.


2

Nota: questo post si basa sulle informazioni sulle capacità del tipo decimale da http://csharpindepth.com/Articles/General/Decimal.aspx e sulla mia interpretazione di ciò che ciò significa. Presumo che Double sia normale doppia precisione IEEE.

Nota2: il più piccolo e il più grande in questo post si riferisce all'entità del numero.

Pro di "decimale".

  • "decimale" può rappresentare esattamente numeri che possono essere scritti come frazioni decimali (sufficientemente brevi), il doppio non può. Questo è importante nei registri finanziari e simili dove è importante che i risultati corrispondano esattamente a ciò che un umano farebbe i calcoli.
  • "decimale" ha una mantissa molto più grande di "doppio". Ciò significa che per valori all'interno del suo intervallo normalizzato "decimale" avrà una precisione molto più elevata rispetto al doppio.

Contro di decimale

  • Sarà molto più lento (non ho benchmark ma immagino almeno un ordine di grandezza forse più), il decimale non trarrà beneficio da alcuna accelerazione hardware e l'aritmetica su di esso richiederà una moltiplicazione / divisione relativamente costosa per potenze di 10 ( che è molto più costoso della moltiplicazione e della divisione per potenze di 2) per abbinare l'esponente prima dell'addizione / sottrazione e per riportare l'esponente nel suo intervallo dopo la moltiplicazione / divisione.
  • il decimale traboccerà prima del doppio. i decimali possono rappresentare solo numeri fino a ± 2 96 -1. In confronto, il doppio può rappresentare numeri fino a quasi ± 2 1024
  • il decimale effettuerà il underflow in precedenza. I numeri più piccoli rappresentabili in decimale sono ± 10 -28 . In confronto, il doppio può rappresentare valori fino a 2-149 (circa 10 -45 ) se sono supportati i numeri subnromici e 2-126 (circa 10 -38 ) se non lo sono.
  • il decimale occupa il doppio della memoria rispetto al doppio.

La mia opinione è che dovresti usare di default "decimale" per lavoro con denaro e altri casi in cui la corrispondenza del calcolo umano è esattamente importante e che dovresti usare il doppio come scelta predefinita per il resto del tempo.


2

Dipende da cosa ti serve.

Poiché float e double sono tipi di dati binari, ci sono alcune difficoltà ed errori nei numeri di arrotondamenti, quindi ad esempio double arrotonderebbe da 0,1 a 0,100000001490116, anche double arrotonderà da 1/3 a 0,33333334326441. In poche parole, non tutti i numeri reali hanno una rappresentazione accurata in doppi tipi

Fortunatamente C # supporta anche la cosiddetta aritmetica decimale in virgola mobile, in cui i numeri sono rappresentati tramite il sistema numerico decimale anziché tramite il sistema binario. Pertanto, l'aritmetica in virgola mobile decimale non perde precisione durante la memorizzazione e l'elaborazione di numeri in virgola mobile. Ciò lo rende immensamente adatto ai calcoli in cui è necessario un elevato livello di precisione.


0

Utilizzare i punti mobili se si valuta la prestazione rispetto alla correttezza.


6
I numeri decimali non sono più corretti, tranne in alcuni casi limitati che a volte (non sempre) sono importanti.
David Thornley,

0

Scegli il tipo in funzione della tua applicazione. Se hai bisogno di precisione come nell'analisi finanziaria, hai risposto alla tua domanda. Ma se la tua applicazione può accontentarsi di un preventivo, ok con il doppio.

La tua richiesta ha bisogno di un calcolo veloce o avrà tutto il tempo per darti una risposta? Dipende molto dal tipo di applicazione.

Grafica affamata? float o double è abbastanza. Analisi dei dati finanziari, meteorite che colpisce un tipo di precisione del pianeta? Quelli avrebbero bisogno di un po 'di precisione :)


8
Anche i numeri decimali sono stime. Sono conformi alle convenzioni dell'aritmetica finanziaria, ma non c'è alcun vantaggio, per esempio, nei calcoli che coinvolgono la fisica.
David Thornley,

0

Il decimale ha byte più ampi, il doppio è supportato nativamente dalla CPU. Il decimale è base-10, quindi una conversione da decimale a doppia sta avvenendo mentre viene calcolato un decimale.

For accounting - decimal
For finance - double
For heavy computation - double

Ricorda che .NET CLR supporta solo Math.Pow (doppio, doppio). Il decimale non è supportato.

.NET Framework 4

[SecuritySafeCritical]
public static extern double Pow(double x, double y);

0

Un doppio valore verrà serializzato sulla notazione scientifica per impostazione predefinita se tale notazione è più breve della visualizzazione decimale. (ad es., 00000003 saranno 3e-8) I valori decimali non verranno mai serializzati sulla notazione scientifica. Quando si serializza per il consumo da una parte esterna, questa può essere una considerazione.

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.