Bagaimana cara menangani presisi angka floating point dalam JavaScript?

620

Saya memiliki skrip tes dummy berikut:

function test() {
  var x = 0.1 * 0.2;
  document.write(x);
}
test();

Ini akan mencetak hasil 0.020000000000000004sementara 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 toFixedatau 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.

Juri
sumber
118
Sebenarnya, kesalahannya adalah karena tidak ada cara untuk memetakan 0.1ke nomor floating point biner terbatas.
Aaron Digulla
10
Sebagian besar pecahan tidak dapat dikonversi ke desimal dengan presisi yang tepat. Penjelasan yang baik ada di sini: docs.python.org/release/2.5.1/tut/node16.html
Nate Zaugg
7
kemungkinan rangkap dari Apakah JavaScript JavaScript rusak?
epascarello
53
@SalmanA: Bahwa runtime JavaScript Anda menyembunyikan masalah ini dari Anda bukan berarti saya salah.
Aaron Digulla
5
Tidak setuju dengan Harun, ada cara untuk kode 0,1 dengan sempurna dan sepenuhnya dalam biner. Tetapi IEEE 754 tidak selalu mendefinisikan ini. Bayangkan representasi di mana Anda akan mengkode bagian integer dalam biner di satu sisi, bagian desimal di sisi lain, hingga n desimal, dalam biner juga, seperti integer normal> 0, dan akhirnya, posisi titik desimal . Nah, Anda akan mewakili 0,1 dengan sempurna, tanpa kesalahan. Btw, karena JS menggunakan jumlah desimal yang terbatas secara internal, mereka mungkin juga mengkodekan nyali untuk tidak membuat kesalahan pada desimal terakhir.
Fabien Haddadi

Jawaban:

469

Dari Floating-Point Guide :

Apa yang bisa saya lakukan untuk menghindari masalah ini?

Itu tergantung pada jenis perhitungan yang Anda lakukan.

  • Jika Anda benar-benar membutuhkan hasil Anda untuk menambahkan dengan tepat, terutama ketika Anda bekerja dengan uang: gunakan tipe data desimal khusus.
  • Jika Anda tidak ingin melihat semua tempat desimal tambahan itu: cukup format hasil Anda dibulatkan ke sejumlah tempat desimal saat menampilkannya.
  • Jika Anda tidak memiliki tipe data desimal, alternatifnya adalah bekerja dengan bilangan bulat, mis. Lakukan penghitungan uang seluruhnya dalam sen. Tetapi ini lebih banyak pekerjaan dan memiliki beberapa kelemahan.

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.

Michael Borgwardt
sumber
11
Saya perhatikan tautan mati Anda untuk BigDecimal dan saat mencari cermin, saya menemukan alternatif bernama BigNumber: jsfromhell.com/classes/bignumber
Jacksonkr
4
@ bass-t: Ya, tetapi float dapat mewakili integer hingga panjang signifikansi, dan sesuai standar ECMA, ini adalah float 64bit. Jadi itu bisa mewakili bilangan bulat hingga 2 ^ 52
Michael Borgwardt
5
@ Kararl: Fraksi desimal 1/10 tidak dapat direpresentasikan sebagai fraksi biner terbatas dalam basis 2, dan itulah nomor Javascript. Jadi ini sebenarnya masalah yang sama persis.
Michael Borgwardt
12
Saya belajar hari ini bahwa bahkan bilangan bulat memiliki masalah presisi dalam javascript. Pertimbangkan yang console.log(9332654729891549)benar - benar mencetak 9332654729891548(yaitu dimatikan satu!)
mlathe
12
@mlathe: Doh .. ;P... Antara 2⁵²= 4,503,599,627,370,496dan 2⁵³= 9,007,199,254,740,992angka yang dapat diwakili adalah bilangan bulat . Untuk rentang berikutnya, dari 2⁵³hingga 2⁵⁴, semuanya dikalikan dengan2 , sehingga angka yang dapat diwakili adalah genap , dll. Sebaliknya, untuk rentang sebelumnya dari 2⁵¹hingga 2⁵², jaraknya adalah 0.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 didokumentasikan toPrecision()untuk nilai antara 0dan 1).
GitaarLAB
126

Saya suka solusi Pedro Ladaria dan menggunakan sesuatu yang serupa.

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

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.

linux_mike
sumber
21
Ada alasan mengapa Anda memilih 12?
qwertymk
18
toPrecisionmengembalikan string bukan angka. Ini mungkin tidak selalu diinginkan.
SStanley
7
parseFloat (1.005) .toPrecision (3) => 1.00
Peter
5
@ user2428118, saya tahu, saya bermaksud menunjukkan kesalahan pembulatan, hasilnya adalah 1,00 bukannya 1,01
Peter
9
Apa yang dikatakan @ user2428118 mungkin tidak cukup jelas: (9.99*5).toPrecision(2)= 50 bukannya 49,95 karena toPrecision menghitung seluruh angka, bukan hanya desimal. Anda kemudian dapat menggunakan toPrecision(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 menggunakan toFixed(2)bukannya
aexl
79

Untuk 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 adalah 10, dan Anda melakukan perhitungan:

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

Solusi (sangat cepat) terlihat seperti:

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

Pada kasus ini:

> Math.m(0.1, 0.2)
0.02

Saya merekomendasikan menggunakan perpustakaan yang diuji seperti SinfulJS

SheetJS
sumber
1
Saya suka solusi elegan ini tetapi tampaknya tidak sempurna: jsfiddle.net/Dm6F5/1 Math.a (76.65, 38.45) mengembalikan 115.10000000000002
nicolallias
3
Math.m (10,2332226616) memberi saya "-19627406800" yang merupakan nilai negatif ... Saya harap harus ada batas atas - mungkin itulah yang menyebabkan masalah ini. Tolong sarankan
Shiva Komuravelly
1
Ini semua tampak hebat, tetapi tampaknya memiliki satu atau dua kesalahan di suatu tempat.
MrYellow
5
Solusi yang sangat cepat katanya ... perbaikan rusak tidak ada yang pernah mengatakan.
Cozzbie
2
Jangan gunakan kode di atas. Sama sekali bukan 'solusi cepat' jika tidak berhasil. Ini adalah pertanyaan terkait matematika, jadi ketelitian diperlukan.
Drenai
49

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 miliki 0.123 * 0.12maka kita tahu bahwa akan ada 5 tempat desimal karena 0.123memiliki 3 tempat desimal dan 0.12memiliki dua. Jadi jika JavaScript memberi kita angka seperti 0.014760000002kita dapat dengan aman membulatkan ke tempat desimal ke-5 tanpa takut kehilangan presisi.

Nate Zaugg
sumber
6
... dan cara mendapatkan jumlah desimal yang tepat .
line-o
7
0,5 * 0,2 = 0,10; Anda masih dapat memotong di 2 tempat desimal (atau kurang). Tetapi tidak akan pernah ada angka dengan signifikansi matematika di luar hukum ini.
Nate Zaugg
3
Apakah Anda memiliki kutipan untuk ini? Perhatikan juga bahwa hal yang sama tidak berlaku untuk pembagian.
Griffin
3
@NateZaugg Anda tidak dapat memotong desimal yang melimpah, Anda harus membulatkan jumlahnya, karena 2090.5 * 8.61 adalah 17999.205 tetapi dalam float itu 17999.204999999998
Lostfields
3
@Lostfields - Anda benar! Saya telah memperbarui jawaban saya.
Nate Zaugg
29

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:

var yourString = sprintf("%.2f", yourNumber);

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.

Douglas
sumber
4
Saya pikir ini adalah solusi terbersih. Kecuali jika Anda benar-benar membutuhkan hasilnya 0,02, kesalahan kecil dapat diabaikan. Kedengarannya yang penting adalah bahwa nomor Anda ditampilkan dengan baik, bukan karena Anda memiliki presisi yang sewenang-wenang.
Long Ouyang
2
Untuk tampilan, ini memang pilihan terbaik, untuk perhitungan yang rumit, periksa jawaban Borgwardt.
Tidak tersedia
4
Tapi sekali lagi ini akan mengembalikan string yang sama persis dengan yourNumber.toFixed (2).
Robert
27

Saya menemukan BigNumber.js memenuhi kebutuhan saya.

Pustaka JavaScript untuk aritmatika desimal dan non-desimal presisi arbitrer.

Ini memiliki dokumentasi yang baik dan penulis sangat rajin menanggapi umpan balik.

Penulis yang sama memiliki 2 perpustakaan serupa lainnya:

Big.js

Pustaka JavaScript kecil dan cepat untuk aritmatika desimal presisi-sembarang. Adik perempuan ke bignumber.js.

dan Decimal.js

Jenis desimal presisi-arbitrer untuk JavaScript.

Berikut beberapa kode menggunakan BigNumber:

$(function(){

  
  var product = BigNumber(.1).times(.2);  
  $('#product').text(product);

  var sum = BigNumber(.1).plus(.2);  
  $('#sum').text(sum);


});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- 1.4.1 is not the current version, but works for this example. -->
<script src="http://cdn.bootcss.com/bignumber.js/1.4.1/bignumber.min.js"></script>

.1 &times; .2 = <span id="product"></span><br>
.1 &plus; .2 = <span id="sum"></span><br>

Ronnie Overby
sumber
3
Menggunakan perpustakaan jelas merupakan pilihan terbaik menurut saya.
Anthony
1
Dari tautan ini github.com/MikeMcl/big.js/issues/45 bignumber.js -> financial decimal.js -> ilmiah big.js -> ???
vee
20
var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

---atau---

var fpFix = function (n) {
    return Math.round(n * 100)/100;
};

fpFix(0.1*0.2); // -> 0.02

---juga---

var fpArithmetic = function (op, x, y) {
    var n = {
            '*': x * y,
            '-': x - y,
            '+': x + y,
            '/': x / y
        }[op];        

    return Math.round(n * 100)/100;
};

--- seperti dalam ---

fpArithmetic('*', 0.1, 0.2);
// 0.02

fpArithmetic('+', 0.1, 0.2);
// 0.3

fpArithmetic('-', 0.1, 0.2);
// -0.1

fpArithmetic('/', 0.2, 0.1);
// 2
shawndumas
sumber
4
Saya pikir itu akan memberikan masalah yang sama sebagai hasilnya. Anda mengembalikan floating point sehingga kemungkinan besar nilai pengembalian juga akan "salah".
Gertjan
1
Sangat cerdas dan bermanfaat, +1.
Jonatas Walker
18

Fungsi ini akan menentukan presisi yang diperlukan dari perkalian dua angka floating point dan mengembalikan hasil dengan presisi yang sesuai. Meskipun tidak elegan.

function multFloats(a,b){
  var atens = Math.pow(10,String(a).length - String(a).indexOf('.') - 1), 
      btens = Math.pow(10,String(b).length - String(b).indexOf('.') - 1); 
  return (a * atens) * (b * btens) / (atens * btens); 
}
Gabriel
sumber
Ew. Ya, mari kita konversi angka menjadi string untuk matematika floating point dan mari kita usulkan itu sebagai jawaban.
Andrew
17

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.

function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

console.log (precisionRound (1234.5678, 1)); // output yang diharapkan: 1234.6

console.log (precisionRound (1234.5678, -1)); // output yang diharapkan: 1230

var inp = document.querySelectorAll('input');
var btn = document.querySelector('button');

btn.onclick = function(){
  inp[2].value = precisionRound( parseFloat(inp[0].value) * parseFloat(inp[1].value) , 5 );
};

//MDN function
function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}
button{
display: block;
}
<input type='text' value='0.1'>
<input type='text' value='0.2'>
<button>Get Product</button>
<input type='text'>

UPDATE: Aug / 20/2019 Baru saja melihat kesalahan ini. Saya percaya ini karena kesalahan presisi floating point dengan Math.round ().

precisionRound(1.005, 2) // produces 1, incorrect, should be 1.01

Kondisi ini berfungsi dengan benar:

precisionRound(0.005, 2) // produces 0.01
precisionRound(1.0005, 3) // produces 1.001
precisionRound(1234.5, 0) // produces 1235
precisionRound(1234.5, -1) // produces 1230

Memperbaiki:

function precisionRoundMod(number, precision) {
  var factor = Math.pow(10, precision);
  var n = precision < 0 ? number : 0.01 / factor + number;
  return Math.round( n * factor) / factor;
}

Ini hanya menambahkan angka ke kanan saat membulatkan desimal. MDN telah memperbarui halaman Math.round jadi mungkin seseorang dapat memberikan solusi yang lebih baik.

HelloWorldPeace
sumber
jawaban yang salah. 10.2 akan selalu mengembalikan 10.19. jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas
@ Žilvinas Tautan JSBin yang Anda poskan tidak menggunakan fungsi MDN yang tercantum di atas. Saya pikir komentar Anda diarahkan pada orang yang salah.
HelloWorldPeace
13

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:

Math.floor(x*PREC_LIM)/PREC_LIM  // floor - you are cutting off, not rounding

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).

ZXX
sumber
Anda juga dapat menambahkan 0,5 untuk melakukan pembulatan orang miskin: Math.floor (x * PREC_LIM + 0,5) / PREC_LIM
cmroanirgo
Perhatikan, misalnya Math.floor(-2.1)itu -3. Jadi mungkin gunakan misalnyaMath[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
MikeM
Kenapa floorbukannya round?
Quinn Comendant
12

Anda dapat menggunakan parseFloat()dan toFixed()jika Anda ingin mem-bypass masalah ini untuk operasi kecil:

a = 0.1;
b = 0.2;

a + b = 0.30000000000000004;

c = parseFloat((a+b).toFixed(2));

c = 0.3;

a = 0.3;
b = 0.2;

a - b = 0.09999999999999998;

c = parseFloat((a-b).toFixed(2));

c = 0.1;
Softwareddy
sumber
11

Fungsi round () di phpjs.org berfungsi dengan baik: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07
Tom
sumber
2
@ jrg Berdasarkan konvensi, angka yang diakhiri dengan "5" dibulatkan ke angka genap terdekat (karena selalu membulatkan ke atas atau ke bawah akan menimbulkan bias pada hasil Anda). Oleh karena itu, 4,725 dibulatkan ke dua tempat desimal memang harus 4,72.
Mark A. Durham
9

0.6 * 3 itu luar biasa!)) Bagi saya ini berfungsi dengan baik:

function dec( num )
{
    var p = 100;
    return Math.round( num * p ) / p;
}

Sangat sangat sederhana))

Олег Всильдеревьев
sumber
Akankah ini bekerja dengan sesuatu seperti 8.22e-8 * 1.3?
Paul Carlton
0,6 x 3 = 1,8, kode yang Anda berikan hasil ke 2 ... jadi tidak baik.
Zyo
@Zyo Ini mengembalikan 1,8 dalam contoh ini. Bagaimana Anda menjalankannya?
Drenai
Menarik. Anda dapat menukar operator perkalian dan divisi dalam hal ini dan itu juga berfungsi.
Andrew
9

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.EPSILONdidefinisikan untuk menentukan margin kesalahan yang dapat diterima:
Jadi alih-alih melakukan perbandingan seperti ini

0.1 + 0.2 === 0.3 // which returns false

Anda dapat mendefinisikan fungsi perbandingan khusus, seperti ini:

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

Sumber: http://2ality.com/2015/04/number-math-es6.html#numberepsilon

pengguna10089632
sumber
Dalam kasus saya Number.EPSILON terlalu kecil, yang menghasilkan misalnya0.9 !== 0.8999999761581421
Tom
8

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.

Keith
sumber
8

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).

Gertjan
sumber
7

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

  • Masukan bulat ke presisi yang diharapkan atau pastikan tidak ada nilai yang dapat dimasukkan dengan presisi lebih tinggi.
  • Tambahkan nilai kecil ke output sebelum membulatkan / memformatnya yang lebih kecil dari atau sama dengan 1/4 dari presisi yang diinginkan dan lebih besar dari kesalahan maksimum yang diharapkan yang disebabkan oleh kesalahan pembulatan pada input dan selama perhitungan. Jika itu tidak memungkinkan, kombinasi ketepatan tipe data yang digunakan tidak cukup untuk memberikan ketepatan keluaran yang diinginkan untuk perhitungan Anda.

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.

Stefan Mondelaers
sumber
4

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.

Marius
sumber
5
Masalahnya bukan floating point vs fixed point, masalahnya adalah biner vs desimal.
Michael Borgwardt
4

Coba pustaka aritmatika cabai saya, yang bisa Anda lihat di sini . Jika Anda menginginkan versi yang lebih baru, saya dapat memperolehnya untuk Anda.

Robert L.
sumber
4

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 .

MSN
sumber
4

masukkan deskripsi gambar di sini

    You can use library https://github.com/MikeMcl/decimal.js/. 
    it will   help  lot to give proper solution. 
    javascript console output 95 *722228.630 /100 = 686117.1984999999
    decimal library implementation 
    var firstNumber = new Decimal(95);
    var secondNumber = new Decimal(722228.630);
    var thirdNumber = new Decimal(100);
    var partialOutput = firstNumber.times(secondNumber);
    console.log(partialOutput);
    var output = new Decimal(partialOutput).div(thirdNumber);
    alert(output.valueOf());
    console.log(output.valueOf())== 686117.1985
Ashish Singhal
sumber
3

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).

skalee
sumber
Anda benar, alasan untuk itu adalah presisi terbatas dari angka floating point - <pedant>sebenarnya, OP meletakkannya untuk operasi floating point yang tidak tepat, yang salah</pedant>
detly
3

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:

x = Math.round(x*100);
// I only need 2 decimal places, if i needed 3 I would use 1,000, etc.
x = x / 100;
xB = new BigNumber(x);
mcgeo52
sumber
2

Gunakan Nomor (1.234443) .toFixed (2); itu akan mencetak 1,23

function test(){
    var x = 0.1 * 0.2;
    document.write(Number(x).toFixed(2));
}
test();
Harish.bazee
sumber
2

decimal.js , big.js atau bignumber.js dapat digunakan untuk menghindari masalah manipulasi floating-point dalam Javascript:

0.1 * 0.2                                // 0.020000000000000004
x = new Decimal(0.1)
y = x.times(0.2)                          // '0.2'
x.times(0.2).equals(0.2)                  // true

big.js: minimalis; mudah digunakan; presisi yang ditentukan di tempat desimal; presisi diterapkan hanya untuk divisi.

bignumber.js: base 2-64; opsi konfigurasi; NaN; Tak terbatas; presisi yang ditentukan di tempat desimal; ketelitian hanya diterapkan pada divisi; awalan dasar.

decimal.js: base 2-64; opsi konfigurasi; NaN; Tak terbatas; kekuatan non-integer, exp, ln, log; presisi yang ditentukan dalam digit signifikan; presisi selalu diterapkan; angka acak.

tautan ke perbandingan terperinci

Erikas Pliauksta
sumber
2

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 .decimalke akhir angka, formula, atau Mathfungsi bawaan.

// First extend the native Number object to handle precision. This populates
// the functionality to all math operations.

Object.defineProperty(Number.prototype, "decimal", {
  get: function decimal() {
    Number.precision = "precision" in Number ? Number.precision : 3;
    var f = Math.pow(10, Number.precision);
    return Math.round( this * f ) / f;
  }
});


// Now lets see how it works by adjusting our global precision level and 
// checking our results.

console.log("'1/3 + 1/3 + 1/3 = 1' Right?");
console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true

console.log(0.3333.decimal); // 0.333 - A raw 4 digit decimal, trimmed to 3...

Number.precision = 3;
console.log("Precision: 3");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0.001

Number.precision = 2;
console.log("Precision: 2");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 1;
console.log("Precision: 1");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 0;
console.log("Precision: 0");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Bersulang!

Bernesto
sumber
2
Jika Anda memilih untuk downvote, setidaknya berikan alasannya.
Bernesto
1

Menggunakan

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);
Himadri
sumber
4
Hmm ... tapi perhatikan, ini selalu membulatkan ke 2 desimal. Itu tentu saja akan menjadi pilihan, tetapi bagaimana dengan perhitungan 0,55 * 0,55 (karena saya tidak tahu angka pastinya di muka. Itu akan memberi 0,3 bukannya 0,3025. Tentu saja saya bisa menggunakan 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 baik
Juri
10.2 selalu mengembalikan 10.19 jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas
1

tidak elegan tetapi melakukan pekerjaannya (menghilangkan nol tambahan)

var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02
Peter
sumber
6
toFixed tidak selalu berfungsi: stackoverflow.com/questions/661562/…
Sam Hasler
1

Ini bekerja untuk saya:

function round_up( value, precision ) { 
    var pow = Math.pow ( 10, precision ); 
    return ( Math.ceil ( pow * value ) + Math.ceil ( pow * value - Math.ceil ( pow * value ) ) ) / pow; 
}

round_up(341.536, 2); // 341.54
Antonio Max
sumber
1
sayangnya, round_up (4.15,2) => 4.16.
jrg
1

Output menggunakan fungsi berikut:

var toFixedCurrency = function(num){
    var num = (num).toString();
    var one = new RegExp(/\.\d{1}$/).test(num);
    var two = new RegExp(/\.\d{2,}/).test(num);
    var result = null;

    if(one){ result = num.replace(/\.(\d{1})$/, '.$10');
    } else if(two){ result = num.replace(/\.(\d{2})\d*/, '.$1');
    } else { result = num*100; }

    return result;
}

function test(){
    var x = 0.1 * 0.2;
    document.write(toFixedCurrency(x));
}

test();

Perhatikan outputnya toFixedCurrency(x).

Júlio Paulillo
sumber