Przechowywanie pieniędzy w kolumnie dziesiętnej - jaka precyzja i skala?

173

Używam kolumny dziesiętnej do przechowywania wartości pieniężnych w bazie danych, a dziś zastanawiałem się, jakiej dokładności i skali użyć.

Ponieważ podobno kolumny typu char o stałej szerokości są bardziej wydajne, pomyślałem, że to samo może dotyczyć kolumn dziesiętnych. Czy to jest?

A jakiej precyzji i skali powinienem użyć? Myślałem o precyzji 24/8. Czy to przesada, za mało czy w porządku?


Oto, co postanowiłem zrobić:

  • Przechowuj kursy wymiany (jeśli dotyczy) w samej tabeli transakcji jako zmiennoprzecinkowe
  • Przechowuj walutę w tabeli kont
  • Kwota transakcji będzie wynosić DECIMAL(19,4)
  • Wszystkie obliczenia z wykorzystaniem kursu konwersji będą obsługiwane przez moją aplikację, więc mam kontrolę nad kwestiami dotyczącymi zaokrąglania

Nie sądzę, aby liczba zmiennoprzecinkowa dla współczynnika konwersji była problemem, ponieważ jest ona głównie w celach informacyjnych, a mimo to będę rzucać ją na ułamek dziesiętny.

Dziękuję wszystkim za cenny wkład.

Ivan
źródło
2
Zadaj sobie pytanie: czy naprawdę konieczne jest przechowywanie danych w postaci dziesiętnej? Czy mogę przechowywać dane jako centy / grosze -> liczby całkowite?
Terence
5
DECIMAL(19, 4) jest popularnym wyborem sprawdź to również sprawdź tutaj Formaty walut świata, aby zdecydować, ile miejsc dziesiętnych użyć, nadzieja pomaga.
shaijut

Odpowiedzi:

181

Jeśli szukasz jednego rozmiaru dla wszystkich, sugeruję, że DECIMAL(19, 4)jest to popularny wybór (szybki Google to znosi). Myślę, że pochodzi to ze starego typu danych VBA / Access / Jet Currency, będąc pierwszym typem dziesiętnym ze stałym przecinkiem w języku; Decimalpojawił się tylko w stylu „wersji 1.0” (tj. nie w pełni zaimplementowany) w VB6 / VBA6 / Jet 4.0.

Praktyczną zasadą przechowywania wartości dziesiętnych ze stałymi punktami jest przechowywanie co najmniej jednego miejsca po przecinku więcej niż jest to wymagane, aby umożliwić zaokrąglanie. Jednym z powodów odwzorowania starego Currencytypu z przodu na DECIMAL(19, 4)typ z tyłu było to, że Currencybankowcy wykazywali zaokrąglanie z natury, podczas gdy DECIMAL(p, s)zaokrąglanie przez obcięcie.

Dodatkowe miejsce po przecinku w pamięci DECIMALumożliwia zaimplementowanie niestandardowego algorytmu zaokrąglania zamiast przyjmowania wartości domyślnej dostawcy (a zaokrąglanie przez bankierów jest co najmniej alarmujące dla projektanta oczekującego, że wszystkie wartości kończące się na .5 zaokrągla się od zera) .

Tak, DECIMAL(24, 8)brzmi to dla mnie przesada. Większość walut jest podawana z dokładnością do czterech lub pięciu miejsc po przecinku. Znam sytuacje, w których wymagana jest skala dziesiętna wynosząca 8 (lub więcej), ale jest to sytuacja, w której `` normalna '' kwota pieniężna (powiedzmy cztery miejsca po przecinku) została proporcjonalna, co oznacza, że ​​dokładność dziesiętna powinna zostać odpowiednio zmniejszona (również rozważ typ zmiennoprzecinkowy w takich okolicznościach). I nikt nie ma dziś tak dużo pieniędzy, by wymagać dziesiętnej dokładności 24 :)

Jednak zamiast podejścia uniwersalnego, niektóre badania mogą być w porządku. Zapytaj swojego projektanta lub eksperta dziedzinowego o zasady księgowania, które mogą mieć zastosowanie: GAAP, EU, itp. Mglisto przypominam sobie niektóre transfery wewnątrzpaństwowe w UE z wyraźnymi zasadami zaokrąglania do pięciu miejsc po przecinku, a zatem używam ich DECIMAL(p, 6)do przechowywania. Wydaje się, że księgowi preferują cztery miejsca po przecinku.


PS Unikaj MONEYtypu danych SQL Server, ponieważ wiąże się to z poważnymi problemami z dokładnością podczas zaokrąglania, między innymi kwestiami, takimi jak przenośność itp. Zobacz blog Aarona Bertranda .


Microsoft i projektanci języków wybrali zaokrąglanie bankierów, ponieważ projektanci sprzętu wybrali to [cytat?]. Jest na przykład zapisana w normach Instytutu Inżynierów Elektryków i Elektroników (IEEE). A projektanci sprzętu wybrali to, ponieważ wolą to matematycy. Zobacz Wikipedię ; parafrazując: Wydanie Prawdopodobieństwa i teorii błędów z 1906 r. nazywało to „regułą komputera” („komputery” oznaczające ludzi wykonujących obliczenia).

onedaywhen
źródło
1
Zobacz tę odpowiedź i tę stronę, aby dowiedzieć się więcej o tym, dlaczego projektanci języków wybierają zaokrąglanie Bankera.
Nick Chammas,
1
onedaywhen: Zaokrąglanie bankierów nie należy do firmy Microsoft. @NickChammas: nie jest też wynalazkiem projektanta języka. Microsoft i projektanci języka wybrali go zasadniczo, ponieważ wybrali go projektanci sprzętu; jest on zapisany na przykład w normach Instytutu Inżynierów Elektryków i Elektroników (IEEE). Projektanci sprzętu wybrali to, ponieważ wolą to matematycy. Zobacz en.wikipedia.org/wiki/Rounding#History ; parafrazując: Wydanie Prawdopodobieństwa i teorii błędów z 1906 r. nazywało to „regułą komputera” („komputery” oznaczające ludzi wykonujących obliczenia).
phoog
9
więc czego powinienem używać dla Bitcoin?
Zestaw narzędzi,
1
@oneday, dlaczego jest DECIMAL(19, 4)bardziej popularne niż DECIMAL(19, 2)? Większość walut świata ma tylko dwa miejsca po przecinku.
Cokedude
3
@ zypA13510: tak, moje twierdzenie jest takie dziesięć lat temu! Ale to jest moja trzecia najczęściej głosowana odpowiedź i stanowi sporą część zmian w odniesieniu do mojego przedstawiciela SO, więc mam pieniądze :)
onedaywhen
105

Niedawno wdrożyliśmy system, który musi obsługiwać wartości w wielu walutach i przeliczać między nimi, i na własnej skórze odkryliśmy kilka rzeczy.

NIGDY NIE UŻYWAJ PUNKTÓW ZMIENNYCH DO PIENIĘDZY

Arytmetyka zmiennoprzecinkowa wprowadza niedokładności, których można nie zauważyć, dopóki czegoś nie schrzanią. Wszystkie wartości powinny być przechowywane jako liczby całkowite lub typy ze stałymi dziesiętnymi, a jeśli zdecydujesz się użyć typu stałego dziesiętnego, upewnij się, że dokładnie rozumiesz, co ten typ robi pod maską (tj. Czy wewnętrznie używa liczby całkowitej czy zmiennoprzecinkowej rodzaj).

Kiedy musisz wykonać obliczenia lub konwersje:

  1. Konwertuj wartości na zmiennoprzecinkowe
  2. Oblicz nową wartość
  3. Zaokrąglij liczbę i zamień ją z powrotem na liczbę całkowitą

Podczas konwertowania liczby zmiennoprzecinkowej z powrotem na liczbę całkowitą w kroku 3, nie rzucaj jej po prostu - użyj funkcji matematycznej, aby ją najpierw zaokrąglić. Zwykle tak będzie round, chociaż w szczególnych przypadkach może to być floorlub ceil. Poznaj różnicę i rozważnie wybierz.

Przechowuj typ liczby obok wartości

Może to nie być tak ważne dla Ciebie, jeśli obsługujesz tylko jedną walutę, ale było to dla nas ważne w przypadku obsługi wielu walut. Użyliśmy 3-znakowego kodu waluty, takiej jak USD, GBP, JPY, EUR itd.

W zależności od sytuacji pomocne może być również przechowywanie:

  • Czy liczba jest przed czy po opodatkowaniu (i jaka była stawka podatku)
  • Czy liczba jest wynikiem konwersji (i z czego została przekonwertowana)

Poznaj granice dokładności liczb, z którymi masz do czynienia

W przypadku wartości rzeczywistych chcesz być tak dokładny, jak najmniejsza jednostka waluty. Oznacza to, że nie masz wartości mniejszych niż cent, pens, jen, torfowisko itp. Nie przechowuj wartości z większą dokładnością niż ta bez powodu.

Wewnętrznie możesz wybrać mniejsze wartości, w takim przypadku jest to inny typ wartości waluty . Upewnij się, że twój kod wie, który jest który i nie pomieszał ich. Unikaj używania wartości zmiennoprzecinkowych nawet tutaj.


Dodając wszystkie te zasady razem, zdecydowaliśmy się na następujące zasady. W działającym kodzie waluty są przechowywane przy użyciu liczby całkowitej dla najmniejszej jednostki.

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

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

W bazie danych wartości są przechowywane jako ciąg w następującym formacie:

USD:2500

To przechowuje wartość 25,00 $. Byliśmy w stanie to zrobić tylko dlatego, że kod zajmujący się walutami nie musi znajdować się w samej warstwie bazy danych, więc wszystkie wartości można najpierw przekonwertować na pamięć. Inne sytuacje bez wątpienia nadają się do innych rozwiązań.


A jeśli nie wyjaśniłem tego wcześniej, nie używaj pływaka!

Marcus Downing
źródło
1
Nigdy nie mów nigdy: czasami kwoty pieniędzy są proporcjonalne i trzeba je później zsumować. Przykład: podzielenie całkowitej dywidendy (stosunkowo niewielkiej) podzielonej na liczbę wyemitowanych akcji (stosunkowo niewielkiej), aby uzyskać wartość netto na akcję. Czasami unosić rund lepiej :)
onedaywhen
23
Stoję przy swoim nigdy. Specyfikacje zmiennoprzecinkowe mają niedokładności, które dodadzą się do liczby wykonanych obliczeń. Jeśli chcesz przechowywać wartości mniejsze niż cent lub grosz, określ wymagany poziom dokładności i trzymaj się go. Nie używaj pływaka. Poważnie. To zły pomysł.
Marcus Downing
4
Ta odpowiedź jest również zgodna z najlepszymi praktykami dotyczącymi języka JavaScript przedstawionymi przez Douglasa Crockforda w jego serii „Crockford on JavaScript”, w której zaleca wykonywanie wszystkich obliczeń walutowych w PENNIES, aby uniknąć błędów maszyny w zaokrąglaniu. Więc jeśli pracujesz z walutami w javascript, bardzo sensowne jest przechowywanie wartości w ten sposób.
Paperreduction
1
@onedayw przypadku tych przypadków netto na udział można zsumować kwoty proporcjonalne i porównać je z pierwotną sumą i opracować strategię postępowania z resztą (przy użyciu typów dziesiętnych / całkowitych).
Shiv
2
W przypadku MySQL zalecam przechowywanie liczby całkowitej (2500) jako a, bigintjeśli zamierzasz sortować według kwoty. I nie trać czasu na 32-bitowe PHP w przypadku dużych liczb całkowitych, uaktualnij do wersji 64-bitowej lub Node.JS;)
Ricky Boyce
4

Podczas obsługi pieniędzy w MySQL, użyj DECIMAL (13,2), jeśli znasz dokładność swoich wartości pieniężnych, lub użyj DOUBLE, jeśli chcesz uzyskać szybką, wystarczająco dobrą przybliżoną wartość. Jeśli więc Twoja aplikacja musi obsługiwać wartości pieniężne do biliona dolarów (lub euro lub funtów), powinno to działać:

DECIMAL(13, 2)

Lub, jeśli chcesz zachować zgodność z GAAP, użyj:

DECIMAL(13, 4)
pollux1er
źródło
3
Czy możesz zamieścić link do określonej części wytycznych GAAP zamiast do zawartości 2500-stronicowego dokumentu? Dzięki.
ReactingToAngularVues
@ReactingToAngularVues wygląda na to, że strona się zmieniła. Przepraszamy
pollux1er
2

Cztery miejsca po przecinku zapewniłyby dokładność przechowywania najmniejszych na świecie podjednostek waluty. Możesz zejść dalej, jeśli potrzebujesz dokładności mikropłatności (nanopłat ?!).

Ja też wolę DECIMALtypy pieniędzy specyficzne dla DBMS, bezpieczniej zachowujesz tego rodzaju logikę w aplikacji IMO. Innym podejściem w tych samych wierszach jest po prostu użycie [długiej] liczby całkowitej, z formatowaniem na ¤unit.subunit dla czytelności dla człowieka (¤ = symbol waluty) wykonanym na poziomie aplikacji.

bobince
źródło
1

Typ danych Money na serwerze SQL Server ma cztery cyfry po przecinku.

Z SQL Server 2000 Books Online:

Dane pieniężne przedstawiają dodatnie lub ujemne kwoty pieniędzy. W Microsoft® SQL Server ™ 2000 dane pieniężne są przechowywane przy użyciu typów danych money i smallmoney. Dane pieniężne mogą być przechowywane z dokładnością do czterech miejsc po przecinku. Typ danych Money służy do przechowywania wartości z zakresu od -922337203685477,5808 do +922337203685477,5807 (wymaga 8 bajtów do przechowywania wartości). Typ danych smallmoney służy do przechowywania wartości z zakresu od -214 748,3648 do 214 748,3647 (do przechowywania wartości wymagane są 4 bajty). Jeśli wymagana jest większa liczba miejsc dziesiętnych, użyj zamiast tego typu danych dziesiętnych.

Austin Salonen
źródło
1

Czasami trzeba będzie zapłacić mniej niż centa, a istnieją waluty międzynarodowe, które wykorzystują bardzo duże kwoty demoniczne. Na przykład możesz obciążyć swoich klientów kwotą 0,088 centa za transakcję. W mojej bazie danych Oracle kolumny są zdefiniowane jako NUMBER (20,4)

W W.
źródło
1

Jeśli zamierzasz wykonywać jakiekolwiek operacje arytmetyczne w bazie danych (pomnożenie stawek rozliczeniowych itd.), Prawdopodobnie będziesz potrzebować dużo większej precyzji niż sugerują ludzie, z tych samych powodów, dla których nigdy byś nie chcą używać w kodzie aplikacji wartości mniejszych niż wartość zmiennoprzecinkowa o podwójnej precyzji.

Hank Gay
źródło
To było to, o czym myślałem, ale w kategoriach kursów walut (tj. Przeliczania dolarów Zimbawe na USD). Przeprowadzę kilka eksperymentów na bazach danych, których używam (psql, sqlite), aby zobaczyć, jak radzą sobie z zaokrąglaniem do bardzo małych miejsc po przecinku.
Ivan
Poza tym, czy elementy zmiennoprzecinkowe nie mają problemów z dokładnością w niektórych dbms / językach?
Ivan
2
zmiennoprzecinkowe mają problemy z dokładnością we WSZYSTKICH językach.
Marcus Downing
Najbardziej powszechnym zaleceniem w dzisiejszych czasach jest użycie arbitralnej precyzji (myśl BigDecimal), ale przez długi czas była to podwójna precyzja (myśl doublezamiast float). Ponadto, w niektórych przypadkach arbitralna precyzja ma znaczący wpływ na wydajność. Testowanie to zdecydowanie właściwe podejście.
Hank Gay
0

Gdyby używany był serwer IBM Informix Dynamic Server, miałbyś typ MONEY, który jest podrzędnym wariantem typu DECIMAL lub NUMERIC. Jest to zawsze typ stałoprzecinkowy (podczas gdy DECIMAL może być typem zmiennoprzecinkowym). Możesz określić skalę od 1 do 32 i dokładność od 0 do 32 (domyślnie skala 16 i dokładność 2). Tak więc, w zależności od tego, co chcesz przechowywać, możesz użyć DECIMAL (16,2) - wciąż wystarczająco dużego, aby utrzymać deficyt federalny USA, z dokładnością do jednego centa - lub możesz użyć mniejszego zakresu lub większej liczby miejsc dziesiętnych.

Jonathan Leffler
źródło
0

Wydaje mi się, że w dużej mierze wymagania twoje lub twojego klienta powinny dyktować, jakiej dokładności i skali użyć. Na przykład w przypadku witryny handlu elektronicznego, nad którą pracuję, która obsługuje pieniądze tylko w GBP, zostałem zobowiązany do zachowania wartości Decimal (6, 2).

ayaz
źródło
0

Późna odpowiedź tutaj, ale użyłem

DECIMAL(13,2)

co mam rację, powinno pozwolić do 99 999 999 999,99.

Mike Upjohn
źródło