The problem and solution of precision loss when js performs four operations

1, Preface:
This problem can be said to be a pit that programmers must step on. Therefore, there are many detailed analysis of this problem on the Internet, the solutions are relatively unified, and the writing method is similar. I thought the expected effect could be perfect as they said, but the effect is not satisfactory.

2, Question:
First of all, let's take a look at a classic problem of adding two numbers. Those who have found a lot of information on the Internet will find that most people start the problem of analysis accuracy. However, in the end, the so-called solution lies in it and the failure lies in it.

0.1+0.2=0.30000000000000004


The perfect solution on the Internet:

function add(arg1,arg2){
    var digits1,digits2,maxDigits;
    try{digits1=arg1.toString().split(".")[1].length}catch(e){digits1=0}
    try{digits2=arg2.toString().split(".")[1].length}catch(e){digits2=0}
    maxDigits=Math.pow(10,Math.max(digits1,digits2))
    return (arg1*maxDigits+arg2*maxDigits)/maxDigits
}

Output results after application:

0.1+0.2=0.3

It can be seen from the results that this method perfectly solves this classical problem. Can other numbers be perfectly solved? I found no after most tests. I thought it might be caused by the development environment or browser, but it is still the case when switching multiple development environments and browsers. Therefore, it can be seen that there is a problem with the method itself.

Output result of direct addition of two numbers:

2.22+2.22=4.44
4.44+2.22=6.66
6.66+2.22=8.88
8.88+2.22=11.100000000000001
11.10+2.22=13.32
13.32+2.22=15.540000000000001

Output results of the method found on the Internet:

2.22+2.22=4.44
4.44+2.22=6.660000000000001
6.66+2.22=8.88
8.88+2.22=11.100000000000001
11.10+2.22=13.32
13.32+2.22=15.54

By comparing the two results, it can be found that the accuracy problem of some numbers has been solved, while some numbers have not been solved at all, and even some numbers that would not have accuracy problems have problems. In addition to addition, the methods on subtraction, multiplication and division online are also not feasible. Limited to the scope of the article, there are no more examples one by one here.

3, Solution:
Finally, it is found that those methods have a common feature, that is, multiply the original floating-point number with N decimal places by the n power of 10, but the main problem that has not been solved is the data type operation. As long as there are floating-point operations, there will be accuracy problems in any calculation.

Therefore, it is still necessary to ensure that the number involved in the operation is an Integer. In fact, their method multiplies the floating-point number with n decimal places to the nth power of 10, but it also needs to be forced to specify the type as int. However, if the number multiplied by 10 to the nth power is directly converted into an Integer object, there will be a precision problem and precision loss when multiplying, that is, the calculation result is incorrect. Therefore, it is still necessary to use the round method in Math library to round the number, and then carry out the operation.

Method of adding two numbers:

function add(arg1, arg2) {
  return (Math.round(arg1 * 100) + Math.round(arg2 * 100)) / 100;
}

The operation result of adding two numbers by using this method:

0.1+0.2=0.3
2.22+2.22=4.44
4.44+2.22=6.66
6.66+2.22=8.88
8.88+2.22=11.1
11.10+2.22=13.32
13.32+2.22=15.54


Subtraction of two numbers (i.e. one positive number plus one negative number):

function subtract(arg1, arg2) {
  return this.add(arg1, -arg2);
}

Multiply two numbers:

function multiple(arg1, arg2) {
  return (Math.round(arg1 * 100) * Math.round(arg2 * 100)) / 10000;
}

Divide two numbers:

/**
* arg1 Divide by arg2 and keep 2 decimal places by rounding
*/
function divide(arg1, arg2) {
var d1, d2,
n1 = Number(arg1.toString().replace(".", "")),
n2 = Number(arg2.toString().replace(".", ""));
try {d1 = arg1.toString().split(".")[1].length;} catch (e) {d1 = 0;}
try {d2 = arg2.toString().split(".")[1].length;} catch (e) {d2 = 0;}
return this.toFixed((n1 / n2) * Math.pow(10, d2 - d1), 2);
}

/**
* arg Keep n decimal places by rounding
*/
function toFixed(arg, n) {
if(n == 0) {
return Math.round(arg)
}
try {
var d, carryD,
ds = arg.toString().split('.'), // Split integer and decimal
len = ds[1].length
// arg The number of decimal places exceeds the limit n It needs to be handled
if (len > n) {
// Before interception n Decimal place
d = Number(ds[1].substring(0, n))
// Whether the decimal part can be rounded to one: pass 0.[The first n Number after decimal place] Rounded to 0 or 1
carryD = Math.round(Number('0.' + ds[1].substring(n, len)))
len = (d + carryD).toString().length
if (len > n) {
// When d=9...,carryD=1,After adding, you have to carry to the integer and directly return the integer part+1
return Number(ds[0]) + 1
} else if (len == n) {
return Number(ds[0] + '.' + (d + carryD))
}
// Decimals begin with 0 and must be supplemented by 0
var b0 = '', k = 0
while(k < n && ds[1].substring(0, ++k) == '0') {
b0 += '0'
}
return Number(ds[0] + '.' + b0 + (d + carryD))
}
} catch (e) {}
return arg
}

Note: the toFixed function in js will cut off the extra decimal directly. Therefore, I rewrite it here and keep the decimal by rounding. This method is also very practical.
--------


Copyright notice: This is the original article of CSDN blogger "Homilier", which follows the CC 4.0 BY-SA copyright agreement. Please attach the original source link and this notice for reprint.
Original link: https://blog.csdn.net/Honiler/article/details/86559589

Added by alabonte on Mon, 27 Dec 2021 05:43:10 +0200