As we all know,JS has a classic floating point operation precision loss problem, today we will talk about the cause of this problem, and how to solve it?
Let’s start with the following code: 0.1+0.2 does not equal 0.3. Is this beyond our previous knowledge? After all, 0.1+0.2=0.3 but what we have learned in primary school, how can it be different here?
0.1 + 0.2 //0.30000000000000004
Copy the code
Let’s first understand how it happens and then tackle it.
Why did it happen?
First, we need to know how numbers are stored and run in a computer. In the computer, both fixed point and floating point numbers are stored in the way of bit binary. Js is stored in the double precision standard of IEEE 754, which is a 64-bit double precision floating point number storage method. Where 1 bit represents the sign bit, which is positive and negative,0 is positive and 1 is negative. Eleven digits represent the exponent, and the remaining 52 digits represent the mantissa. It is expressed in the following format: (s) * (m) * (2 ^ e) S is the sign bit,m is the mantissa, and e is the exponent.
ES6 adds a tiny constant Number.EPSILON to the Number object. According to the specification, it represents the difference between 1 and the smallest floating point number greater than 1. Let’s print it out on the console, and you can see it
Number. 2.220446049250313 e-16 EPSILON / /Copy the code
Since 52 bits of a 64-bit floating point number are precision, the smallest floating point number greater than 1 should be 1.000… 001, there are 51 zeros behind the decimal point, and then 1 1. Subtracting 1 from this number gives 2 to the -52 power, which is math.pow (2,-52), so the following result will print true
Number.EPSILON === Math.pow(2,-52) //true
Copy the code
Therefore, we can think of number. EPSILON as the minimum precision that can be represented in JS. When the error is less than that, it doesn’t make any sense, you can say that the error is gone. That is, if the difference between two floating-point numbers is less than number. EPSILON, then the two floating-point numbers are considered equal. Back up, let’s figure out why 0.1+0.2 isn’t equal to 0.3. To convert a decimal decimal into a binary decimal, we use the double round method. You multiply the decimal by 2, and then you take the whole number, and then you multiply the rest of the decimal by 2, and then you multiply the rest of the decimal by 2, and you keep multiplying it until it’s zero.
We first convert 0.1 to binary and the result is 0.000110011.. 001 where the decimal part starts with 0011 from the second place and keeps repeating. We seem to have an infinite decimal, 0.1, which is actually infinite in computers. Due to limited storage space, the computer will discard the following values. We convert 0.2 to binary and the result is 0.00110011.. 001 where the decimal part starts at the first place and is 0011 and continues. Provide me with an online base conversion url here. Then we use IEEE 754 double precision 64-bit floating-point notation in JS to display 0.1 and 0.2, and the result is:
Decimal fraction | Index e | Mantissa m |
---|---|---|
0.1 | 4 – | 1.1001100110011001100110011001100110011001100110011010(52位) |
0.2 | – 3 | 1.1001100110011001100110011001100110011001100110011010(52位) |
And then we add them up, and if the exponents are different here, we move to the right, because the loss is less precise.
Decimal fraction | Index e | Mantissa m |
---|---|---|
0.1 | – 3 | 0.1100110011001100110011001100110011001100110011001101(52位) |
0.2 | – 3 | 1.1001100110011001100110011001100110011001100110011010(52位) |
– 3 | 10.0110011001100110011001100110011001100110011001100111 (52) |
e=-3; M = 0.1100110011001100110011001100110011001100110011001101 (52) + e = 3; M = 1.1001100110011001100110011001100110011001100110011010 (52), the result is: e = 3; M = 10.0110011001100110011001100110011001100110011001100111 (52) : e = 2; M = 1.00110011001100110011001100110011001100110011001100111 (53)
It can be seen that there are already 53 mantras at this time. We adopt round to nearest, tie to even rounding. It just means you take whatever you’re close to, and you take even when you’re close to it. For example :1.0101 has 3 decimal places, so it can be 1.010 or 1.011. So, e=-2; M = 1.00110011001100110011001100110011001100110011001100111 (53) converted to e = 2; M = 1.0011001100110011001100110011001100110011001100110100 (52), its binary decimal notation is 0.010011001100110011001100110011001100110011001100110100, we show it to the decimal system and with fractions, and the result is 0.30000000000000004. This result can be verified at the base conversion website provided above. From there, we can see why 0.1+0.2! = 0.3.
How to solve
Now that we know what causes this problem, how can we solve it?
Using a function library
Common function libraries such as Decimal. js can solve this problem
Write your own function
Knowing what the problem is, we have an idea of how to solve it. The usual way to do this is to turn a floating point number into an integer and then determine the position of the decimal point. The following addition function achieves the desired result.
function add(num1, num2){
let r1, r2, m;
try{
r1 = num1.toString().split('. ')[1].length
}catch(e){
r1 = 0
}
try{
r2 = num2.toString().split('. ')[1].length
}catch(e){
r2 = 0
}
m = Math.pow(10, Math.max(r1, r2))
return (num1 * m + num2 * m) / m
}
Copy the code
Similarly, there are subtraction, multiplication and division functions, which we also show below:
Subtraction function:
function sub(num1, num2){
let r1, r2, m, n;
try{
r1 = num1.toString().split('. ')[1].length
}catch(e){
r1 = 0
}
try{
r2 = num2.toString().split('. ')[1].length
}catch(e){
r2 = 0
}
n = Math.max(r1, r2)
m = Math.pow(10, n)
return Number(((num1 * m - num2 * m) / m).toFixed(n))
}
Copy the code
Multiplication function:
function multiply(num1, num2){
let m = 0,
s1 = num1.toString(),
s2 = num2.toString()
try {
m += s1.split('. ')[1].length
}catch(e){}
try {
m += s2.split('. ')[1].length
}catch(e){}
return Number(s1.replace('. '.' ')) * Number(s2.replace('. '.' ')) / Math.pow(10,m)
}
Copy the code
Division function:
function divide(num1, num2){
let t1,t2,r1,r2;
try {
t1 = num1.toString().split('. ')[1].length
}catch(e){
t1 = 0
}
try {
t2 = num2.toString().split('. ')[1].length
}catch(e){
t2 = 0
}
r1 = Number(num1.toString().replace('. '.' '))
r2 = Number(num2.toString().replace('. '.' '))
return (r1 / r2) * Math.pow(10, t2 - t1)
}
Copy the code
These functions above can solve the problem of addition, subtraction, multiplication and division precision display respectively.