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 精度丢失问题 #43

Open
lovelmh13 opened this issue Feb 23, 2020 · 0 comments
Open

JS 精度丢失问题 #43

lovelmh13 opened this issue Feb 23, 2020 · 0 comments

Comments

@lovelmh13
Copy link
Owner

lovelmh13 commented Feb 23, 2020

如果要深究 IEEE 754 精度为什么会丢失的原理,需要从原码、反码、补码说起。具体可以查看浮点数精度之谜这篇文章。

那么怎么可以避免这种情况呢?

网上不少文章里,提到了直接乘10的n次幂,完了再除以10的n次幂,但是这样也可能碰到问题:

35.41 * 100 = 3540.9999999999995

这里可以采取另一种方法:浮点数toStringindexOf('.'),记录一下小数位的长度,然后将小数点抹掉

  1. 先把浮点数都转换成整数,把.给抹掉,并记录一下有多少位小数。
  2. 然后进行整数的运算
  3. 再除以刚才保存的小数位数

如果是加减乘除,就需要对每一项,都进行1、2步骤的转换。然后选取最长的小数位的项,最终返回的结果除以最长的小数位的长度,得到最终的值。

 /*** method **
 *  add / subtract / multiply /divide
 * floatObj.add(0.1, 0.2) >> 0.3
 * floatObj.multiply(19.9, 100) >> 1990
 *
 */
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}  times是倍数,num是转换后的整数
     */
    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 = 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)
     *
     */
    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
    }
}();

在来试一下刚才的问题:

35.41 * 100 = 3540.9999999999995
||
floatObj.multiply(35.41, 100) // 3541

参考:
JS中浮点数精度问题

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