Solution to the problem of inaccurate calculation accuracy of js floating point number

Posted by adamhhh on Wed, 29 Dec 2021 20:05:47 +0100

Solution to the problem of inaccurate calculation accuracy of js floating point number

Today, when calculating the commodity price, we encounter the problem of js floating point calculation error again. We have encountered this problem before. We simply use the tofixed method to deal with it, which is extremely lax for a programmer. Therefore, some articles dealing with the precision of floating-point numbers are collected on the Internet. I think others have written very well. I'm simply summarizing it for later reference.

Causes of floating point error:

Let's take a look at an example:

0.1 + 0.2 =?

0.1 + 0.2 = 0.3?

Let's start with a JS.

console.log( 0.1+ 0.2);

The output is 0.300000000000000 4. Isn't it wonderful
In fact, for the four operations of floating-point numbers, almost all programming languages will have similar accuracy errors, but in C++/C#/Java has encapsulated methods to avoid the problem of precision, while JavaScript It is a weakly typed language. There is no strict data type for floating-point numbers from the design idea, so the problem of precision error is particularly prominent. The following is an analysis of why there is this accuracy error and how to repair it.

First of all, we should think about the seemingly pediatric problem of 0.1 + 0.2 from the perspective of computer. We know that what can be read by the computer is binary, not decimal, so let's convert 0.1 and 0.2 into binary first:

0.1 = > 0.0001 1001 1001... (infinite loop)
0.2 = > 0.0011 0011 0011... (infinite loop)

Above, we found that 0.1 and 0.2 become an infinite loop number after being converted into binary. In real life, we can understand the infinite loop, but the computer does not allow infinite loop. For the decimal of infinite loop, the computer will round off. The decimal part of a double precision floating-point number is supported at most 52 bits, so the two add up to get such a string 0.0100110011001100110011001100110011001100110011001100 The binary number truncated due to the limitation of floating-point decimal places. At this time, we convert it into decimal system, which becomes 0.300000000000000 4.

We know the cause of floating point numbers, so how to deal with this problem?

Method 1: specify the number of decimal places to keep (0.1 + 0.2) toFixed(1) = 0.3; This method ToFixed is rounded, and it is not very accurate. It is not recommended for the rigorous problem of calculating the amount, and the calculation results of ToFixed are different in different browsers.

Method 2: the number to be calculated is upgraded (multiplied by the n-th power of 10) to an integer that can be accurately recognized by the computer, and then degraded (divided by the n-th power of 10) after calculation. This is a general method for most programming languages to deal with accuracy differences

 

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

 

Online processing method:

//addition  
Number.prototype.add = function(arg){  
        var r1,r2,m;  
        try{r1=this.toString().split(".")[1].length}catch(e){r1=0}  
        try{r2=arg.toString().split(".")[1].length}catch(e){r2=0}  
        m=Math.pow(10,Math.max(r1,r2))  
        return (this*m+arg*m)/m  
} 
//subtraction
Number.prototype.sub = function (arg){  
    return this.add(-arg);  
}  
//multiplication
Number.prototype.mul = function (arg)   {  
    var m=0,s1=this.toString(),s2=arg.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)  
}  
//division
Number.prototype.div = function (arg){  
    var t1=0,t2=0,r1,r2;  
    try{t1=this.toString().split(".")[1].length}catch(e){}  
    try{t2=arg.toString().split(".")[1].length}catch(e){}  
    with(Math){  
        r1=Number(this.toString().replace(".",""))  
        r2=Number(arg.toString().replace(".",""))  
        return (r1/r2)*pow(10,t2-t1);  
    }  
}

 

* ** method **
 *  add / subtract / multiply /divide
 *
 * ** explame **
 *  0.1 + 0.2 == 0.30000000000000004 (0 more.00000000000004)
 *  0.2 + 0.4 == 0.6000000000000001  (0 more.0000000000001)
 *  19.9 * 100 == 1989.9999999999998 (Missing 0.0000000000002)
 *
 * floatObj.add(0.1, 0.2) >> 0.3
 * floatObj.multiply(19.9, 100) >> 1990
 *
 */
var floatObj = function() {
 
    /*
     * Judge whether obj is an integer
     */
    function isInteger(obj) {
        return Math.floor(obj) === obj
    }
 
    /*
     * Converts a floating-point number to an integer and returns an integer and multiple. If 3.14 > > 314, the multiple is 100
     * @param floatNum {number} decimal
     * @return {object}
     *   {times:100, num: 314}
     */
    function toInteger(floatNum) {
        var ret = {times: 1, 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
    }
 
    /*
     * The core method realizes addition, subtraction, multiplication and division to ensure no loss of accuracy
     * Idea: enlarge the decimal to integer (multiply), perform arithmetic operation, and then reduce it to decimal (divide)
     *
     * @param a {number} Operand 1
     * @param b {number} Operand 2
     * @param digits {number} Precision, the number of decimal points reserved, such as 2, is reserved as two decimal places
     * @param op {string} Operation type, including add/subtract/multiply/divide
     *
     */
    function operation(a, b, digits, op) {
        var o1 = toInteger(a)
        var o2 = toInteger(b)
        var n1 = o1.num
        var n2 = o2.num
        var t1 = o1.times
        var t2 = o2.times
        var max = t1 > t2 ? t1 : t2
        var result = null
        switch (op) {
            case 'add':
                if (t1 === t2) { // The two decimal places are the same
                    result = n1 + n2
                } else if (t1 > t2) { // o1 decimal places greater than o2
                    result = n1 + n2 * (t1 / t2)
                } else { // o1 decimal places less than o2
                    result = n1 * (t2 / t1) + n2
                }
                return result / max
            case 'subtract':
                if (t1 === t2) {
                    result = n1 - n2
                } else if (t1 > t2) {
                    result = n1 - n2 * (t1 / t2)
                } else {
                    result = n1 * (t2 / t1) - n2
                }
                return result / max
            case 'multiply':
                result = (n1 * n2) / (t1 * t2)
                return result
            case 'divide':
                result = (n1 / n2) * (t2 / t1)
                return result
        }
    }
 
    // Four interfaces for addition, subtraction, multiplication and division
    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 The repairs are as follows
 
 
// toFixed repair
function toFixed(num, s) {
    var times = Math.pow(10, s)
    var des = num * times + 0.5
    des = parseInt(des, 10) / times
    return des + ''
}