Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【JS】关于js浮点数计算精度不准确问题的解决办法 #11

Open
lesonwu opened this issue Aug 1, 2019 · 0 comments
Open

Comments

@lesonwu
Copy link
Owner

lesonwu commented Aug 1, 2019

前言

  在项目中计算商品价格的时候再次遇到js浮点数计算出现误差的问题,以前一碰到这个问题就用tofixed方法进行处理一下,这对于一个程序员来说是及其不严谨的。何况用tofixed方法也是有问题的。如下问题:

console.log(1.35.toFixed(1)); // 1.4 正确
console.log(1.335.toFixed(2));//1.33 错误 
console.log(1.3335.toFixed(3));//1.33 错误
console.log(1.33335.toFixed(4));//1.3334 正确
console.log(1.333335.toFixed(5));//1.33333 错误
console.log(1.3333335.toFixed(6));//1.333333 错误

解决方法有下面两种:

** 方法一:(定义一个全局方法,直接调用)**

// num表示需要四舍五入的小数
    // s表示需要保留几位小数
    function toFixed(num, s) {
        var times = Math.pow(10, s);
        var des = num * times + 0.5;
        des = parseInt(des, 10) / times;
        return des + '';
    }

调用方法如下:

console.log(toFixed(0.335,2));//0.34

方法二:重写toFixed方法:

Number.prototype.toFixed = function (s) {
        var times = Math.pow(10, s);
        var des = this * times + 0.5;
        des = parseInt(des, 10) / times;
        return des + '';
};

调用方法如下:

console.log(1.335.toFixed(2));//1.34

1、发现问题:

在测试js浮点数进行加减乘除计算时,都可能出现问题,如下:

console.log(0.1 + 0.2);//0.30000000000000004
console.log(1.0 - 0.9);//0.09999999999999998
console.log(19.9 * 100);//1989.9999999999998
console.log(6.6 / 0.2);//32.99999999999999

2、不精准原因:

下面我们来说一下浮点数运算产生误差的原因:(拿0.1+0.2=0.30000000000000004进行举例)

首先,我们要站在计算机的角度思考 0.1 + 0.2 这个看似小儿科的问题。我们知道,能被计算机读懂的是二进制,而不是十进制,所以我们先把 0.1 和 0.2 转换成二进制看看:  

0.1 => 0.0001 1001 1001 1001…(无限循环)

0.2 => 0.0011 0011 0011 0011…(无限循环)  

上面我们发现0.1和0.2转化为二进制之后,变成了一个无限循环的数字,这在现实生活中,无限循环我们可以理解,但计算机是不允许无限循环的,对于无限循环的小数,计算机会进行舍入处理。进行双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 ,因浮点数小数位的限制而截断的二进制数字,这时候,我们再把它转换为十进制,就成了 0.30000000000000004。

注:十进制与二进制之间的转换

3、解决办法:


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: 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
        }
 
        /*
         * 核心方法,实现加减乘除运算,确保不丢失精度
         * 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除)
         *
         * @param a {number} 运算数1
         * @param b {number} 运算数2
         * @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide)
         *
         */
        function 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
            }
        }
 
        // 加减乘除的四个接口
        function add(a, b) {
            return operation(a, b, 'add')
        }
 
        function subtract(a, b) {
            return operation(a, b, 'subtract')
        }
 
        function multiply(a, b) {
            return operation(a, b, 'multiply')
        }
 
        function divide(a, b) {
            return operation(a, b, 'divide')
        }
 
        // exports
        return {
            add: add,
            subtract: subtract,
            multiply: multiply,
            divide: divide
        }
    }();

调用方法:

console.log(floatObj.add(0.1, 0.2));//0.3
console.log(floatObj.subtract(1.0, 0.9));//0.1
console.log(floatObj.multiply(19.9, 100));//1990
console.log(floatObj.divide(6.6, 0.2));//33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant