Home > Article > Web Front-end > How to solve the problem of losing numerical precision in JavaScript_javascript tips
This article is divided into three parts
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
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.