Menyimpan uang dalam kolom desimal - presisi dan skala apa?

173

Saya menggunakan kolom desimal untuk menyimpan nilai uang pada database, dan hari ini saya bertanya-tanya presisi dan skala apa yang digunakan.

Karena seharusnya kolom char dengan lebar tetap lebih efisien, saya berpikir hal yang sama bisa berlaku untuk kolom desimal. Apakah itu?

Dan presisi dan skala apa yang harus saya gunakan? Saya sedang berpikir presisi 24/8. Apakah itu berlebihan, tidak cukup atau ok?


Inilah yang saya putuskan untuk lakukan:

  • Simpan kurs konversi (bila berlaku) dalam tabel transaksi itu sendiri, sebagai float
  • Simpan mata uang di tabel akun
  • Jumlah transaksi akan a DECIMAL(19,4)
  • Semua perhitungan menggunakan tingkat konversi akan ditangani oleh aplikasi saya jadi saya tetap mengendalikan masalah pembulatan

Saya tidak berpikir float untuk tingkat konversi adalah masalah, karena sebagian besar untuk referensi, dan saya akan tetap menggunakan desimal.

Terima kasih semua atas masukan berharga Anda.

Ivan
sumber
2
Tanyakan kepada diri Anda pertanyaan: Apakah benar-benar perlu untuk menyimpan data dalam bentuk desimal? Tidak bisakah saya menyimpan data sebagai Cents / Pennies -> integer?
Terence
5
DECIMAL(19, 4) adalah pilihan yang populer periksa ini juga periksa di sini Format Mata Uang Dunia untuk memutuskan berapa banyak tempat desimal yang digunakan, semoga membantu.
shaijut

Jawaban:

181

Jika Anda mencari yang satu ukuran untuk semua, saya sarankan DECIMAL(19, 4)adalah pilihan yang populer (Google cepat menjelaskan ini). Saya pikir ini berasal dari tipe data VBA / Access / Jet Currency lama, menjadi tipe desimal titik tetap pertama dalam bahasa; Decimalhanya datang dalam gaya 'versi 1.0' (yaitu tidak sepenuhnya diimplementasikan) di VB6 / VBA6 / Jet 4.0.

Aturan praktis untuk penyimpanan nilai desimal titik tetap adalah menyimpan setidaknya satu tempat desimal lebih dari yang sebenarnya Anda perlukan untuk pembulatan. Salah satu alasan untuk memetakan Currencytipe lama di ujung depan untuk DECIMAL(19, 4)mengetik di ujung belakang adalah karena Currencydipamerkan pembulatan bankir secara alami, sedangkan DECIMAL(p, s)dibulatkan oleh pemotongan.

Tempat desimal ekstra dalam penyimpanan untuk DECIMALmemungkinkan algoritme pembulatan kustom diterapkan daripada mengambil standar vendor (dan pembulatan bankir mengkhawatirkan, untuk sedikitnya, bagi desainer yang mengharapkan semua nilai yang diakhiri dengan 0,5 untuk memisahkan dari nol) .

Ya, DECIMAL(24, 8)kedengarannya seperti berlebihan bagi saya. Sebagian besar mata uang dikutip ke empat atau lima tempat desimal. Saya tahu situasi di mana skala desimal 8 (atau lebih) berada diperlukan tetapi ini adalah di mana 'normal' jumlah moneter (mengatakan empat tempat desimal) telah pro rata'd, menyiratkan presisi desimal harus dikurangi sesuai (juga mempertimbangkan tipe floating point dalam keadaan seperti itu). Dan tidak ada yang memiliki banyak uang saat ini untuk membutuhkan ketepatan desimal 24 :)

Namun, daripada pendekatan satu ukuran untuk semua, beberapa penelitian mungkin dilakukan. Tanyakan desainer atau pakar domain Anda tentang aturan akuntansi yang mungkin berlaku: GAAP, UE, dll. Saya secara samar-samar mengingat beberapa transfer intra-negara UE dengan aturan eksplisit untuk membulatkan ke lima tempat desimal, oleh karena itu digunakan DECIMAL(p, 6)untuk penyimpanan. Akuntan umumnya tampaknya menyukai empat tempat desimal.


PS Hindari MONEYtipe data SQL Server karena memiliki masalah serius dengan akurasi ketika pembulatan, antara lain pertimbangan portabilitas dll. Lihat blog Aaron Bertrand .


Microsoft dan perancang bahasa memilih pembulatan bankir karena perancang perangkat keras memilihnya [rujukan?]. Ini diabadikan dalam standar Institute of Electrical Engineers (IEEE), misalnya. Dan perancang perangkat keras memilihnya karena ahli matematika lebih menyukainya. Lihat Wikipedia ; untuk parafrase: Edisi 1906 Probabilitas dan Teori Kesalahan menyebut ini 'aturan komputer' ("komputer" yang berarti manusia yang melakukan perhitungan).

suatu hari nanti
sumber
1
Lihat jawaban ini dan halaman ini untuk lebih banyak tentang mengapa perancang bahasa memilih pembulatan Bankir.
Nick Chammas
1
onedaywhen: Pembulatan Banker bukan karena Microsoft. @NickChammas: juga bukan merupakan perancang bahasa. Microsoft dan perancang bahasa pada dasarnya memilihnya karena perancang perangkat keras memilihnya; itu diabadikan dalam standar Institute of Electrical Engineers (IEEE), misalnya. Dan perancang perangkat keras memilihnya karena ahli matematika lebih menyukainya. Lihat en.wikipedia.org/wiki/Rounding#History ; untuk parafrase: Edisi 1906 Probabilitas dan Teori Kesalahan menyebut ini 'aturan komputer' ("komputer" yang berarti manusia yang melakukan perhitungan).
phoog
9
jadi apa yang harus saya gunakan untuk Bitcoin?
Toolkit
1
@onedaywhen mengapa DECIMAL(19, 4)lebih populer daripada DECIMAL(19, 2)? Sebagian besar mata uang dunia hanya dua tempat desimal.
Cokedude
3
@ zypA13510: yeah, pernyataan saya sepuluh tahun yang lalu! Tapi ini jawaban ketiga saya yang paling banyak dipilih dan menyumbang sejumlah perubahan yang adil terkait dengan perwakilan SO saya, jadi saya cintai :)
onedaywhen
105

Kami baru-baru ini mengimplementasikan sistem yang perlu menangani nilai dalam berbagai mata uang dan mengkonversi di antara mereka, dan menemukan beberapa hal dengan cara yang sulit.

JANGAN PERNAH MENGGUNAKAN NOMOR TITIK FLOATING UNTUK UANG

Aritmatika titik mengambang memperkenalkan ketidakakuratan yang mungkin tidak diperhatikan sampai mereka mengacaukan sesuatu. Semua nilai harus disimpan sebagai bilangan bulat atau tipe desimal tetap, dan jika Anda memilih untuk menggunakan tipe desimal tetap maka pastikan Anda memahami dengan tepat apa yang dilakukan tipe di bawah tenda (yaitu, apakah secara internal menggunakan bilangan bulat atau titik mengambang Tipe).

Ketika Anda perlu melakukan perhitungan atau konversi:

  1. Konversi nilai menjadi floating point
  2. Hitung nilai baru
  3. Membulatkan angka dan mengubahnya kembali menjadi bilangan bulat

Saat mengonversi angka floating point kembali ke integer pada langkah 3, jangan hanya melemparkannya - gunakan fungsi matematika untuk membulatkannya terlebih dahulu. Ini biasanya round, meskipun dalam kasus khusus bisa flooratau ceil. Ketahui perbedaannya dan pilih dengan cermat.

Simpan jenis angka di samping nilainya

Ini mungkin tidak sepenting bagi Anda jika Anda hanya menangani satu mata uang, tetapi penting bagi kami dalam menangani banyak mata uang. Kami menggunakan kode 3 karakter untuk mata uang, seperti USD, GBP, JPY, EUR, dll.

Bergantung pada situasinya, mungkin berguna juga untuk menyimpan:

  • Apakah nomor tersebut sebelum atau sesudah pajak (dan berapa tarif pajaknya)
  • Apakah nomor tersebut merupakan hasil konversi (dan dari mana itu dikonversi)

Ketahui batas akurasi angka yang Anda hadapi

Untuk nilai nyata, Anda ingin setepat unit terkecil dari mata uang. Ini berarti Anda tidak memiliki nilai lebih kecil dari sen, sen, yen, fen, dll. Jangan menyimpan nilai dengan akurasi lebih tinggi dari itu tanpa alasan.

Secara internal, Anda dapat memilih untuk berurusan dengan nilai yang lebih kecil, dalam hal ini jenis nilai mata uang yang berbeda . Pastikan kode Anda tahu yang mana dan tidak mencampuradukkannya. Hindari menggunakan nilai floating point bahkan di sini.


Menambahkan semua aturan itu bersama-sama, kami memutuskan aturan berikut. Dalam menjalankan kode, mata uang disimpan menggunakan integer untuk unit terkecil.

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

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

Dalam database, nilai-nilai disimpan sebagai string dalam format berikut:

USD:2500

Itu menyimpan nilai $ 25,00. Kami dapat melakukan itu hanya karena kode yang berhubungan dengan mata uang tidak perlu berada di dalam lapisan basis data itu sendiri, sehingga semua nilai dapat dikonversi menjadi memori terlebih dahulu. Situasi lain tidak diragukan lagi akan memberikan solusi lain.


Dan jika saya tidak menjelaskan sebelumnya, jangan gunakan float!

Marcus Downing
sumber
1
Never say never: terkadang jumlah uang pro rata dan perlu ditambah lagi nanti. Contoh: membagi total dividen (relatif kecil) dibidani dengan jumlah saham yang diterbitkan (relatif kecil) untuk memberikan net per saham. Terkadang mengapung lebih baik :)
onedaywhen
23
Saya berdiri tidak pernah. Spesifikasi floating point memiliki ketidakakuratan yang akan menambah semakin banyak perhitungan yang Anda lakukan. Jika Anda perlu menyimpan nilai lebih kecil dari satu sen atau satu sen, maka tentukan tingkat akurasi yang Anda butuhkan dan patuhi itu. Jangan gunakan float. Serius. Itu ide yang buruk.
Marcus Downing
4
Jawaban ini juga sejalan dengan praktik terbaik javascript yang diuraikan oleh Douglas Crockford dalam "seri Crockford on JavaScript", di mana ia merekomendasikan untuk melakukan semua perhitungan mata uang dalam PENNIES untuk menghindari kesalahan mesin dalam pembulatan. Jadi, jika Anda bekerja dengan mata uang dalam javascript, sangat masuk akal untuk menyimpan nilai dengan cara ini.
paperreduction
1
@oneday saat Kasus bersih per saham tersebut, Anda dapat menjumlahkan jumlah yang di-rata rata dan membandingkannya dengan total aslinya dan menyusun strategi untuk menangani sisanya (saat menggunakan tipe desimal / bilangan bulat).
Shiv
2
Untuk Mysql, saya sarankan menyimpan integer (2500) sebagai bigintjika Anda berniat mengurutkan berdasarkan jumlah. Dan jangan buang waktu Anda dengan PHP 32bit ketika berhadapan dengan bilangan bulat besar, tingkatkan ke 64bit atau Node.JS;)
Ricky Boyce
4

Saat menangani uang di MySQL, gunakan DECIMAL (13,2) jika Anda tahu ketepatan nilai uang Anda atau gunakan DOUBLE jika Anda hanya menginginkan nilai perkiraan cepat yang cukup bagus. Jadi, jika aplikasi Anda perlu menangani nilai uang hingga satu triliun dolar (atau euro atau pound), maka ini akan berhasil:

DECIMAL(13, 2)

Atau, jika Anda harus mematuhi GAAP maka gunakan:

DECIMAL(13, 4)
pollux1er
sumber
3
Bisakah Anda menautkan ke bagian spesifik dari pedoman GAAP alih-alih isi dokumen 2500 halaman? Terima kasih.
ReactingToAngularVues
@ReactingToAngularVues sepertinya halaman berubah. Maaf
pollux1er
2

4 tempat desimal akan memberi Anda akurasi untuk menyimpan sub-unit mata uang terkecil di dunia. Anda dapat menurunkannya lebih jauh jika Anda membutuhkan akurasi pembayaran mikro (nanopayment ?!).

Saya juga lebih suka DECIMALjenis uang spesifik DBMS, Anda lebih aman menjaga logika semacam itu dalam aplikasi IMO. Pendekatan lain di sepanjang garis yang sama adalah dengan menggunakan integer [panjang], dengan memformat menjadi ¤unit.subunit untuk keterbacaan manusia (¤ = simbol mata uang) yang dilakukan pada tingkat aplikasi.

bobince
sumber
1

Jenis data uang pada SQL Server memiliki empat digit setelah desimal.

Dari SQL Server 2000 Books Online:

Data moneter menunjukkan jumlah uang positif atau negatif. Dalam Microsoft® SQL Server ™ 2000, data moneter disimpan menggunakan uang dan tipe data uang kecil. Data moneter dapat disimpan dengan akurasi empat tempat desimal. Gunakan tipe data uang untuk menyimpan nilai dalam kisaran dari -922.337.203.685.477.5808 hingga +922.333.2020.685.447.5807 (membutuhkan 8 byte untuk menyimpan nilai). Gunakan tipe data smallmoney untuk menyimpan nilai dalam kisaran dari -214.748,3648 hingga 214.748.3647 (membutuhkan 4 byte untuk menyimpan nilai). Jika lebih banyak tempat desimal diperlukan, gunakan tipe data desimal sebagai gantinya.

Austin Salonen
sumber
1

Kadang-kadang Anda harus pergi ke kurang dari satu sen dan ada mata uang internasional yang menggunakan demoniasi yang sangat besar. Misalnya, Anda mungkin menagih pelanggan Anda 0,088 sen per transaksi. Dalam database Oracle saya, kolom didefinisikan sebagai NUMBER (20,4)

WW.
sumber
1

Jika Anda akan melakukan segala macam operasi aritmatika di DB (mengalikan tarif penagihan dan sebagainya), Anda mungkin ingin lebih presisi daripada yang disarankan orang di sini, untuk alasan yang sama bahwa Anda tidak akan pernah ingin menggunakan apa pun yang kurang dari nilai floating point presisi ganda dalam kode aplikasi.

Hank Gay
sumber
Itulah yang saya pikirkan, tetapi dalam hal nilai tukar mata uang (yaitu, mengubah dolar Zimbawe menjadi USD). Saya akan melakukan beberapa percobaan pada database yang saya gunakan (psql, sqlite) untuk melihat bagaimana mereka menangani pembulatan pada desimal yang sangat kecil.
Ivan
Juga, bukankah float memiliki masalah akurasi dalam beberapa dbms / bahasa?
Ivan
2
float memiliki masalah akurasi dalam SEMUA bahasa.
Marcus Downing
Rekomendasi yang paling umum hari ini adalah menggunakan presisi yang sewenang-wenang (pikirkan BigDecimal), tetapi untuk waktu yang lama itu adalah presisi ganda (pikirkan doublesebagai ganti float). Juga, ketepatan yang sewenang-wenang memiliki hukuman kinerja yang signifikan dalam beberapa kasus. Pengujian jelas merupakan pendekatan yang tepat.
Hank Gay
0

Jika Anda menggunakan IBM Informix Dynamic Server, Anda akan memiliki tipe MONEY yang merupakan varian minor pada tipe DECIMAL atau NUMERIC. Itu selalu merupakan tipe titik tetap (sedangkan DECIMAL dapat menjadi tipe titik mengambang). Anda dapat menentukan skala dari 1 hingga 32, dan presisi dari 0 hingga 32 (default ke skala 16 dan presisi 2). Jadi, tergantung pada apa yang perlu Anda simpan, Anda mungkin menggunakan DECIMAL (16,2) - masih cukup besar untuk menahan Defisit Federal AS, hingga sen terdekat - atau Anda mungkin menggunakan rentang yang lebih kecil, atau lebih banyak tempat desimal.

Jonathan Leffler
sumber
0

Saya akan berpikir bahwa sebagian besar kebutuhan Anda atau klien Anda harus menentukan presisi dan skala yang digunakan. Sebagai contoh, untuk situs web e-commerce saya sedang mengerjakan yang berurusan dengan uang hanya dalam GBP, saya telah diminta untuk menyimpannya hingga Desimal (6, 2).

ayaz
sumber
0

Sebuah jawaban terlambat di sini, tapi saya sudah menggunakan

DECIMAL(13,2)

yang saya benar dalam berpikir harus memungkinkan hingga 99.999.999.999,99.

Mike Upjohn
sumber