Anda mungkin harus menskalakan nilai desimal Anda dengan 100, dan mewakili semua nilai moneter dalam sen penuh. Ini untuk menghindari masalah dengan logika floating-point dan aritmatika . Tidak ada tipe data desimal di JavaScript - satu-satunya tipe data numerik adalah floating-point. Oleh karena itu, umumnya disarankan untuk menangani uang sebagai 2550
sen, bukan 25.50
dolar.
Pertimbangkan itu di JavaScript:
var result = 1.0 + 2.0; // (result === 3.0) returns true
Tapi:
var result = 0.1 + 0.2; // (result === 0.3) returns false
Ekspresi 0.1 + 0.2 === 0.3
kembali false
, tetapi untungnya aritmatika integer dalam floating-point tepat, sehingga kesalahan representasi desimal dapat dihindari dengan penskalaan 1 .
Perhatikan bahwa sementara himpunan bilangan real tidak terbatas, hanya bilangan terbatas dari mereka (tepatnya 18.437.736.874.454.810.627) yang dapat direpresentasikan secara tepat oleh format titik mengambang JavaScript. Oleh karena itu representasi bilangan lain akan menjadi perkiraan dari bilangan sebenarnya 2 .
1 Douglas Crockford: JavaScript: Bagian yang Baik : Lampiran A - Bagian yang Mengerikan (halaman 105) .
2 David Flanagan: JavaScript: Panduan Definitif, Edisi Keempat : 3.1.3 Literal Titik Mengambang (halaman 31) .
3000.57 + 0.11 === 3000.68
pengembalianfalse
.Menskalakan setiap nilai dengan 100 adalah solusinya. Melakukannya dengan tangan mungkin tidak berguna, karena Anda dapat menemukan perpustakaan yang melakukannya untuk Anda. Saya merekomendasikan moneysafe, yang menawarkan API fungsional yang cocok untuk aplikasi ES6:
https://github.com/ericelliott/moneysafe
Bekerja di Node.js dan browser.
sumber
in$, $
nama nilai ambigu untuk seseorang yang belum pernah menggunakan paket sebelumnya. Saya tahu itu adalah pilihan Eric untuk menamai hal-hal seperti itu, tetapi saya masih merasa itu adalah kesalahan yang cukup sehingga saya mungkin akan mengganti namanya dalam pernyataan persyaratan impor / dirusak.Money$afe has not yet been tested in production at scale.
. Hanya menunjukkan hal itu sehingga siapa pun dapat mempertimbangkan apakah itu sesuai untuk kasus penggunaan merekaTidak ada yang namanya kalkulasi finansial yang "tepat" karena hanya menggunakan dua digit pecahan desimal, tetapi itu adalah masalah yang lebih umum.
Dalam JavaScript, Anda dapat menskalakan setiap nilai dengan 100 dan menggunakan
Math.round()
setiap pecahan dapat terjadi.Anda dapat menggunakan objek untuk menyimpan angka dan menyertakan pembulatan dalam
valueOf()
metode prototipe . Seperti ini:Dengan cara itu, setiap kali Anda menggunakan objek Uang, objek tersebut akan dibulatkan menjadi dua desimal. Nilai yang tidak dibulatkan masih dapat diakses melalui
m.amount
.Anda dapat membangun algoritme pembulatan Anda sendiri
Money.prototype.valueOf()
, jika Anda mau.sumber
Money(0.1)
lexer JavaScript membaca string "0.1" dari sumber dan kemudian mengubahnya menjadi floating point biner dan kemudian Anda sudah melakukan pembulatan yang tidak diinginkan. Masalahnya adalah tentang representasi (biner vs desimal) bukan tentang presisi .gunakan decimaljs ... Ini adalah perpustakaan yang sangat bagus yang memecahkan bagian sulit dari masalah ...
gunakan saja di semua operasi Anda.
https://github.com/MikeMcl/decimal.js/
sumber
Masalah Anda berasal dari ketidakakuratan dalam perhitungan floating point. Jika Anda hanya menggunakan pembulatan untuk menyelesaikannya, Anda akan mendapatkan kesalahan yang lebih besar saat mengalikan dan membagi.
Solusinya dibawah ini, penjelasannya sbb:
Anda harus memikirkan matematika di balik ini untuk memahaminya. Bilangan real seperti 1/3 tidak dapat direpresentasikan dalam matematika dengan nilai desimal karena tidak ada habisnya (misalnya - .333333333333333 ...). Beberapa angka dalam desimal tidak dapat direpresentasikan dalam biner dengan benar. Misalnya, 0,1 tidak dapat direpresentasikan dalam biner dengan benar dengan jumlah digit yang terbatas.
Untuk penjelasan lebih rinci lihat di sini: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
Lihat implementasi solusi: http://floating-point-gui.de/languages/javascript/
sumber
Karena sifat biner pengkodeannya, beberapa bilangan desimal tidak dapat direpresentasikan dengan akurasi yang sempurna. Sebagai contoh
Jika Anda perlu menggunakan javascript murni maka Anda harus memikirkan solusi untuk setiap perhitungan. Untuk kode di atas kita dapat mengubah desimal menjadi bilangan bulat utuh.
Menghindari Masalah dengan Matematika Desimal di JavaScript
Ada perpustakaan khusus untuk perhitungan keuangan dengan dokumentasi yang bagus. Finance.js
sumber
Sayangnya semua jawaban sejauh ini mengabaikan fakta bahwa tidak semua mata uang memiliki 100 sub-unit (misalnya, sen adalah sub-unit dari dolar AS (USD)). Mata uang seperti Dinar Irak (IQD) memiliki 1000 sub-unit: Dinar Irak memiliki 1000 fils. Yen Jepang (JPY) tidak memiliki sub-unit. Jadi "kalikan dengan 100 untuk melakukan aritmatika integer" tidak selalu merupakan jawaban yang benar.
Selain itu, untuk kalkulasi moneter, Anda juga perlu melacak mata uang. Anda tidak dapat menambahkan Dolar AS (USD) ke Rupee India (INR) (tanpa mengonversikan satu ke yang lain terlebih dahulu).
Ada juga batasan jumlah maksimum yang dapat diwakili oleh tipe data integer JavaScript.
Dalam kalkulasi moneter, Anda juga harus ingat bahwa uang memiliki presisi yang terbatas (biasanya 0-3 desimal poin) & pembulatan perlu dilakukan dengan cara tertentu (misalnya, pembulatan "normal" vs. pembulatan bankir). Jenis pembulatan yang akan dilakukan mungkin juga berbeda menurut yurisdiksi / mata uang.
Bagaimana menangani uang di javascript memiliki diskusi yang sangat bagus tentang poin-poin yang relevan.
Dalam pencarian saya, saya menemukan perpustakaan dinero.js yang membahas banyak masalah dengan kalkulasi moneter . Belum menggunakannya dalam sistem produksi jadi tidak bisa memberikan opini yang terinformasi.
sumber