Saya memiliki skrip tes dummy berikut:
function test() {
var x = 0.1 * 0.2;
document.write(x);
}
test();
Ini akan mencetak hasil 0.020000000000000004
sementara itu hanya harus mencetak 0.02
(jika Anda menggunakan kalkulator Anda). Sejauh yang saya mengerti ini adalah karena kesalahan dalam presisi multiplikasi floating point.
Adakah yang punya solusi yang baik sehingga dalam kasus seperti itu saya mendapatkan hasil yang benar 0.02
? Saya tahu ada fungsi seperti toFixed
atau pembulatan akan menjadi kemungkinan lain, tetapi saya ingin benar-benar mencetak seluruh angka tanpa pemotongan dan pembulatan. Hanya ingin tahu apakah salah satu dari Anda memiliki solusi yang bagus dan elegan.
Tentu saja, kalau tidak saya akan membulatkan sekitar 10 digit atau lebih.
0.1
ke nomor floating point biner terbatas.Jawaban:
Dari Floating-Point Guide :
Perhatikan bahwa poin pertama hanya berlaku jika Anda benar-benar membutuhkan perilaku desimal spesifik yang spesifik . Kebanyakan orang tidak membutuhkan itu, mereka hanya kesal karena program mereka tidak bekerja dengan benar dengan angka-angka seperti 1/10 tanpa menyadari bahwa mereka bahkan tidak akan berkedip pada kesalahan yang sama jika itu terjadi dengan 1/3.
Jika poin pertama benar-benar berlaku untuk Anda, gunakan BigDecimal untuk JavaScript , yang sama sekali tidak elegan, tetapi sebenarnya menyelesaikan masalah daripada memberikan solusi yang tidak sempurna.
sumber
console.log(9332654729891549)
benar - benar mencetak9332654729891548
(yaitu dimatikan satu!);P
... Antara2⁵²
=4,503,599,627,370,496
dan2⁵³
=9,007,199,254,740,992
angka yang dapat diwakili adalah bilangan bulat . Untuk rentang berikutnya, dari2⁵³
hingga2⁵⁴
, semuanya dikalikan dengan2
, sehingga angka yang dapat diwakili adalah genap , dll. Sebaliknya, untuk rentang sebelumnya dari2⁵¹
hingga2⁵²
, jaraknya adalah0.5
, dll. Ini disebabkan hanya dengan meningkatkan | mengurangi basis | radix 2 | eksponen biner di / dari nilai float 64-bit (yang pada gilirannya menjelaskan perilaku 'tak terduga' yang jarang didokumentasikantoPrecision()
untuk nilai antara0
dan1
).Saya suka solusi Pedro Ladaria dan menggunakan sesuatu yang serupa.
Tidak seperti solusi Pedros ini akan mengumpulkan 0,999 ... berulang dan akurat untuk plus / minus pada digit paling signifikan.
Catatan: Saat berurusan dengan float 32 atau 64 bit, Anda harus menggunakan toPrecision (7) dan toPrecision (15) untuk hasil terbaik. Lihat pertanyaan ini untuk info mengapa.
sumber
toPrecision
mengembalikan string bukan angka. Ini mungkin tidak selalu diinginkan.(9.99*5).toPrecision(2)
= 50 bukannya 49,95 karena toPrecision menghitung seluruh angka, bukan hanya desimal. Anda kemudian dapat menggunakantoPrecision(4)
, tetapi jika hasil Anda> 100 maka Anda kurang beruntung lagi, karena itu akan memungkinkan tiga angka pertama dan satu desimal, dengan cara itu menggeser titik, dan membuat ini lebih atau kurang dapat digunakan. Saya akhirnya menggunakantoFixed(2)
bukannyaUntuk yang cenderung secara matematis: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
Pendekatan yang disarankan adalah menggunakan faktor koreksi (kalikan dengan kekuatan 10 yang sesuai sehingga aritmatika terjadi di antara bilangan bulat). Misalnya, dalam kasus
0.1 * 0.2
, faktor koreksi adalah10
, dan Anda melakukan perhitungan:Solusi (sangat cepat) terlihat seperti:
Pada kasus ini:
Saya merekomendasikan menggunakan perpustakaan yang diuji seperti SinfulJS
sumber
Apakah Anda hanya melakukan perkalian? Jika demikian maka Anda dapat menggunakan untuk keuntungan Anda rahasia yang rapi tentang aritmatika desimal. Itu dia
NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals
. Artinya jika kita miliki0.123 * 0.12
maka kita tahu bahwa akan ada 5 tempat desimal karena0.123
memiliki 3 tempat desimal dan0.12
memiliki dua. Jadi jika JavaScript memberi kita angka seperti0.014760000002
kita dapat dengan aman membulatkan ke tempat desimal ke-5 tanpa takut kehilangan presisi.sumber
Anda mencari seorang
sprintf
implementasi untuk JavaScript, sehingga Anda dapat menulis float dengan kesalahan kecil di dalamnya (karena disimpan dalam format biner) dalam format yang Anda harapkan.Coba javascript-sprintf , Anda akan menyebutnya seperti ini:
untuk mencetak nomor Anda sebagai pelampung dengan dua tempat desimal.
Anda juga dapat menggunakan Number.toFixed () untuk tujuan tampilan, jika Anda lebih suka tidak menyertakan lebih banyak file hanya untuk pembulatan titik mengambang ke ketepatan tertentu.
sumber
Saya menemukan BigNumber.js memenuhi kebutuhan saya.
Ini memiliki dokumentasi yang baik dan penulis sangat rajin menanggapi umpan balik.
Penulis yang sama memiliki 2 perpustakaan serupa lainnya:
Big.js
dan Decimal.js
Berikut beberapa kode menggunakan BigNumber:
sumber
---atau---
---juga---
--- seperti dalam ---
sumber
Fungsi ini akan menentukan presisi yang diperlukan dari perkalian dua angka floating point dan mengembalikan hasil dengan presisi yang sesuai. Meskipun tidak elegan.
sumber
Anehnya, fungsi ini belum diposting meskipun yang lain memiliki variasi yang serupa. Ini dari dokumen web MDN untuk Math.round (). Ini ringkas dan memungkinkan beragam ketelitian.
console.log (precisionRound (1234.5678, 1)); // output yang diharapkan: 1234.6
console.log (precisionRound (1234.5678, -1)); // output yang diharapkan: 1230
UPDATE: Aug / 20/2019 Baru saja melihat kesalahan ini. Saya percaya ini karena kesalahan presisi floating point dengan Math.round ().
Kondisi ini berfungsi dengan benar:
Memperbaiki:
Ini hanya menambahkan angka ke kanan saat membulatkan desimal. MDN telah memperbarui halaman Math.round jadi mungkin seseorang dapat memberikan solusi yang lebih baik.
sumber
Anda hanya harus memutuskan berapa banyak angka desimal yang Anda inginkan - tidak dapat memiliki kue dan memakannya juga :-)
Kesalahan numerik menumpuk dengan setiap operasi lebih lanjut dan jika Anda tidak memotongnya lebih awal, itu hanya akan tumbuh. Perpustakaan numerik yang menyajikan hasil yang terlihat bersih cukup memotong 2 digit terakhir di setiap langkah, co-prosesor numerik juga memiliki panjang "normal" dan "penuh" untuk alasan yang sama. Cuf-offs murah untuk prosesor tetapi sangat mahal untuk Anda dalam skrip (mengalikan dan membagi dan menggunakan pov (...)). Lib matematika yang baik akan memberikan dasar (x, n) untuk melakukan cut-off untuk Anda.
Jadi, paling tidak Anda harus membuat global var / constant dengan pov (10, n) - artinya Anda memutuskan ketepatan yang Anda butuhkan :-) Lalu lakukan:
Anda juga bisa terus melakukan matematika dan hanya memotong pada akhirnya - dengan asumsi bahwa Anda hanya menampilkan dan tidak melakukan jika-s dengan hasil. Jika Anda dapat melakukannya, maka .toFixed (...) mungkin lebih efisien.
Jika Anda melakukan if-s / perbandingan dan tidak ingin memotong maka Anda juga memerlukan konstanta kecil, biasanya disebut eps, yang merupakan satu tempat desimal lebih tinggi dari kesalahan yang diharapkan maks. Katakan bahwa cut-off Anda adalah dua desimal terakhir - maka eps Anda memiliki 1 di tempat ke-3 dari yang terakhir (ke-3 paling signifikan) dan Anda dapat menggunakannya untuk membandingkan apakah hasilnya berada dalam kisaran eps yang diharapkan (0,02 -ep <0,1 * 0,2 <0,02 + eps).
sumber
Math.floor(-2.1)
itu-3
. Jadi mungkin gunakan misalnyaMath[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
floor
bukannyaround
?Anda dapat menggunakan
parseFloat()
dantoFixed()
jika Anda ingin mem-bypass masalah ini untuk operasi kecil:sumber
Fungsi round () di phpjs.org berfungsi dengan baik: http://phpjs.org/functions/round
sumber
0.6 * 3 itu luar biasa!)) Bagi saya ini berfungsi dengan baik:
Sangat sangat sederhana))
sumber
8.22e-8 * 1.3
?Perhatikan bahwa untuk penggunaan umum, perilaku ini kemungkinan dapat diterima.
Masalah muncul ketika membandingkan nilai-nilai floating point untuk menentukan tindakan yang tepat.
Dengan munculnya ES6, konstanta baru
Number.EPSILON
didefinisikan untuk menentukan margin kesalahan yang dapat diterima:Jadi alih-alih melakukan perbandingan seperti ini
Anda dapat mendefinisikan fungsi perbandingan khusus, seperti ini:
Sumber: http://2ality.com/2015/04/number-math-es6.html#numberepsilon
sumber
0.9 !== 0.8999999761581421
Hasil yang Anda dapatkan adalah benar dan cukup konsisten di seluruh implementasi floating point dalam berbagai bahasa, prosesor, dan sistem operasi - satu-satunya hal yang berubah adalah tingkat ketidaktepatan ketika float sebenarnya ganda (atau lebih tinggi).
0,1 dalam floating point biner seperti 1/3 dalam desimal (yaitu 0,3333333333333 ... selamanya), tidak ada cara akurat untuk menanganinya.
Jika Anda selalu berurusan dengan pelampung mengharapkan kesalahan pembulatan kecil, jadi Anda juga harus selalu membulatkan hasil yang ditampilkan ke sesuatu yang masuk akal. Sebagai imbalannya Anda mendapatkan aritmatika yang sangat sangat cepat dan kuat karena semua perhitungan berada dalam biner asli prosesor.
Sebagian besar waktu solusinya adalah tidak beralih ke aritmatika titik tetap, terutama karena itu jauh lebih lambat dan 99% dari waktu Anda hanya tidak membutuhkan keakuratan. Jika Anda berurusan dengan hal-hal yang memang membutuhkan tingkat akurasi (misalnya transaksi keuangan), Javascript mungkin bukan alat terbaik untuk digunakan (karena Anda ingin menerapkan jenis titik tetap, bahasa statis mungkin lebih baik ).
Anda sedang mencari solusi elegan maka saya khawatir ini dia: float cepat tetapi memiliki kesalahan pembulatan kecil - selalu bulat untuk sesuatu yang masuk akal ketika menampilkan hasil mereka.
sumber
Untuk menghindari ini, Anda harus bekerja dengan nilai integer alih-alih floating point. Jadi ketika Anda ingin memiliki 2 posisi kerja presisi dengan nilai * 100, untuk 3 posisi gunakan 1000. Saat menampilkan Anda menggunakan formatter untuk dimasukkan ke pemisah.
Banyak sistem tidak menggunakan desimal dengan cara ini. Itulah alasan mengapa banyak sistem bekerja dengan sen (sebagai bilangan bulat), bukan dolar / euro (sebagai titik mengambang).
sumber
Masalah
Floating point tidak dapat menyimpan semua nilai desimal dengan tepat. Jadi ketika menggunakan format floating point akan selalu ada kesalahan pembulatan pada nilai input. Kesalahan pada input tentu saja menghasilkan kesalahan pada output. Dalam hal fungsi diskrit atau operator, mungkin ada perbedaan besar pada output di sekitar titik di mana fungsi atau operator diskrit.
Input dan output untuk nilai floating point
Jadi, ketika menggunakan variabel floating point, Anda harus selalu menyadari hal ini. Dan output apa pun yang Anda inginkan dari perhitungan dengan floating point harus selalu diformat / dikondisikan sebelum ditampilkan dengan mempertimbangkan hal ini.
Ketika hanya fungsi dan operator kontinu yang digunakan, pembulatan ke presisi yang diinginkan sering dilakukan (jangan terpotong). Fitur pemformatan standar yang digunakan untuk mengonversi float ke string biasanya akan melakukan ini untuk Anda.
Karena pembulatan menambahkan kesalahan yang dapat menyebabkan kesalahan total lebih dari setengah dari presisi yang diinginkan, output harus dikoreksi berdasarkan pada presisi input yang diharapkan dan presisi output yang diinginkan. Kamu harus
2 hal ini biasanya tidak dilakukan dan dalam banyak kasus perbedaan yang disebabkan oleh tidak melakukan hal itu terlalu kecil untuk menjadi penting bagi sebagian besar pengguna, tetapi saya sudah memiliki proyek di mana output tidak diterima oleh pengguna tanpa koreksi tersebut.
Fungsi atau operator yang berbeda (seperti modula)
Ketika operator atau fungsi terpisah terlibat, koreksi tambahan mungkin diperlukan untuk memastikan output seperti yang diharapkan. Membulatkan dan menambahkan koreksi kecil sebelum pembulatan tidak dapat menyelesaikan masalah.
Pemeriksaan / koreksi khusus pada hasil perhitungan menengah, segera setelah menerapkan fungsi diskrit atau operator mungkin diperlukan. Untuk kasus tertentu (operator modula), lihat jawaban saya pada pertanyaan: Mengapa operator modulus mengembalikan bilangan pecahan dalam javascript?
Lebih baik hindari masalah
Sering kali lebih efisien untuk menghindari masalah ini dengan menggunakan tipe data (bilangan bulat atau format titik tetap) untuk perhitungan seperti ini yang dapat menyimpan input yang diharapkan tanpa kesalahan pembulatan. Contohnya adalah bahwa Anda tidak boleh menggunakan nilai floating point untuk perhitungan keuangan.
sumber
Lihatlah aritmetika titik-Tetap . Mungkin akan menyelesaikan masalah Anda, jika kisaran angka yang ingin Anda operasikan kecil (mis., Mata uang). Saya akan membulatkannya ke beberapa nilai desimal, yang merupakan solusi paling sederhana.
sumber
Coba pustaka aritmatika cabai saya, yang bisa Anda lihat di sini . Jika Anda menginginkan versi yang lebih baru, saya dapat memperolehnya untuk Anda.
sumber
Anda tidak bisa mewakili sebagian besar pecahan desimal persis dengan tipe titik mengambang biner (yang digunakan ECMAScript untuk merepresentasikan nilai titik mengambang). Jadi tidak ada solusi yang elegan kecuali jika Anda menggunakan tipe aritmatika presisi sewenang-wenang atau tipe floating point berbasis desimal. Misalnya, aplikasi Kalkulator yang dikirimkan bersama Windows sekarang menggunakan aritmatika presisi sewenang-wenang untuk menyelesaikan masalah ini .
sumber
sumber
Anda benar, alasan untuk itu adalah ketepatan terbatas dari angka floating point. Simpan angka rasional Anda sebagai pembagian dua angka integer dan dalam kebanyakan situasi Anda akan dapat menyimpan angka tanpa kehilangan presisi. Ketika datang untuk mencetak, Anda mungkin ingin menampilkan hasilnya sebagai pecahan. Dengan representasi yang saya usulkan, itu menjadi sepele.
Tentu saja itu tidak akan banyak membantu dengan angka irasional. Tetapi Anda mungkin ingin mengoptimalkan perhitungan Anda dengan cara mereka akan menyebabkan masalah paling sedikit (misalnya mendeteksi situasi seperti
sqrt(3)^2)
.sumber
<pedant>
sebenarnya, OP meletakkannya untuk operasi floating point yang tidak tepat, yang salah</pedant>
Saya memiliki masalah kesalahan pembulatan yang buruk dengan mod 3. Kadang-kadang ketika saya harus mendapatkan 0 saya akan mendapatkan .000 ... 01. Itu cukup mudah untuk ditangani, cukup uji untuk <= .01. Tapi terkadang saya mendapat 2.99999999999998. ADUH!
Nomor Besar memecahkan masalah, tetapi memperkenalkan masalah lain yang agak ironis. Ketika mencoba memuat 8,5 ke BigNumber saya diberitahu bahwa itu benar-benar 8,4999 ... dan memiliki lebih dari 15 digit signifikan. Ini berarti BigNumbers tidak dapat menerimanya (saya percaya saya sebutkan masalah ini agak ironis).
Solusi sederhana untuk masalah ironis:
sumber
Gunakan Nomor (1.234443) .toFixed (2); itu akan mencetak 1,23
sumber
decimal.js , big.js atau bignumber.js dapat digunakan untuk menghindari masalah manipulasi floating-point dalam Javascript:
tautan ke perbandingan terperinci
sumber
Elegan, Dapat Diprediksi, dan Dapat Digunakan Kembali
Mari kita hadapi masalah dengan cara yang elegan dan dapat digunakan kembali. Tujuh baris berikut akan memungkinkan Anda mengakses ketelitian titik apung yang Anda inginkan pada angka apa pun hanya dengan menambahkan
.decimal
ke akhir angka, formula, atauMath
fungsi bawaan.Bersulang!
sumber
Menggunakan
sumber
Math.round(x*Math.pow(10,4))/Math.pow(10,4);
. Pembulatan selalu menjadi pilihan, tapi aku hanya ingin tahu apakah ada beberapa solusi yang lebih baiktidak elegan tetapi melakukan pekerjaannya (menghilangkan nol tambahan)
sumber
Ini bekerja untuk saya:
sumber
Output menggunakan fungsi berikut:
Perhatikan outputnya
toFixedCurrency(x)
.sumber