如何处理数字精度丢失

337 阅读2分钟

例子:0.1 + 0.2 != 0.3

一、原因

计算机是通过二进制的方式存储数据的,所以计算机计算 0.1 + 0.2 时,实际上计算的是两个数的二进制的和

0.1的二进制是 0.0001100110011001100...(1100循环)

0.2的二进制是 0.001100110011001100...(1100循环)

这两个数的二进制都是无限循环的数。

number类型相当于其他强类型语言中的double类型(双精度浮点型),不区分浮点型和整数型。由于Js的所有数字类型都是双精度浮点型(64位)采用 IEEE754 标准,最大可以存储53位有效数字,53位后面的会遵循“0舍1入”的原则。

根据这个原则,0.1和0.2的二进制数相加,再转换为十进制数就是:0.30000000000000004

所以0.1 + 0.2 != 0.3

二、解决方案

方案1:Number.EPSILON

设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2^52,在ES6中,提供了Number.EPSILON属性,而它的值就是2^52,只要判断 0.1 + 0.2 - 0.3 是否小于Number.EPSILON,如果小于,则 0.1 + 0.2 == 0.3


function isEqual(a, b) {
    return Math.abs(a - b) < Number.EPSILON
}

方案2:转为字符串

function addStrings(num1, num2) {
    let i = num1.length - 1;
    let j = num2.length - 1;
    const result = [];
    let carry = 0;
    while (i >= 0 || j >= 0) {
        const n1 = i > 0 ? Number(num[i]) : 0
        const n2 = j > 0 ? Number(num[j]) : 0
        const sum = n1 + n2 + carry;
        result.unshift(sum % 10);
        carry = Math.floor(sum / 10);
        i--;
        j--;
    }
    if(carry) {
        result.unshift(carry);
    }
    retuen result.join('');
}

function isEqual(a, b, sum) {
    const [intStr1, deciStr1] = a.toString().split('.');
    const [intStr2, deciStr2] = b.toString().split('.');
    const intSum = addStrings()(intStr1, intStr2); // 获取整数相加部分
    const deciSum = addStrings(deciStr1, deciStr2); // 获取小数相加部分
    return intSum + '.' + deciSum === String(sum)
}

实际开发场景中,判等场景较少,多数为两个数字直接运算

// 加法
function accAdd(arg1, arg2) {
  let r1, r2, m;
  try { r1 = arg1.toString().split(".")[1].length } catch (e) { r1 = 0 }
  try { r2 = arg2.toString().split(".")[1].length } catch (e) { r2 = 0 }

  // 当 4个两位小数的的值相加时,仍然又精度问题,处理方式:讲数值先乘以 m,再除以m
  m = Math.pow(10, Math.max(r1, r2) + 2)
  console.log('selectedRowKeys changed: ', m)
  return (arg1 * m + arg2 * m) / m
}


// 减法
function accSubtr(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));
  
  //动态控制精度长度
  n = (r1 >= r2) ? r1 : r2;
  return ((arg1 * m - arg2 * m) / m).toFixed(n);
}

// 乘法
function accMul(arg1, arg2) {
  if (typeof (arg1) !== 'undefined' && typeof (arg2) !== 'undefined') {
    let m = 0, s1 = arg1.toString(), 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)
  }
}

// 除法
funcion accDiv(arg1, arg2) {
  let t1 = 0, t2 = 0, r1, r2;
  try { t1 = arg1.toString().split(".")[1].length } catch (e) { }
  try { t2 = arg2.toString().split(".")[1].length } catch (e) { }
  if (Math) {
    r1 = Number(arg1.toString().replace(".", ""))
    r2 = Number(arg2.toString().replace(".", ""))
    return (r1 / r2) * Math.pow(10, t2 - t1);
  }
}