本质

js计算精度问题导致,js整数最大取值为2的53次方,也就是9007199254740992

需要掌握的知识点

  1. 进制(十进制,二进制,八进制,十六进制)
  2. 科学计数法
  3. 浮点型的存储机制
进制不做过多介绍了,主要说一下后两点

科学计数法

对于浮点型的数,小数点位置的不固定和小数点后数组的不固定,所有就有人提出了科学计数法来标识浮点数
公式:
3929587194.png

a表示浮点数的二进制数

e标识小数点移动的位数

例如:10转换成二进制为 1010,用科学计数法表示为:1.010 * 2^3
再来解释一下科学计数法在计算中是怎么存储的

名称符号位指数位小数位指数偏移量
单精度浮点数1位8位23位127
双精度浮点数1位11位52位1023

解释:

  • 1位用来保存符号
  • 11位用来保存指数
  • 52位用来保存小数部分

符号位:1表示正数,0表示负数
指数位:e可以是正数,也可以使负数,转换规则,e+偏移量的结果转换成二进制
小数位:就是科学计数法小数点后的数,总数是52,不够补0,这里的52也是表示整数的最大取值为52位
指数偏移量:公式
144001956.jpg

k是指数位个数(双精度的指数位为11位,所有x = 2^11-1 = 1023)

例如:10的二进制1010,科学计数法为:1.010 * 2^3
转成二进制标准形式为:
符号位 + 指数位 + 小数位 = 1 + 10000000010 + 0100000000000000000000000000000000000000000000000000
正好64位

再举一个浮点数的例子:
0.1 + 0.2 = 0.30000000000000004;
我们来通过上面的知识点来分析这个问题:

备注:小数转二进制,通过乘2去整,顺序排列
  1. 推导0.1
    0.1 的二进制:0.0001100110011001100110011001100110011001100110011001101 (备注:在科学计数法的形式中只能保存64位有效数字,这里进行了四舍五入末尾0变成1)
    0.1 的科学计数法:1.100110011001100110011001100110011001100110011001101 * 2^(-4)
    0.1 在计算机中实际存储的形式:1011111110111001100110011001100110011001100110011001100110011010 正好64位
  2. 推导0.2
    0.2 的二进制:0.001100110011001100110011001100110011001100110011001101
    0.2 的科学计数法:1.100110011001100110011001100110011001100110011001101 * 2^(-3)
    0.2 在计算机中实际存储的形式:1011111111001001100110011001100110011001100110011001100110011010
  3. 将0.1和0.2二进制相加
    0.0001100110011001100110011001100110011001100110011001101 + 0.0011001100110011001100110011001100110011001100110011010 =
    0.0100110011001100110011001100110011001100110011001100111
    转换为十进制也就是0.30000000000000004
明白了原理和基本知识点,接下来就是大数相加问题的处理了

思路1:

  1. 首先number类型是有对打值的超过最大值,就会不准确,最大位数为16位,所以第一步会先对大数字符窜做截取,以14位为一段(考虑到15位和15位相也可能超多最大值,这里采用了比较安全的14位截取)
  2. 当每段的14位先加完,判断相加后的总长度是否大于等于15位,也就是有没有进位,如果有则在下一轮计算中需要把进位1加上
function addBigNum(num1, num2){
    var l1 = String(num1).length,           //保存num1长度
        l2 = String(num2).length,           //保存num2长度
        flag = false,               //判断是否有进位
        newNum1 = String(num1),             //用来后面做截取
        newNum2 = String(num2),
        newSum = "",
        sum = '';
    if(l1 < 14 && l2 < 14){
        return Number(num1) + Number(num2);
    }
    while (l1 >= 14 || l2 >= 14){
        newNum1 = l1 && l1 > 14 ? newNum1.slice(l1-14?l1-14:0, l1): '';
        newNum2 = l2 && l2 > 14 ? newNum2.slice(l2-14?l2-14:0, l2): '';

        //判断时候有进位
        if(flag){
            newSum = String(Number(newNum1) + Number(newNum2) + 1);
            flag = false;
        }else {
            newSum = String(Number(newNum1) + Number(newNum2));
        }

        if(newSum.length >= 15){
            sum = String(newSum.slice(1, newSum.length)) + String(sum);
            flag = true;
        }else {
            sum =  String(newSum) + String(sum);
        }

        l1 -= 14;
        l2 -= 14;

        //最后把不足14位的部分做加法
        if(l1 < 14 && l2 < 14){
            if(flag){
                sum =  String(Number(num1.slice(0, l1)) + Number(num2.slice(0, l2)) + 1) + String(sum);
            }else {
                sum =  String(Number(num1.slice(0, l1)) + Number(num2.slice(0, l2))) + String(sum);
            }
        }
    }

    return sum;
}

Last modification:August 19, 2022
If you think my article is useful to you, please feel free to appreciate