pfan123 / Articles

经验文章
169 stars 25 forks source link

JavaScript 大数运算精度问题 #78

Open pfan123 opened 3 years ago

pfan123 commented 3 years ago

先从一个例子讲起

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

这个不用算简单看一下都知道计算结果不对,最后几位肯定应该都是1的,可是为什么会得到一个不对的值呢?

JavaScript Number的精度丢失问题

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

Math.pow(2, 53);    // 9007199254740992
Math.pow(2, 53) === Math.pow(2, 53) + 1;    // true
9007199254740992 === 9007199254740992 + 1;    // true

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

image

解决方案

当有两个整数 a 和 b ,在通常情况下我们有“+”运算符对其进行相加运算:

let sum = a + b;

但是 JS 在存放整数的时候是有一个安全范围的,一旦数字超过这个范围便会损失精度。

我们不能拿精度损失的数字进行运行,因为运算结果一样是会损失精度的。

所以,我们要用字符串来表示数据!(不会丢失精度)

JS 中整数的最大安全范围可以查到是:9007199254740991 假如我们要进行 9007199254740991 + 1234567899999999999

我们要先准备两个字符串变量和一个方法:

const a = "9007199254740991"
const b = "1234567899999999999"

function add(a ,b){
   //...
}

然后将字符串长度对齐:

const a = "9007199254740991"
const b = "1234567899999999999"

function add(a, b){
   //取两个数字的最大长度
   let maxLength = Math.max(a.length, b.length)
   //用0去补齐长度
   a = a.padStart(maxLength , 0)    //"0009007199254740991"
   b = b.padStart(maxLength , 0)   //"1234567899999999999"
}

然后从个位开始相加:

const a = "9007199254740991";
const b = "1234567899999999999";

function add(a, b) {
    if (typeof a === 'number') a = String(a)
    if (typeof b === 'number') b = String(b)

    //取两个数字的最大长度
    const maxLength = Math.max(a.length, b.length)

    //用0去补齐长度
    a = a.padStart(maxLength, 0)
    b = b.padStart(maxLength, 0)

    //定义加法过程中需要用到的变量
    let t = 0
    let f = 0  // 进位
    let sum = ''

    for(let i = maxLength; i--;) {
        t = parseInt(a[i]) + parseInt(b[i]) + f
        f = Math.floor(t/10)
        sum = t % 10 + sum
    }

    if (f === 1) {
        sum = '1' + sum
    }
    return sum
}

运行:

add(a ,b).    // 结果为:1243575099254740990

add(1234567899999999999, '1234567899999999999')   // 结果为: "2469135799999999999"