• JavaScript超大整数加法


    什么是「超大整数」?

    JavaScript 采用 IEEE754标准 中的浮点数算法来表示数字 Number。

    我也没花时间去详细了解 IEEE754标准 ,但对于处理超大整数,了解下面的几个知识点就足够了。

    首先,JavaScript 实际上可以表示的最大数是: 1.7976931348623157e+308

    Number.MAX_VALUE;    // 1.7976931348623157e+308

    虽然这个数可以正确表示出来,但会存在「精度丢失」的问题。

    那什么是「精度丢失」? 我们看看下面的例子:

    num1 = 10000000000000000000000000 + 11111111111111111111111111;    // 2.111111111111111e+25
    num2 = 21111111111111111111111000;    // 2.111111111111111e+25
    num1 === num2;    // true

    按照常规的数学预算, num1 的计算结果是 21111111111111111111111111,而 num2 的值是 21111111111111111111111000,两者是不可能相等。但实际上 JavaScript 可以精确表示到个位的最大整数是:9007199254740992

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

    关于 JavaScript Number 的一些上下极限,更详细的资料可以看下图:

    正因为 JavaScript 的 Number 类型存在这些限制,当我们需要处理两个「超大整数」的相加时,直接套用加法运算符会存在以下问题:

    • 当结果大于 Math.pow(2, 53)  时,会出现精度丢失,导致最终结果存在偏差
    • 当结果大于 Number.MAX_VALUE,直接返回 Infinity

    为了解决这些问题,才产生了「超大整数」加法的需求,实现代码如下:

    var largeIntegerAddition = function () {
        function isNumberString() {
            var result = true;
            for (var i = arguments.length; i--;) {
                if (typeof arguments[i] !== 'string' || !/^d+$/.test(arguments[i])) {
                    console.error('arguments format is incorrect!');
                    result = false;
                    break;
                }
            }
            return result;
        }
    
        function trimHeadZero(numberStr) {
            return numberStr.replace(/^0*/, '');
        }
    
        return function () {
            var bigNum1 = arguments[0],
                bigNum2 = arguments[1];
    
            if (!bigNum2) {
                return isNumberString(bigNum1) ? trimHeadZero(bigNum1) : '0';
            } else {
                if (!isNumberString(bigNum1, bigNum2)) {
                    return '0';
                }
    
                bigNum1 = trimHeadZero(bigNum1);
                bigNum2 = trimHeadZero(bigNum2);
    
                var carry = 0,  // 进位
                    bigNum1Split = bigNum1.split('').reverse(),
                    bigNum2Split = bigNum2.split('').reverse(),
                    result = '',
                    maxNumSize = bigNum1Split.length > bigNum2Split.length ? bigNum1Split.length : bigNum2Split.length;
    
                for (var i = 0; i < maxNumSize; i++) {
                    var n1 = bigNum1Split[i] ? +bigNum1Split[i] : 0,
                        n2 = bigNum2Split[i] ? +bigNum2Split[i] : 0,
                        sum = (n1 + n2 + carry).toString();
                    if (sum.length > 1) {
                        carry = +sum.slice(0, 1);
                        result = sum.slice(1, 2) + result;
                    } else {
                        carry = 0;
                        result = sum + result;
                    }
                }
    
                if (carry !== 0) {
                    result = carry + result;
                }
    
                if (arguments[2]) {
                    var argumentArr = Array.prototype.slice.call(arguments, 0).slice(2);
                    argumentArr.unshift(result);
                    return largeIntegerAddition.apply(this, argumentArr);
                } else {
                    return result;
                }
            }
        }
    }();

    测试用例:

    // 测试用例
    function unitTest(arg, result) {
        var res = largeIntegerAddition.apply(this, arg);
        console.log(res, res === result);
    }
    unitTest([], '');
    unitTest(['012', 3], '15');
    unitTest(['012', '0013', '214', 100002], '100241');
    unitTest(['1.1111111111111111e+227', '1'], '1.1111111111111111e+227');
    unitTest(['123'], '123');
    unitTest(['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'], '45');
    unitTest(['0', '2', '3', '4', '123'], '132');
    unitTest(['012', '3'], '15');
    unitTest(['012', '0013', '214', '100002'], '100241');
    unitTest(['99999999999999999999', '1'], '100000000000000000000');
    unitTest(['99999999999999999999', '11111111111111111111'], '111111111111111111110');
    unitTest(['99999999999999999999', '11111111111111111111', '11111111'], '111111111111122222221');
    unitTest(['4810284728175829182', '92817475910285750182'], '97627760638461579364');
    unitTest(['4810284728175829182', '92817475910285750182', '9728172845'], '97627760648189752209');
    unitTest(['4810284728175829182', '92817475910285750182', '9728172845' , '92875018002020102'], '97720635666191772311');
    unitTest([
        (function () {
            var str = '';
            for (var i = 500; i--;) {
                str += '9';
            }
            return str;
        })(),
        (function () {
            var str = '';
            for (var i = 500; i--;) {
                str += '1';
            }
            return str;
        })()
    ], (function () {
        var str = '';
        for (var i = 500; i--;) {
            str += '1';
        }
        return str + '0';
    })());

    本文作者:Maple Jan

    本文链接:http://www.cnblogs.com/maplejan/p/3893545.html

  • 相关阅读:
    Linux块设备IO子系统(二) _页高速缓存
    Linux块设备IO子系统(一) _驱动模型
    Linux usb子系统(二) _usb-skeleton.c精析
    Linux usb子系统(一) _写一个usb鼠标驱动
    Linux i2c子系统(四) _从i2c-s3c24xx.c看i2c控制器驱动的编写
    Linux i2c子系统(三) _解决probe无法执行
    Linux i2c子系统(二) _通过i2c-dev.c访问设备的方法
    Linux i2c子系统(一) _动手写一个i2c设备驱动
    从0移植uboot(六) _实现网络功能
    从0移植uboot(五) _实现串口输出
  • 原文地址:https://www.cnblogs.com/maplejan/p/3893545.html
Copyright © 2020-2023  润新知