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

javascript 实现精度运算 #8

Open
sunhengzhe opened this issue Aug 9, 2017 · 7 comments
Open

javascript 实现精度运算 #8

sunhengzhe opened this issue Aug 9, 2017 · 7 comments
Assignees

Comments

@sunhengzhe
Copy link
Member

sunhengzhe commented Aug 9, 2017

用 js 实现高精度运算,这个问题是老生常谈了。如果真的要前端做一个高精度运算(比如钱),还是要注意这个问题的。

function add (numLeft, numRight){
    // todo
}

ex: add(0.1, 0.2) => 0.3

更一般地,实现四则运算的精度运算

function operation (numLeft, numRight, character){
    // todo
}

ex: operation(0.1, 0.2, '+') => 0.3
@sunhengzhe
Copy link
Member Author

sunhengzhe commented Aug 9, 2017

这个问题是在一个前端群里讨论的,放在 这里。后续可以去那里讨论

@keer3
Copy link
Member

keer3 commented Aug 12, 2017

感觉写得不好

function add() {

    const result = [...arguments].reduce((x, y) => {
        y = y.toString();

        // 兼容整数
        y = y.indexOf('.') == -1 ? y = y + '.0' : y;

        let [intX, floatX] = x.split('.');
        let [intY, floatY] = y.split('.');

        let floatRet = addStr(floatX, floatY);

        floatRet = floatRet.indexOf('.') != -1 ? floatRet.split('.') : (floatRet + '.0').split('.') ;
        let intRet = parseInt(intX) + parseInt(intY) + parseInt(floatRet[0]);

        return intRet + '.' + floatRet[1];
    }, '0.0');
    return result;
}

const addStr = (x, y) => {

    const xLength = x.length,
          yLength = y.length,
          maxLength = Math.max(xLength, yLength);

    // 填充长度
    x = x + Array(maxLength - xLength).fill(0).join('');
    y = y + Array(maxLength - yLength).fill(0).join('');

    let result = [],
          j = 0,
          flag = 0;
    for(let i = maxLength - 1; i >= 0; i--) {
        let r = 0;
        r = parseInt(x[i]) + parseInt(y[i]) + flag;
        flag = Math.floor(r / 10);
        r = r < 10 ? r : r % 10;
        result[j++] = r;
    }

    flag ? result.push(flag) : '';
    let ret = '';
    if (result.length - maxLength ===  0) {
        result.reverse().splice(result.length - maxLength, 0, '0.');
    } else {
        result.reverse().splice(result.length - maxLength, 0, '.');
    }
    return result.join('');

}

@sunhengzhe
Copy link
Member Author

sunhengzhe commented Aug 12, 2017

@SUNSHUMIN

x = x + Array(maxLength - xLength).fill(0).join('');

这里可以使用 es6 的 padEnd() 方法代替:x = x.padEnd(maxLength, 0)

@sunhengzhe
Copy link
Member Author

sunhengzhe commented Aug 12, 2017

@SUNSHUMIN

if (result.length - maxLength ===  0) {
    result.reverse().splice(result.length - maxLength, 0, '0.');
} else {
    result.reverse().splice(result.length - maxLength, 0, '.');
}

这里的判断实际上就是有无进位,我感觉这个方法返回 是否进位小数部分 两个东西比较好。然后在 add 方法里,根据你的做法是把整数部分加起来(其实这个地方可能溢出,最好是像处理小数一样处理成字符串)。把你代码简化了一下:

function add(/* number1, number2, number3, ... */) {
    const result = [...arguments].reduce((x, y) => {
        // ...
        const { isCarry, decimals } = addStr(floatX, floatY);

        const intRet = parseInt(intX) + parseInt(intY) + isCarry; // 自动转型

        return intRet + '.' + decimals;
    }, '0.0');
    return result;
}

const addStr = (x, y) => {
    //...
    flag ? result.push(flag) : '';

    const isCarry = result.length !== maxLength;

    if (isCarry) {
        result.pop();
    }

    return {
        isCarry,
        decimals: result.reverse().join('')
    };
}

@sunhengzhe
Copy link
Member Author

还有两个精度有关的补充:

  1. 使用 ES6 引入的 Number.EPSILON
x = 0.2;
y = 0.3;
z = 0.1;
equal = (Math.abs(x - y + z) < Number.EPSILON);
  1. parseInt(0.0000008) === 8 结果为 true

因为 0.0000008 会先被转成 8e-7

@ShaofeiZi
Copy link

EMM 我又碰到了 不过是四舍五入
https://juejin.im/post/5ad5c104518825558002bdc0

@ShaofeiZi
Copy link

     * 四舍五入
     * @param number 要四舍五入的数字
     * @param precision 精度 保留小数点位数
     * @returns {*}
     */
    function round(number,precision) {
      const enlargeDigits = function enlargeDigits(times) {
        return function (number) {
          return +(String(number) + "e" + String(times));
        };
      };
      const toFixed = function toFixed(precision) {
        return function (number) {
          return number.toFixed(precision);
        };
      };
      const compose = function compose() {
        for (var _len = arguments.length, functions = Array(_len), _key = 0; _key < _len; _key++) {
          functions[_key] = arguments[_key];
        }

        var nonFunctionTypeLength = functions.filter(function (item) {
          return typeof item !== 'function';
        }).length;
        if (nonFunctionTypeLength > 0) {
          throw new Error("compose's params must be functions");
        }
        if (functions.length === 0) {
          return function (arg) {
            return arg;
          };
        }
        if (functions.length === 1) {
          return functions[0];
        }
        return functions.reduce(function (a, b) {
          return function () {
            return a(b.apply(undefined, arguments));
          };
        });
      };
      var precision = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2;

      if (Number.isNaN(+number)) {
        throw new Error("number's type must be Number");
      }
      if (Number.isNaN(+precision)) {
        throw new Error("precision's type must be Number");
      }
      return compose(toFixed(precision), enlargeDigits(-precision), Math.round, enlargeDigits(precision))(number)
    }```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests