Artikel ini dibahagikan kepada tiga bahagian
1. Beberapa masalah tipikal kehilangan ketepatan digital JS
1. Tambahkan dua nombor titik terapung mudah
0.1 + 0.2 != 0.3 // true
Ini sebenarnya bukan masalah Firebug, anda boleh cuba menggunakan alert (haha, gurau je).
Lihat hasil operasi Java
Lihat Python sekali lagi
2. Operasi integer besar
Tidak masuk akal bahawa nombor 16 digit dan 17 digit adalah sama.
Contoh lain
var x = 9007199254740992 x + 1 == x // ?
Lihat hasilnya
Tiga pandangan telah ditumbangkan lagi.
3. toFixed tidak akan bulat (Chrome)
Terdapat kes dalam talian di mana harga dalam Chrome tidak konsisten dengan penyemak imbas lain
2. Sebab nombor JS hilang ketepatan
Pelaksanaan binari komputer dan had bit mengehadkan beberapa nombor yang tidak boleh diwakili secara terhingga. Sama seperti beberapa nombor tidak rasional tidak boleh diwakili secara terhingga, seperti pi 3.1415926..., 1.3333... dll. JS mengikut spesifikasi IEEE 754, menggunakan storan berketepatan dua kali dan menduduki 64 bit. Seperti yang ditunjukkan dalam gambar
Maksud
Nombor titik terapung, seperti
0.1 >> 0.0001 1001 1001 1001…(1001无限循环) 0.2 >> 0.0011 0011 0011 0011…(0011无限循环)
Pada masa ini, kita hanya boleh meniru perpuluhan untuk pembundaran, tetapi binari hanya mempunyai dua nombor: 0 dan 1, jadi ia menjadi 0 dan 1 untuk pembundaran. Ini adalah punca ralat dan kehilangan ketepatan dalam beberapa operasi nombor titik terapung dalam komputer.
Kehilangan ketepatan integer besar pada asasnya sama dengan nombor titik terapung Bilangan maksimum digit mantissa ialah 52. Oleh itu, integer terbesar yang boleh diwakili dengan tepat dalam JS ialah Math.pow(2, 53) , yang dalam perpuluhan ialah 9007199254740992.
Yang lebih besar daripada 9007199254740992 mungkin kehilangan ketepatan
9007199254740992 >> 10000000000000...000 // 共计 53 个 0 9007199254740992 + 1 >> 10000000000000...001 // 中间 52 个 0 9007199254740992 + 2 >> 10000000000000...010 // 中间 51 个 0
Sebenarnya
9007199254740992 + 1 // 丢失 9007199254740992 + 2 // 未丢失 9007199254740992 + 3 // 丢失 9007199254740992 + 4 // 未丢失
Hasilnya seperti dalam gambar
Daripada perkara di atas, kita boleh tahu bahawa nombor yang kelihatan terhingga adalah tidak terhingga dalam perwakilan binari komputer Disebabkan oleh had bilangan digit penyimpanan, terdapat "pembundaran", dan kehilangan ketepatan berlaku.
3. Penyelesaian
Untuk integer, kebarangkalian masalah bahagian hadapan mungkin agak rendah Lagipun, beberapa keperluan perniagaan memerlukan penggunaan integer yang sangat besar selagi hasil operasi tidak melebihi Math.pow(2, 53). ketepatan tidak akan hilang.
Untuk perpuluhan, masih terdapat banyak kemungkinan masalah di bahagian hadapan, terutamanya apabila beberapa laman web e-dagang melibatkan data seperti jumlah. Penyelesaian: Masukkan perpuluhan ke dalam integer (darab), kemudian kurangkan kembali kepada gandaan asal (bahagi gandaan)
// 0.1 + 0.2 (0.1*10 + 0.2*10) / 10 == 0.3 // true
Berikut ialah objek yang saya tulis untuk melindungi kehilangan ketepatan dalam operasi tambah perpuluhan, tolak, darab dan bahagi. Sudah tentu, integer yang ditukar masih tidak boleh melebihi 9007199254740992.
/** * floatObj 包含加减乘除四个方法,能确保浮点数运算不丢失精度 * * 我们知道计算机编程语言里浮点数计算会存在精度丢失问题(或称舍入误差),其根本原因是二进制和实现位数限制有些数无法有限表示 * 以下是十进制小数对应的二进制表示 * 0.1 >> 0.0001 1001 1001 1001…(1001无限循环) * 0.2 >> 0.0011 0011 0011 0011…(0011无限循环) * 计算机里每种数据类型的存储是一个有限宽度,比如 JavaScript 使用 64 位存储数字类型,因此超出的会舍去。舍去的部分就是精度丢失的部分。 * * ** method ** * add / subtract / multiply /divide * * ** explame ** * 0.1 + 0.2 == 0.30000000000000004 (多了 0.00000000000004) * 0.2 + 0.4 == 0.6000000000000001 (多了 0.0000000000001) * 19.9 * 100 == 1989.9999999999998 (少了 0.0000000000002) * * floatObj.add(0.1, 0.2) >> 0.3 * floatObj.multiply(19.9, 100) >> 1990 * */ var floatObj = function() { /* * 判断obj是否为一个整数 */ function isInteger(obj) { return Math.floor(obj) === obj } /* * 将一个浮点数转成整数,返回整数和倍数。如 3.14 >> 314,倍数是 100 * @param floatNum {number} 小数 * @return {object} * {times:100, num: 314} */ function toInteger(floatNum) { var ret = {times: 0, num: 0} if (isInteger(floatNum)) { ret.num = floatNum return ret } var strfi = floatNum + '' var dotPos = strfi.indexOf('.') var len = strfi.substr(dotPos+1).length var times = Math.pow(10, len) var intNum = parseInt(floatNum * times + 0.5, 10) ret.times = times ret.num = intNum return ret } /* * 核心方法,实现加减乘除运算,确保不丢失精度 * 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除) * * @param a {number} 运算数1 * @param b {number} 运算数2 * @param digits {number} 精度,保留的小数点数,比如 2, 即保留为两位小数 * @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide) * */ function operation(a, b, digits, op) { var o1 = toInteger(a) var o2 = toInteger(b) var max = o1.times > o2.times ? o1.times : o2.times var result = null switch (op) { case 'add': result = o1.num + o2.num break case 'subtract': result = o1.num - o2.num break case 'multiply': result = o1.num * o2.num break case 'divide': result = o1.num / o2.num break } return result / max } // 加减乘除的四个接口 function add(a, b, digits) { return operation(a, b, digits, 'add') } function subtract(a, b, digits) { return operation(a, b, digits, 'subtract') } function multiply(a, b, digits) { return operation(a, b, digits, 'multiply') } function divide(a, b, digits) { return operation(a, b, digits, 'divide') } // exports return { add: add, subtract: subtract, multiply: multiply, divide: divide } }();
ToFixed ditetapkan seperti berikut
// toFixed 修复 function toFixed(num, s) { var times = Math.pow(10, s) var des = num * times + 0.5 des = parseInt(des, 10) / times return des + '' }
Perkara di atas adalah mengenai masalah kehilangan ketepatan berangka JavaScript Ia menganalisis masalah biasa, menganalisis sebab kehilangan ketepatan berangka, dan juga berkongsi penyelesaian saya harap ia akan membantu pembelajaran semua orang.