Saya selalu diberitahu untuk tidak mewakili uang dengan double
atau float
jenis, dan kali ini saya mengajukan pertanyaan kepada Anda: mengapa?
Saya yakin ada alasan yang sangat bagus, saya hanya tidak tahu apa itu.
floating-point
currency
Fran Fitzpatrick
sumber
sumber
Jawaban:
Karena pelampung dan ganda tidak dapat secara akurat mewakili basis 10 kelipatan yang kita gunakan untuk uang. Masalah ini bukan hanya untuk Java, tetapi untuk bahasa pemrograman apa pun yang menggunakan tipe basis-mengambang 2.
Dalam basis 10, Anda dapat menulis 10.25 sebagai 1025 * 10 -2 (bilangan bulat kali kekuatan 10). Angka - angka floating-point IEEE-754 berbeda, tetapi cara yang sangat sederhana untuk memikirkannya adalah dengan mengalikannya dengan kekuatan dua. Misalnya, Anda bisa melihat 164 * 2 -4 (bilangan bulat kali kekuatan dua), yang juga sama dengan 10,25. Itu bukan bagaimana angka-angka direpresentasikan dalam memori, tetapi implikasi matematikanya sama.
Bahkan dalam basis 10, notasi ini tidak dapat secara akurat mewakili fraksi paling sederhana. Misalnya, Anda tidak bisa mewakili 1/3: representasi desimal berulang (0,3333 ...), jadi tidak ada bilangan bulat terbatas yang bisa Anda kalikan dengan kekuatan 10 untuk mendapatkan 1/3. Anda dapat memilih urutan 3 dan eksponen kecil, seperti 333333333 * 10 -10 , tetapi itu tidak akurat: jika Anda mengalikannya dengan 3, Anda tidak akan mendapatkan 1.
Namun, untuk tujuan penghitungan uang, setidaknya untuk negara yang uangnya dinilai dalam urutan besarnya dolar AS, biasanya yang Anda butuhkan hanyalah menyimpan kelipatan 10 -2 , sehingga tidak masalah 1/3 itu tidak bisa diwakili.
Masalah dengan float dan double adalah bahwa sebagian besar angka seperti uang tidak memiliki representasi yang tepat sebagai bilangan bulat kali kekuatan 2. Bahkan, satu-satunya kelipatan 0,01 antara 0 dan 1 (yang signifikan ketika berhadapan dengan uang karena mereka bilangan bulat bilangan bulat) yang dapat direpresentasikan secara tepat sebagai angka floating-point biner IEEE-754 adalah 0, 0,25, 0,5, 0,75 dan 1. Yang lainnya dimatikan dengan jumlah kecil. Sebagai analogi dengan contoh 0,333333, jika Anda mengambil nilai floating-point untuk 0,1 dan Anda mengalikannya dengan 10, Anda tidak akan mendapatkan 1.
Merepresentasikan uang sebagai
double
ataufloat
mungkin akan terlihat bagus pada awalnya ketika perangkat lunak menyelesaikan kesalahan kecil, tetapi ketika Anda melakukan lebih banyak penambahan, pengurangan, perkalian dan pembagian pada angka yang tidak tepat, kesalahan akan bertambah dan Anda akan berakhir dengan nilai yang terlihat. tidak akurat. Hal ini membuat pelampung dan ganda tidak memadai untuk berurusan dengan uang, di mana dibutuhkan akurasi sempurna untuk kelipatan basis 10 kekuatan.Solusi yang berfungsi dalam hampir semua bahasa adalah menggunakan bilangan bulat, dan menghitung sen. Misalnya, 1025 akan menjadi $ 10,25. Beberapa bahasa juga memiliki tipe bawaan untuk menangani uang. Antara lain, Java memiliki
BigDecimal
kelas, dan C # memilikidecimal
tipe.sumber
1.0 / 10 * 10
mungkin tidak sama dengan 1.0.Dari Bloch, J., Java Efektif, edisi ke-2, Item 48:
Meskipun
BigDecimal
memiliki beberapa peringatan (silakan lihat jawaban yang diterima saat ini).sumber
long a = 104
dan menghitung dalam sen, bukan dalam dolar.BigDecimal
.Ini bukan masalah keakuratan, juga bukan masalah ketepatan. Ini adalah masalah memenuhi harapan manusia yang menggunakan basis 10 untuk perhitungan, bukan basis 2. Sebagai contoh, menggunakan ganda untuk perhitungan keuangan tidak menghasilkan jawaban yang "salah" dalam arti matematika, tetapi dapat menghasilkan jawaban yang bukan apa yang diharapkan dalam arti finansial.
Bahkan jika Anda membulatkan hasil pada menit terakhir sebelum output, Anda kadang-kadang masih bisa mendapatkan hasil menggunakan ganda yang tidak sesuai dengan harapan.
Menggunakan kalkulator, atau menghitung hasil dengan tangan, 1,40 * 165 = 231 tepat. Namun, secara internal menggunakan ganda, pada lingkungan sistem kompiler / sistem operasi saya, ini disimpan sebagai angka biner mendekati 230,99999 ... jadi jika Anda memotong angka, Anda mendapatkan 230 bukannya 231. Anda dapat beralasan bahwa pembulatan alih-alih pemotongan akan telah memberikan hasil yang diinginkan dari 231. Itu benar, tetapi pembulatan selalu melibatkan pemotongan. Apapun teknik pembulatan yang Anda gunakan, masih ada kondisi batas seperti ini yang akan membulatkan ketika Anda berharap untuk mengumpulkan. Mereka cukup langka sehingga mereka sering tidak akan ditemukan melalui pengujian atau pengamatan biasa. Anda mungkin harus menulis beberapa kode untuk mencari contoh yang menggambarkan hasil yang tidak berperilaku seperti yang diharapkan.
Asumsikan Anda ingin membulatkan sesuatu ke sen terdekat. Jadi, Anda mengambil hasil akhir Anda, kalikan dengan 100, tambahkan 0,5, potong, kemudian bagi hasil dengan 100 untuk kembali ke uang. Jika nomor internal yang Anda simpan adalah 3.46499999 .... bukannya 3.465, Anda akan mendapatkan 3.46 sebagai gantinya 3.47 ketika Anda membulatkan angka ke sen terdekat. Tetapi perhitungan dasar 10 Anda mungkin telah mengindikasikan bahwa jawabannya seharusnya 3.465 persis, yang jelas harus dibulatkan hingga 3,47, tidak turun ke 3,46. Hal-hal semacam ini terjadi sesekali dalam kehidupan nyata ketika Anda menggunakan ganda untuk perhitungan keuangan. Ini jarang terjadi, jadi sering kali tanpa disadari sebagai masalah, tetapi itu terjadi.
Jika Anda menggunakan basis 10 untuk perhitungan internal Anda alih-alih ganda, jawabannya selalu persis seperti yang diharapkan oleh manusia, dengan asumsi tidak ada bug lain dalam kode Anda.
sumber
Math.round(0.49999999999999994)
kembali 1?Saya bermasalah dengan beberapa respons ini. Saya pikir ganda dan pelampung memiliki tempat dalam perhitungan keuangan. Tentu saja, ketika menambah dan mengurangi jumlah moneter non-fraksional, tidak akan ada kehilangan presisi saat menggunakan kelas integer atau kelas BigDecimal. Tetapi ketika melakukan operasi yang lebih kompleks, Anda sering berakhir dengan hasil yang keluar beberapa atau banyak tempat desimal, tidak peduli bagaimana Anda menyimpan angka. Masalahnya adalah bagaimana Anda menyajikan hasilnya.
Jika hasil Anda berada di batas antara dibulatkan ke atas dan dibulatkan ke bawah, dan sen terakhir itu benar-benar penting, Anda mungkin harus memberi tahu pemirsa bahwa jawabannya hampir di tengah - dengan menampilkan lebih banyak tempat desimal.
Masalah dengan ganda, dan lebih lagi dengan mengapung, adalah ketika mereka digunakan untuk menggabungkan angka besar dan kecil. Di jawa
hasil dalam
sumber
Mengapung dan ganda merupakan perkiraan. Jika Anda membuat BigDecimal dan mengirimkan float ke konstruktor, Anda akan melihat apa yang sebenarnya sama dengan float:
ini mungkin bukan bagaimana Anda ingin mewakili $ 1,01.
Masalahnya adalah bahwa spesifikasi IEEE tidak memiliki cara untuk secara tepat mewakili semua fraksi, beberapa di antaranya berakhir sebagai fraksi berulang sehingga Anda berakhir dengan kesalahan perkiraan. Karena akuntan menyukai hal-hal yang keluar tepat ke sen, dan pelanggan akan terganggu jika mereka membayar tagihan mereka dan setelah pembayaran diproses mereka berutang 0,01 dan mereka dikenakan biaya atau tidak dapat menutup akun mereka, lebih baik menggunakan jenis persis seperti desimal (dalam C #) atau java.math.BigDecimal di Jawa.
Bukan karena kesalahannya tidak dapat dikontrol jika Anda melakukannya: lihat artikel ini oleh Peter Lawrey . Lebih mudah untuk tidak membulatkannya sejak awal. Sebagian besar aplikasi yang menangani uang tidak memerlukan banyak matematika, operasinya terdiri dari menambahkan hal-hal atau mengalokasikan jumlah ke ember yang berbeda. Memperkenalkan floating point dan pembulatan hanya mempersulit hal-hal.
sumber
float
,double
danBigDecimal
merupakan nilai yang tepat . Konversi kode ke objek tidak eksak serta operasi lainnya. Jenisnya sendiri tidak eksak.Saya akan berisiko diturunkan, tetapi saya pikir ketidakcocokan angka floating point untuk perhitungan mata uang terlalu tinggi. Selama Anda memastikan Anda melakukan cent-rounding dengan benar dan memiliki angka yang cukup signifikan untuk bekerja dalam rangka melawan ketidakcocokan representasi biner-desimal yang dijelaskan oleh zneak, tidak akan ada masalah.
Orang yang menghitung dengan mata uang di Excel selalu menggunakan pelampung presisi ganda (tidak ada jenis mata uang di Excel) dan saya belum melihat ada orang yang mengeluh tentang kesalahan pembulatan.
Tentu saja, Anda harus tetap masuk akal; mis. sebuah toko web sederhana mungkin tidak akan pernah mengalami masalah dengan pelampung presisi ganda, tetapi jika Anda melakukan mis. akuntansi atau hal lain yang membutuhkan penambahan jumlah angka yang besar (tidak dibatasi), Anda tidak akan mau menyentuh angka floating point dengan sepuluh kaki. tiang.
sumber
Memang benar bahwa tipe floating point hanya dapat mewakili data desimal aproksimasi, juga benar bahwa jika seseorang membulatkan angka ke presisi yang diperlukan sebelum mempresentasikannya, ia memperoleh hasil yang benar. Biasanya.
Biasanya karena tipe ganda memiliki ketelitian kurang dari 16 angka. Jika Anda membutuhkan presisi yang lebih baik, itu bukan tipe yang cocok. Juga perkiraan dapat menumpuk.
Harus dikatakan bahwa bahkan jika Anda menggunakan aritmatika titik tetap Anda masih harus membulatkan angka, jika bukan karena BigInteger dan BigDecimal memberikan kesalahan jika Anda mendapatkan angka desimal berkala. Jadi ada perkiraan juga di sini.
Misalnya COBOL, yang secara historis digunakan untuk perhitungan keuangan, memiliki ketepatan maksimum 18 angka. Jadi seringkali ada pembulatan tersirat.
Kesimpulannya, menurut saya, double tidak cocok untuk presisi 16 digit, yang bisa jadi tidak cukup, bukan karena perkiraan.
Pertimbangkan output berikut dari program selanjutnya. Ini menunjukkan bahwa setelah pembulatan ganda memberikan hasil yang sama dengan BigDecimal hingga presisi 16.
sumber
Hasil angka floating point tidak tepat, yang membuatnya tidak cocok untuk perhitungan keuangan yang membutuhkan hasil yang tepat dan bukan perkiraan. float dan double dirancang untuk perhitungan teknik dan ilmiah dan berkali-kali tidak menghasilkan hasil yang tepat juga hasil perhitungan floating point dapat bervariasi dari JVM ke JVM. Lihat contoh BigDecimal dan double primitive di bawah yang digunakan untuk mewakili nilai uang, cukup jelas bahwa perhitungan floating point mungkin tidak tepat dan orang harus menggunakan BigDecimal untuk perhitungan keuangan.
Keluaran:
sumber
double
FP biner ke cent tidak akan kesulitan menghitung ke 0,5 cent karena tidak akan FP desimal. Jika perhitungan floating-point menghasilkan nilai bunga misalnya 123.499941 ¢, baik melalui FP biner atau FP desimal, masalah pembulatan ganda adalah sama - tidak ada keuntungan juga. Premis Anda tampaknya mengasumsikan nilai yang tepat secara matematis dan FP desimal adalah sama - sesuatu yang bahkan FP desimal tidak menjamin. 0.5 / 7.0 * 7.0 adalah masalah untuk untuk biner dan deicmal FP. IAC, sebagian besar akan dapat diperdebatkan seperti yang saya harapkan versi C berikutnya untuk memberikan FP desimal.Seperti yang dikatakan sebelumnya, "Merepresentasikan uang sebagai gandaan atau pelampung mungkin akan terlihat bagus pada awalnya ketika perangkat lunak menyelesaikan kesalahan kecil, tetapi ketika Anda melakukan lebih banyak penambahan, pengurangan, penggandaan, dan pembagian pada angka yang tidak tepat, Anda akan kehilangan lebih banyak dan lebih presisi karena kesalahan bertambah. Ini membuat float dan double tidak cukup untuk berurusan dengan uang, di mana akurasi sempurna untuk kelipatan kekuatan basis 10 diperlukan. "
Akhirnya Jawa memiliki cara standar untuk bekerja dengan Mata Uang dan Uang!
JSR 354: API Uang dan Mata Uang
JSR 354 menyediakan API untuk mewakili, mengangkut, dan melakukan perhitungan komprehensif dengan Uang dan Mata Uang. Anda dapat mengunduhnya dari tautan ini:
JSR 354: Unduhan API Uang dan Mata Uang
Spesifikasi terdiri dari hal-hal berikut:
Contoh Contoh JSR 354: API Uang dan Mata Uang:
Contoh membuat MonetaryAmount dan mencetaknya ke konsol terlihat seperti ini ::
Saat menggunakan API implementasi referensi, kode yang diperlukan jauh lebih sederhana:
API juga mendukung perhitungan dengan MonetaryAmounts:
CurrencyUnit dan MonetaryAmount
MonetaryAmount memiliki berbagai metode yang memungkinkan mengakses mata uang yang ditetapkan, jumlah numerik, ketepatannya, dan lainnya:
Jumlah Moneter dapat dibulatkan menggunakan operator pembulatan:
Saat bekerja dengan koleksi MonetaryAmounts, beberapa metode utilitas yang bagus untuk memfilter, menyortir dan mengelompokkan tersedia.
Operasi MonetaryAmount khusus
Sumber:
Menangani uang dan mata uang di Jawa dengan JSR 354
Melihat ke dalam Java 9 Money and Currency API (JSR 354)
Lihat Juga: JSR 354 - Mata Uang dan Uang
sumber
Jika perhitungan Anda melibatkan berbagai langkah, aritmatika presisi sewenang-wenang tidak akan mencakup Anda 100%.
Satu-satunya cara yang dapat diandalkan untuk menggunakan representasi hasil yang sempurna (Gunakan tipe data Fraksi khusus yang akan mengelompokkan operasi divisi ke langkah terakhir) dan hanya mengonversi ke notasi desimal pada langkah terakhir.
Presisi sewenang-wenang tidak akan membantu karena selalu ada angka yang memiliki begitu banyak tempat desimal, atau beberapa hasil seperti
0.6666666
... Tidak ada representasi sewenang-wenang yang akan mencakup contoh terakhir. Jadi, Anda akan memiliki kesalahan kecil di setiap langkah.Kesalahan ini akan bertambah, mungkin pada akhirnya menjadi tidak mudah untuk diabaikan lagi. Ini disebut Propagasi Kesalahan .
sumber
Sebagian besar jawaban telah menyoroti alasan mengapa seseorang seharusnya tidak menggunakan ganda untuk perhitungan uang dan mata uang. Dan saya sangat setuju dengan mereka.
Itu tidak berarti bahwa ganda tidak pernah bisa digunakan untuk tujuan itu.
Saya telah mengerjakan sejumlah proyek dengan persyaratan gc yang sangat rendah, dan memiliki objek BigDecimal adalah kontributor besar untuk overhead itu.
Kurangnya pemahaman tentang representasi ganda dan kurangnya pengalaman dalam menangani akurasi dan presisi yang menghasilkan saran bijak ini.
Anda dapat membuatnya bekerja jika Anda mampu menangani persyaratan presisi dan akurasi proyek Anda, yang harus dilakukan berdasarkan pada rentang nilai ganda apa yang satu berurusan.
Anda dapat merujuk ke metode FuzzyCompare jambu biji untuk mendapatkan lebih banyak ide. Toleransi parameter adalah kuncinya. Kami menangani masalah ini untuk aplikasi perdagangan efek dan kami melakukan penelitian mendalam tentang toleransi apa yang digunakan untuk nilai numerik yang berbeda dalam rentang yang berbeda.
Juga, mungkin ada situasi ketika Anda tergoda untuk menggunakan pembungkus ganda sebagai kunci peta dengan peta hash sebagai implementasinya. Ini sangat berisiko karena Double.equals dan kode hash misalnya nilai "0,5" & "0,6 - 0,1" akan menyebabkan kekacauan besar.
sumber
Banyak jawaban yang diposting untuk pertanyaan ini membahas IEEE dan standar seputar aritmatika floating-point.
Berasal dari latar belakang ilmu non-komputer (fisika dan teknik), saya cenderung melihat masalah dari sudut pandang yang berbeda. Bagi saya, alasan mengapa saya tidak akan menggunakan double atau float dalam perhitungan matematis adalah bahwa saya akan kehilangan terlalu banyak informasi.
Apa alternatifnya? Ada banyak (dan banyak lagi yang saya tidak sadari!).
BigDecimal di Jawa adalah asli ke bahasa Jawa. Apfloat adalah pustaka presisi arbitrer lain untuk Java.
Tipe data desimal dalam C # adalah Microsoft .NET alternatif untuk 28 angka penting.
SciPy (Scientific Python) mungkin juga dapat menangani perhitungan keuangan (saya belum mencoba, tapi saya curiga demikian).
GNU Multiple Precision Library (GMP) dan GNU MFPR Library adalah dua sumber bebas dan sumber terbuka untuk C dan C ++.
Ada juga perpustakaan presisi numerik untuk JavaScript (!) Dan saya pikir PHP yang dapat menangani perhitungan keuangan.
Ada juga hak milik (khususnya, saya pikir, untuk Fortran) dan solusi open-source juga untuk banyak bahasa komputer.
Saya bukan ilmuwan komputer dengan pelatihan. Namun, saya cenderung condong ke arah BigDecimal di Jawa atau desimal di C #. Saya belum mencoba solusi lain yang saya daftarkan, tetapi mungkin juga sangat bagus.
Bagi saya, saya suka BigDecimal karena metode yang didukungnya. Desimal C # sangat bagus, tetapi saya belum memiliki kesempatan untuk menggunakannya sebanyak yang saya inginkan. Saya melakukan perhitungan ilmiah yang menarik bagi saya di waktu luang saya, dan BigDecimal tampaknya bekerja dengan sangat baik karena saya dapat mengatur ketepatan angka floating point saya. Kerugian BigDecimal? Kadang-kadang bisa lambat, terutama jika Anda menggunakan metode membagi.
Untuk kecepatan, Anda dapat melihat perpustakaan gratis dan eksklusif di C, C ++, dan Fortran.
sumber
Untuk menambahkan jawaban sebelumnya, ada juga opsi untuk mengimplementasikan Joda-Money di Jawa, selain BigDecimal, ketika berhadapan dengan masalah yang dibahas dalam pertanyaan. Nama modul Java adalah org.joda.money.
Ini membutuhkan Java SE 8 atau lebih baru dan tidak memiliki dependensi.
Contoh menggunakan Joda Money:
sumber
Beberapa contoh ... ini berfungsi (sebenarnya tidak berfungsi seperti yang diharapkan), pada hampir semua bahasa pemrograman ... Saya sudah mencoba dengan Delphi, VBScript, Visual Basic, JavaScript dan sekarang dengan Java / Android:
KELUARAN:
round problems?: current total: 0.9999999999999999 round problems?: current total: 2.7755575615628914E-17 round problems?: is total equal to ZERO? NO... thats why you should not use Double for some math!!!
sumber
Float adalah bentuk biner Desimal dengan desain yang berbeda; mereka adalah dua hal yang berbeda. Ada sedikit kesalahan antara dua jenis ketika dikonversi satu sama lain. Juga, float dirancang untuk mewakili sejumlah besar nilai tak terbatas untuk ilmiah. Itu berarti ia dirancang untuk kehilangan presisi ke jumlah sangat kecil dan sangat ekstrim dengan jumlah byte yang tetap. Desimal tidak dapat mewakili jumlah nilai yang tak terbatas, ia hanya terbatas pada angka desimal itu saja. Jadi Float dan Desimal untuk tujuan yang berbeda.
Ada beberapa cara untuk mengelola kesalahan untuk nilai mata uang:
Gunakan bilangan bulat panjang dan hitung dalam sen.
Gunakan presisi ganda, pertahankan angka signifikan Anda hanya sampai 15 sehingga desimal dapat disimulasikan dengan tepat. Bulat sebelum menyajikan nilai; Bulat sering ketika melakukan perhitungan.
Gunakan perpustakaan desimal seperti Java BigDecimal sehingga Anda tidak perlu menggunakan ganda untuk mensimulasikan desimal.
ps menarik untuk mengetahui bahwa sebagian besar merek kalkulator ilmiah genggam bekerja pada desimal alih-alih mengambang. Jadi tidak ada satu keluhan kesalahan konversi float.
sumber