Rumah  >  Artikel  >  hujung hadapan web  >  Bagaimana untuk menyelesaikan masalah kehilangan ketepatan berangka dalam petua JavaScript_javascript

Bagaimana untuk menyelesaikan masalah kehilangan ketepatan berangka dalam petua JavaScript_javascript

WBOY
WBOYasal
2016-05-16 15:28:002627semak imbas

Artikel ini dibahagikan kepada tiga bahagian

  • Beberapa masalah biasa dengan kehilangan ketepatan berangka JS
  • Sebab mengapa ketepatan berangka JS hilang
  • Penyelesaian (satu objek, satu fungsi)

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

  • 1 bit digunakan untuk mewakili bit tanda
  • 11 bit digunakan untuk mewakili eksponen
  • 52 bit mewakili mantissa

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.

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn