Solve the problem of floating-point precision when using js toFixed to retain decimals and perform js addition, subtraction, multiplication, and division operations.

background:

When developing recently, when using the tofixed method to retain decimal places, it was found that when retaining some decimal places, the number of decimal places that were not rounded correctly was not correct, and when calculating decimal numbers, there was also a precision error. Condition,

Searching the documentation and consulting sources found that this is due to the computer's internal storage.
When we perform calculations, the computer first converts the number into binary form and stores it for calculation, and finally converts the binary result into decimal for output.

The same is true for decimals. But when the decimal is converted to binary, because the data needs to be multiplied by 2 until it is rounded, numbers like 0.5 can be quickly calculated to obtain binary results, but numbers like 0.1 and 0.2 are never calculated To get the exact binary result, when multiplying by 2 all the time, you will not get an integer. At this time, in the process of converting the binary system, an infinite endless loop will be formed.

When the computer stores infinite endless loop data, there must be a limit, and the principle of rounding up is adopted. Therefore, the corresponding binary numbers stored in the computer internally for 0.1 and 0.2 are not accurate, so the 2 obtained by adding The base result is not accurate. After converting to base 10, there will be a certain error, so the result is not exactly 0.3.

Inaccurate phenomena when using toFixed and calculations:

2.3335.toFixed(3) //'2.333'   出现错误
1 - 0.7   //0.30000000000000004   出现精度问题

toFixed imprecise solution:

// toFixed兼容方法
Number.prototype.toFixed = function(len){
    
    
    if(len>20 || len<0){
    
    
        throw new RangeError('toFixed() digits argument must be between 0 and 20');
    }
    // .123转为0.123
    var number = Number(this);
    if (isNaN(number) || number >= Math.pow(10, 21)) {
    
    
        return number.toString();
    }
    if (typeof (len) == 'undefined' || len == 0) {
    
    
        return (Math.round(number)).toString();
    }
    var result = number.toString(),
        numberArr = result.split('.');

    if(numberArr.length<2){
    
    
        //整数的情况
        return padNum(result);
    }
    var intNum = numberArr[0], //整数部分
        deciNum = numberArr[1],//小数部分
        lastNum = deciNum.substr(len, 1);//最后一个数字
    
    if(deciNum.length == len){
    
    
        //需要截取的长度等于当前长度
        return result;
    }
    if(deciNum.length < len){
    
    
        //需要截取的长度大于当前长度 1.3.toFixed(2)
        return padNum(result)
    }
    //需要截取的长度小于当前长度,需要判断最后一位数字
    result = intNum + '.' + deciNum.substr(0, len);
    if(parseInt(lastNum, 10)>=5){
    
    
        //最后一位数字大于5,要进位
        var times = Math.pow(10, len); //需要放大的倍数
        var changedInt = Number(result.replace('.',''));//截取后转为整数
        changedInt++;//整数进位
        changedInt /= times;//整数转为小数,注:有可能还是整数
        result = padNum(changedInt+'');
    }
    return result;
    //对数字末尾加0
    function padNum(num){
    
    
        var dotPos = num.indexOf('.');
        if(dotPos === -1){
    
    
            //整数的情况
            num += '.';
            for(var i = 0;i<len;i++){
    
    
                num += '0';
            }
            return num;
        } else {
    
    
            //小数的情况
            var need = len - (num.length - dotPos - 1);
            for(var j = 0;j<need;j++){
    
    
                num += '0';
            }
            return num;
        }
    }
}

The solution to the imprecise calculation of addition, subtraction, multiplication and division:

 /*
     * 判断obj是否为一个整数
     */
const isInteger = (obj) => {
    
    
    return Math.floor(obj) === obj
}


   /*
     * 将一个浮点数转成整数,返回整数和倍数。如 3.14 >> 314,倍数是 100
     * @param floatNum {number} 小数
     * @return {object}
     *   {times:100, num: 314}
     */
const 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 = Number(floatNum.toString().replace('.', ''))
    ret.times = times
    ret.num = intNum
    return ret
}
   /*
     * 核心方法,实现加减乘除运算,确保不丢失精度
     * 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除)
     *
     * @param a {number} 运算数1
     * @param b {number} 运算数2
     * @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide)
     *
     */
const operation = (a, b, 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) {
    
     // 两个小数位数相同
                result = n1 + n2
            } else if (t1 > t2) {
    
     // o1 小数位 大于 o2
                result = n1 + n2 * (t1 / t2)
            } else {
    
     // o1 小数位 小于 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
    }
}

operation(0.1, 0.2, 'add'); // 0.3   +  
operation(0.1, 0.2, 'subtract');  // -0.1  -
operation(0.1, 0.2, 'multiply');  //  0.02  *
operation(0.1, 0.2, 'divide');  //  0.5  /

It can be perfectly solved by these two methods.

Guess you like

Origin blog.csdn.net/m54584mnkj/article/details/128420684