Archiviazione di denaro in una colonna decimale: quale precisione e scala?


173

Sto usando una colonna decimale per archiviare i valori di denaro in un database e oggi mi chiedevo quale precisione e scala usare.

Dato che presumibilmente le colonne di caratteri con larghezza fissa sono più efficienti, stavo pensando che lo stesso potesse essere vero per le colonne decimali. È?

E quale precisione e scala dovrei usare? Pensavo alla precisione 24/8. È eccessivo, non abbastanza o ok?


Questo è quello che ho deciso di fare:

  • Memorizza i tassi di conversione (se applicabile) nella stessa tabella delle transazioni, come float
  • Memorizza la valuta nella tabella del conto
  • L'importo della transazione sarà a DECIMAL(19,4)
  • Tutti i calcoli che utilizzano un tasso di conversione saranno gestiti dalla mia applicazione in modo da mantenere il controllo dei problemi di arrotondamento

Non penso che un float per il tasso di conversione sia un problema, poiché è principalmente per riferimento e lo proverò comunque con un decimale.

Grazie a tutti per il vostro prezioso contributo.


2
Ponetevi la domanda: è davvero necessario archiviare i dati in forma decimale? Non posso memorizzare i dati come centesimi / centesimi -> numeri interi?
Terence,

5
DECIMAL(19, 4) è una scelta popolare controlla questo controlla anche qui i formati di valuta del mondo per decidere quanti posti decimali usare, la speranza aiuta.
shaijut,

Risposte:


181

Se stai cercando una taglia unica, suggerirei che DECIMAL(19, 4)sia una scelta popolare (un veloce Google lo dimostra). Penso che questo provenga dal vecchio tipo di dati VBA / Access / Jet Currency, essendo il primo tipo decimale a virgola fissa nella lingua; Decimaldisponibile solo in stile "versione 1.0" (ovvero non completamente implementato) in VB6 / VBA6 / Jet 4.0.

La regola empirica per l' archiviazione di valori decimali in virgola fissa è di memorizzare almeno una posizione decimale in più di quella effettivamente richiesta per consentire l'arrotondamento. Uno dei motivi per mappare il vecchio Currencytipo nel front-end per DECIMAL(19, 4)digitare nel back-end è stato il fatto che i Currencybanchieri presentavano un arrotondamento per natura, mentre DECIMAL(p, s)arrotondato per troncamento.

Un'ulteriore posizione decimale in memoria DECIMALconsente di implementare un algoritmo di arrotondamento personalizzato anziché assumere il valore predefinito del fornitore (e l'arrotondamento dei banchieri è, a dir poco, allarmante per un progettista che si aspetta che tutti i valori che terminano con 0,5 si arrotondino per difetto a zero) .

Sì, mi DECIMAL(24, 8)sembra eccessivo. La maggior parte delle valute sono quotate con quattro o cinque cifre decimali. Io so di situazioni in cui una scala decimale di 8 (o più) è richiesta ma questo è dove un importo monetario 'normale' (diciamo quattro cifre decimali) è stata pro rata'd, che implica la precisione decimale deve essere ridotto di conseguenza (si consideri anche un tipo a virgola mobile in tali circostanze). E nessuno ha così tanti soldi al giorno d'oggi per richiedere una precisione decimale di 24 :)

Tuttavia, piuttosto che un approccio unico per tutti, alcune ricerche potrebbero essere in ordine. Chiedi al tuo progettista o esperto di dominio le regole di contabilità che potrebbero essere applicabili: GAAP, UE, ecc. Ricordo vagamente alcuni trasferimenti all'interno dello stato dell'UE con regole esplicite per l'arrotondamento a cinque decimali, quindi usando DECIMAL(p, 6)per l'archiviazione. I contabili sembrano generalmente favorire quattro decimali.


PS Evita il MONEYtipo di dati di SQL Server perché presenta seri problemi di precisione durante l'arrotondamento, tra le altre considerazioni come la portabilità, ecc. Vedi il blog di Aaron Bertrand .


I progettisti Microsoft e del linguaggio hanno scelto l'arrotondamento del banchiere perché i progettisti hardware l'hanno scelto [citazione?]. È incluso negli standard dell'Institute of Electrical and Electronics Engineers (IEEE), per esempio. E i progettisti hardware l'hanno scelto perché i matematici lo preferiscono. Vedi Wikipedia ; parafrasando: l'edizione del 1906 di Probabilità e teoria degli errori chiamava questa "regola del computer" ("computer" che significa umani che eseguono calcoli).


1
Vedi questa risposta e questa pagina per ulteriori informazioni sul perché i designer linguistici scelgono l'arrotondamento del banchiere.
Nick Chammas,

1
onedaywhen: l'arrotondamento del banchiere non è dovuto a Microsoft. @NickChammas: non è nemmeno un'invenzione del designer linguistico. I progettisti Microsoft e del linguaggio lo hanno sostanzialmente scelto perché lo hanno scelto i progettisti hardware; è incluso negli standard dell'Institute of Electrical and Electronics Engineers (IEEE), per esempio. E i progettisti hardware l'hanno scelto perché i matematici lo preferiscono. Vedi en.wikipedia.org/wiki/Rounding#History ; parafrasando: l'edizione del 1906 di Probabilità e teoria degli errori chiamava questa "regola del computer" ("computer" che significa umani che eseguono calcoli).
phoog

9
quindi cosa dovrei usare per Bitcoin?
Toolkit,

1
@onedayquando perché è DECIMAL(19, 4)più popolare di DECIMAL(19, 2)? La maggior parte delle valute mondiali ha solo due decimali.
cokedude

3
@ zypA13510: sì, la mia affermazione è così dieci anni fa! Ma questo è il mio terzo più votato per la risposta e rappresenta una buona dose di cambiamento per quanto riguarda il mio rappresentante SO, quindi mi chiedo :)
onedayquando l'

105

Di recente abbiamo implementato un sistema che deve gestire i valori in più valute e convertirle tra loro, e abbiamo capito alcune cose nel modo più duro.

NON UTILIZZARE MAI NUMERI DI PUNTI GALLEGGIANTI PER SOLDI

L'aritmetica in virgola mobile introduce inesattezze che potrebbero non essere notate fino a quando non hanno rovinato qualcosa. Tutti i valori devono essere archiviati come numeri interi o decimali fissi e, se si sceglie di utilizzare un tipo decimale fisso, assicurarsi di comprendere esattamente cosa fa quel tipo sotto il cofano (ovvero, utilizza internamente un numero intero o virgola mobile genere).

Quando è necessario eseguire calcoli o conversioni:

  1. Converti i valori in virgola mobile
  2. Calcola nuovo valore
  3. Arrotondare il numero e riconvertirlo in un numero intero

Quando converti un numero in virgola mobile in un numero intero nel passaggio 3, non limitarti a lanciarlo, ma usa una funzione matematica per arrotondarlo per primo. Questo di solito lo sarà round, anche se in casi speciali potrebbe essere flooro ceil. Conosci la differenza e scegli attentamente.

Memorizza il tipo di un numero accanto al valore

Questo potrebbe non essere così importante per te se stai gestendo solo una valuta, ma è stato importante per noi nella gestione di più valute. Abbiamo usato il codice di 3 caratteri per una valuta, come USD, GBP, JPY, EUR, ecc.

A seconda della situazione, può anche essere utile memorizzare:

  • Se il numero è prima o dopo le imposte (e quale era l'aliquota fiscale)
  • Se il numero è il risultato di una conversione (e da cosa è stato convertito)

Conosci i limiti di precisione dei numeri con cui hai a che fare

Per valori reali, vuoi essere preciso come la più piccola unità della valuta. Ciò significa che non hai valori inferiori a un centesimo, un penny, uno yen, un fen, ecc. Non memorizzare valori con una precisione superiore a quella senza motivo.

Internamente, puoi scegliere di gestire valori inferiori, nel qual caso si tratta di un diverso tipo di valore valutario . Assicurati che il tuo codice sappia quale è quale e che non li confonda. Evita di usare valori in virgola mobile anche qui.


Sommando tutte quelle regole insieme, abbiamo deciso le seguenti regole. Nel codice in esecuzione, le valute vengono archiviate utilizzando un numero intero per l'unità più piccola.

class Currency {
   String code;       //  eg "USD"
   int value;         //  eg 2500
   boolean converted;
}

class Price {
   Currency grossValue;
   Currency netValue;
   Tax taxRate;
}

Nel database, i valori sono archiviati come stringa nel seguente formato:

USD:2500

Ciò memorizza il valore di $ 25,00. Siamo stati in grado di farlo solo perché il codice che si occupa delle valute non ha bisogno di essere all'interno del livello del database stesso, quindi tutti i valori possono essere prima convertiti in memoria. Altre situazioni si presteranno senza dubbio ad altre soluzioni.


E nel caso non lo avessi chiarito prima, non usare float!


1
Mai dire mai: a volte gli importi in denaro sono proporzionali e devono essere ricaricati in un secondo momento. Esempio: dividendo il dividendo totale (relativamente piccolo) diviso per il numero di azioni in circolazione (relativamente piccolo) da dare netto per azione. A volte gira meglio :)
onedayquando il

23
Sto al mio fianco. Le specifiche in virgola mobile hanno imprecisioni che sommeranno il maggior numero di calcoli effettuati. Se è necessario memorizzare valori inferiori a un centesimo o centesimo, definire il livello di precisione necessario e attenersi ad esso. Non usare il galleggiante. Sul serio. È una cattiva idea.
Marcus Downing,

4
Questa risposta si allinea anche con le migliori pratiche javascript descritte da Douglas Crockford nella sua "serie Crockford su JavaScript", dove raccomanda di fare tutti i calcoli delle valute in PENNIES per evitare errori macchina nell'arrotondamento. Quindi, se stai lavorando con le valute in JavaScript, ha molto senso memorizzare il valore in questo modo.
paperreduction,

1
@onedaywhen Quei casi netti per azione, è possibile sommare gli importi proporzionali e confrontarli con il totale originale e elaborare una strategia per gestire il resto (quando si utilizzano tipi decimali / interi).
Shiv,

2
Per Mysql, raccomando di memorizzare il numero intero (2500) come bigintse si intende ordinare in base all'importo. E non perdere tempo con PHP 32 bit quando hai a che fare con grandi numeri interi, passa a 64 bit o Node.JS;)
Ricky Boyce,

4

Quando gestisci i soldi in MySQL, usa DECIMAL (13,2) se conosci la precisione dei tuoi valori in denaro o usa DOUBLE se vuoi solo un valore approssimativo abbastanza veloce. Quindi, se la tua applicazione deve gestire valori di denaro fino a un trilione di dollari (o euro o sterline), allora dovrebbe funzionare:

DECIMAL(13, 2)

Oppure, se è necessario rispettare GAAP, utilizzare:

DECIMAL(13, 4)

3
Puoi collegarti alla parte specifica delle linee guida GAAP anziché al contenuto di un documento di 2500 pagine? Grazie.
ReactingToAngularVues

@ReactingToAngularVues sembra che la pagina sia cambiata. Siamo spiacenti
pollux1er il

2

4 cifre decimali ti darebbero la precisione di memorizzare le sottounità monetarie più piccole del mondo. Puoi smontarlo ulteriormente se hai bisogno di precisione di micropagamento (nanopagamento ?!).

Anch'io preferisco DECIMALtipi di denaro specifici per DBMS, sei più sicuro mantenendo quel tipo di logica nell'applicazione IMO. Un altro approccio sulla stessa linea è semplicemente quello di utilizzare un [lungo] intero, con la formattazione in ¤unit.subunit per la leggibilità umana (¤ = simbolo di valuta) effettuata a livello di applicazione.


1

Il tipo di dati money su SQL Server ha quattro cifre dopo il decimale.

Dalla documentazione online di SQL Server 2000:

I dati monetari rappresentano importi di denaro positivi o negativi. In Microsoft® SQL Server ™ 2000, i dati monetari vengono archiviati utilizzando i tipi di dati money e smallmoney. I dati monetari possono essere memorizzati con una precisione di quattro cifre decimali. Utilizzare il tipo di dati monetari per memorizzare valori nell'intervallo compreso tra -922.337.203.685.477.5808 e +922.337.203.685.477.5807 (richiede 8 byte per memorizzare un valore). Utilizzare il tipo di dati smallmoney per memorizzare valori nell'intervallo compreso tra -214.748.3648 e 214.748.3647 (richiede 4 byte per memorizzare un valore). Se è richiesto un numero maggiore di posizioni decimali, utilizzare invece il tipo di dati decimali.


1

A volte dovrai andare a meno di un centesimo e ci sono valute internazionali che usano demoniazioni molto grandi. Ad esempio, potresti addebitare ai tuoi clienti 0,088 centesimi per transazione. Nel mio database Oracle le colonne sono definite come NUMBER (20,4)


1

Se eseguirai qualsiasi tipo di operazione aritmetica nel DB (moltiplicando i tassi di fatturazione e così via), probabilmente vorrai molta più precisione di quanto suggeriscano le persone qui, per gli stessi motivi che non avresti mai desidera utilizzare qualcosa di meno di un valore a virgola mobile a precisione doppia nel codice dell'applicazione.


Era quello che stavo pensando, ma in termini di tassi di cambio (ovvero conversione di dollari dello Zimbawe in USD). Eseguirò alcuni esperimenti sui database che sto usando (psql, sqlite) per vedere come gestiscono l'arrotondamento su decimali molto piccoli.
Ivan,

Inoltre, i float non hanno problemi di precisione in alcuni dbms / lingue?
Ivan,

2
i float hanno problemi di precisione in TUTTE le lingue.
Marcus Downing,

La raccomandazione più comune in questi giorni è quella di usare una precisione arbitraria (pensa BigDecimal), ma per lungo tempo è stata una doppia precisione (pensa doubleinvece di float). Inoltre, la precisione arbitraria comporta in alcuni casi significative penalità di prestazione. Il test è sicuramente l'approccio giusto.
Hank Gay,

0

Se si stesse utilizzando IBM Informix Dynamic Server, si avrebbe un tipo DENARO che è una variante minore sul tipo DECIMALE o NUMERICO. È sempre un tipo a virgola fissa (mentre DECIMAL può essere un tipo a virgola mobile). È possibile specificare una scala da 1 a 32 e una precisione da 0 a 32 (l'impostazione predefinita è una scala di 16 e una precisione di 2). Quindi, a seconda di ciò che è necessario conservare, è possibile utilizzare DECIMAL (16,2) - ancora abbastanza grande da contenere il deficit federale degli Stati Uniti, al centesimo più vicino - oppure è possibile utilizzare un intervallo più piccolo o più decimali.


0

Penso che, in gran parte, i requisiti del cliente o del cliente debbano dettare quale precisione e scala utilizzare. Ad esempio, per il sito Web di e-commerce su cui sto lavorando che tratta denaro solo in GBP, mi è stato richiesto di mantenerlo su Decimale (6, 2).


0

Una risposta in ritardo qui, ma ho usato

DECIMAL(13,2)

che ho ragione nel pensare dovrebbe consentire fino a 99.999.999.999,99.

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.