JS处理大数相加问题

先从一个例子讲起

var number1 = 10000000000000000000000000 + 11111111111111111111111111   //理论上number1的值应该是21111111111111111111111111(javascript中会表示为科学计数法:2.111111111111111e+25)
var number2 = 21111111111111111111111000
console.log(number1 === number2)  //true
复制代码

这个不用算简单看一下都知道计算结果不对,最后几位确定应该都是1的,但是为何会获得一个不对的值呢?javascript

JavaScript Number的精度丢失问题

由于JavaScriptNumber类型是遵循IEEE 754规范表示的,这就意味着JavaScript能精确表示的数字是有限的,JavaScript能够精确到个位的最大整数是9007199254740992,也就是2的53次方,超过这个范围就会精度丢失,形成JavaScript没法判断大小,从而会出现下面的现象:php

Math.pow(2, 53);    // 9007199254740992
Math.pow(2, 53) === Math.pow(2, 53) + 1;    // true
9007199254740992 === 9007199254740992 + 1;    // true
复制代码

能够从下面这张图上看到JavaScript Number可以精确表示的上下限: html

image

解决方案

那当两个数据相加时,其中一个或者两个数据都超过了这个精度范围,直接相加结果就会不许了,那怎么解决呢?java

参考网上经常使用的一中方案是将Number转为String,而后将String转为Array,而且注意补齐较短的数组,将他们的长度标称同样再一一相加获得一个新数组,再讲和组成的新数组转为数字就能够了,下面是实现代码:数组

function sumString(a, b) {
  a = '0' + a;
  b = '0' + b;  //加'0'首先是为了转为字符串,并且两个数相加后可能须要进位,这样保证了和的长度就是a、b中长的那个字符的长度
  var arrA = a.split(''),  //将字符串转为数组
      arrB = b.split(''),
      res = [],  //相加结果组成的数组
      temp = '',  //相同位数相加的值
      carry = 0,  //同位数相加结果大于等于10时为1,不然为0
      distance = a.length - b.length,  //计算两个数字字符串的长度差
      len = distance > 0 ? a.length : b.length;  //和的长度
  // 在长度小的那个值前加distance个0,保证两个数相加以前长度是想等的
  if(distance > 0) {
    for(let i = 0; i < distance; i++) {
      arrB.unShift('0');
    }
  }else{
    for(let i = 0; i < distance; i++) {
      arrA.unShift('0');
    }
  }
  // 如今获得了两个长度一致的数组,须要作的就是把他们想通位数的值相加,大于等于10的要进一
  // 最终获得一个和组成的数组,将数组转为字符串,去掉前面多余的0就获得了最终的和
  for(let i = len-1; i >= 0; i--) {
    temp = Number(arrA[i]) + Number(arrB[i]) + carry;
    if(temp >= 10) {
      carry = 1;
      res.unshift((temp + '')[1])
    }
    else{
      carry = 0;
      res.unshift(temp)
    }
  }
  res = res.join('').replace(/^0/, '');
  console.log(res);
}
复制代码

关于0.1+0.2 !== 0.3的问题

前面讲到,在JavaScript中,使用浮点数标准IEEE 754表示数字的,在表示小数的时候,在转化二进制的时候有些数是不能完整转化的,好比0.3,转化成二进制是一个很长的循环的数,是超过了JavaScript能表示的范围的,因此近似等于0.30000000000000004。浏览器

这个是二进制浮点数最大的问题(不只 JavaScript,全部遵循 IEEE 754 规范的语言都是如此)。bash

怎么判断两个值是否想等

在这里咱们要引入ES6中在Number对象上新增的一个极小的常量Number.EPSILON。它表示1与大于1的最小浮点数之差,等于2的-52次方。学习

Number.EPSILON其实是 JavaScript 可以表示的最小精度。偏差若是小于这个值,就能够认为已经没有意义了,即不存在偏差了。ui

因此能够用这个来判断两个数浮点数是否想等:spa

function numIsEqual(lef, rig) {
    return Math.abs(lef - rig) < Number.EPSILON
}
复制代码

若是考虑浏览器兼容的话能够这样写:

function numIsEqual(lef, rig) {
    let EPSILON = Number.EPSILON ? Number.EPSILON : Math.pow(2,-52)
    return Math.abs(lef - rig) < EPSILON
}
复制代码

固然咱们平常使用小数的话通常精确到小数点后两位就够了,是不会有太大的问题的,可是必定要记得用toFied()方法保留小数点后位数,不能直接取计算的值,避免偏差。

尾巴

关于JavaScript中数据的表示问题,我以前在极客时间的《深刻浅出计算机组成原理》中还听到过一些,那里面详细讲解了为何浮点数的IEEE 754标准只能表示有限的数字,不过对于这么底层的东西我还只能勉强意会,哈哈。

参考

相关文章
相关标签/搜索