前端 2019-09-17 17:59:20

js数字计算时,精度丢失

问题

0.1+0.2=0.30000000000000004
0.3-0.1=0.19999999999999998
0.1*0.2=0.020000000000000004
0.31/0.2=1.5499999999999998

计算出现精度丢失

原因

计算机使用二进制表示数字,位数限制导致有些数字无法有限表示。js使用双精度存储,占用64位:

1位用来表示符号位
11位用来表示指数
52位表示尾数

0.1和0.2的二进制表示新式如下:

0.1 >> 0.0001 1001 1001 1001…(1001无限循环)
0.2 >> 0.0011 0011 0011 0011…(0011无限循环)

由于无限循环,无法正确表示。最后模仿十进制的四舍五入,0舍1进。所以导致计算机中部分浮点数运算时出现误差,丢失精度

解决方法

1.直接使用对应类库number-precision

2.封装对应运算函数

// 浮点数 - 加
export function accAdd (arg1, arg2) {
  var r1, r2, m, c;

  try {
    r1 = arg1.toString().split('.')[1].length;
  } catch (e) {
    r1 = 0;
  }

  try {
    r2 = arg2.toString().split('.')[1].length;
  } catch (e) {
    r2 = 0;
  }

  c = Math.abs(r1 - r2);
  m = Math.pow(10, Math.max(r1, r2));
  if (c > 0) {
    var cm = Math.pow(10, c);
    if (r1 > r2) {
      arg1 = Number(arg1.toString().replace('.', ''));
      arg2 = Number(arg2.toString().replace('.', '')) * cm;
    } else {
      arg1 = Number(arg1.toString().replace('.', '')) * cm;
      arg2 = Number(arg2.toString().replace('.', ''));
    }
  } else {
    arg1 = Number(arg1.toString().replace('.', ''));
    arg2 = Number(arg2.toString().replace('.', ''));
  }
  return (arg1 + arg2) / m;
}

// 浮点数 - 减
export function accSub (arg1, arg2) {
  var r1, r2, m, n;
  try {
    r1 = arg1.toString().split('.')[1].length;
  } catch (e) {
    r1 = 0;
  }
  try {
    r2 = arg2.toString().split('.')[1].length;
  } catch (e) {
    r2 = 0;
  }
  m = Math.pow(10, Math.max(r1, r2)); // last modify by deeka //动态控制精度长度
  n = r1 >= r2 ? r1 : r2;
  return ((arg1 * m - arg2 * m) / m).toFixed(n);
}

// 浮点数 - 除以
export function accDiv (arg1, arg2) {
  var t1 = 0;
  var t2 = 0;
  var r1;
  var r2;
  try {
    t1 = arg1.toString().split('.')[1].length;
  } catch (e) {}
  try {
    t2 = arg2.toString().split('.')[1].length;
  } catch (e) {}
  r1 = Number(arg1.toString().replace('.', ''));
  r2 = Number(arg2.toString().replace('.', ''));
  return r1 / r2 / Math.pow(10, t1 - t2);
}

// 浮点数 - 乘以
export function accMul (arg1, arg2) {
  var m = 0;
  var s1 = arg1.toString();
  var s2 = arg2.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)
  );
}