Home  >  Article  >  Web Front-end  >  How to solve the problem of losing numerical precision in JavaScript_javascript tips

How to solve the problem of losing numerical precision in JavaScript_javascript tips

WBOY
WBOYOriginal
2016-05-16 15:28:002627browse

This article is divided into three parts

  • Some typical problems with JS numerical precision loss
  • The reason why JS numerical precision is lost
  • Solution (one object, one function)

1. Some typical problems of JS digital precision loss

1. Add two simple floating point numbers

0.1 + 0.2 != 0.3 // true

This is really not a Firebug problem, you can try using alert (haha, just kidding).

Look at the Java operation results

Look at Python again

2. Large integer operations

It makes no sense that 16-digit and 17-digit numbers are equal.

Another example

var x = 9007199254740992
x + 1 == x // ?

See the results

Three views have been subverted again.

3. toFixed will not round (Chrome)

There have been cases online where prices in Chrome were inconsistent with other browsers

2. Reasons why JS numbers lose precision

The computer’s binary implementation and bit limit limit some numbers that cannot be represented finitely. Just like some irrational numbers cannot be represented finitely, such as pi 3.1415926..., 1.3333... etc. JS follows the IEEE 754 specification, uses double precision storage, and occupies 64 bits. As shown in the picture

Meaning

  • 1 bit is used to represent the sign bit
  • 11 bits are used to represent the exponent
  • 52 bits represents the mantissa

Floating point number, such as

0.1 >> 0.0001 1001 1001 1001…(1001无限循环)
0.2 >> 0.0011 0011 0011 0011…(0011无限循环)

At this time, we can only imitate decimal for rounding, but binary only has two numbers: 0 and 1, so it becomes 0 and 1 for rounding. This is the root cause of errors and loss of precision in some floating-point number operations in computers.

The precision loss of large integers is essentially the same as that of floating point numbers. The maximum number of mantissa digits is 52. Therefore, the largest integer that can be accurately represented in JS is Math.pow(2, 53), which in decimal is 9007199254740992.

Those larger than 9007199254740992 may lose precision

9007199254740992  >> 10000000000000...000 // 共计 53 个 0
9007199254740992 + 1 >> 10000000000000...001 // 中间 52 个 0
9007199254740992 + 2 >> 10000000000000...010 // 中间 51 个 0

Actually

9007199254740992 + 1 // 丢失
9007199254740992 + 2 // 未丢失
9007199254740992 + 3 // 丢失
9007199254740992 + 4 // 未丢失

The result is as shown in the picture

From the above, we can know that the seemingly finite numbers are infinite in the binary representation of the computer. Due to the limitation of the number of storage digits, there is "rounding", and the loss of precision occurs.

3. Solution

For integers, the probability of front-end problems may be relatively low. After all, few business needs require the use of very large integers. As long as the operation result does not exceed Math.pow(2, 53), the accuracy will not be lost.

For decimals, there are still many chances of problems on the front end, especially when some e-commerce websites involve data such as amounts. Solution: Put the decimal into an integer (multiply), then reduce it back to the original multiple (divide multiple)

// 0.1 + 0.2
(0.1*10 + 0.2*10) / 10 == 0.3 // true

The following is an object I wrote to shield the loss of precision in decimal addition, subtraction, multiplication and division operations. Of course, the converted integer still cannot exceed 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 is fixed as follows

// toFixed 修复
function toFixed(num, s) {
 var times = Math.pow(10, s)
 var des = num * times + 0.5
 des = parseInt(des, 10) / times
 return des + ''
}

The above is all about the problem of JavaScript numerical precision loss. It analyzes typical problems, analyzes the reasons for numerical precision loss, and also shares solutions. I hope it will be helpful to everyone's learning.

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn