如安在JavaScript中完成一个Long型——Long.js源码进修与剖析

背景

因为在项目中运用到了WebSocket的自定义二进制协定,须要将二进制转为后端效劳中定义的Long型。而在JavaScript中的Number范例因为本身缘由,并不能完整示意Long型的数字,因而须要我们经由历程其他的体式格局来对Long型值举行存储。

目的

在GitHub中,有一个完成了在JavaScript中存储Long型的对象,详细代码可以戳此。下面,我们经由历程简朴解说一下这个库的详细完成来看看如安在JavaScript中完成一个Long型。假如你了解了这个完成道理,那末与之相似的,在JavaScript中完成一个Long Long型或许其他范例的要领也是相似的。

详细完成

实在,Long的完成很简朴,我们如今只需回归到计算机的实质即可。在计算机中,实在存储的都是01字符串。比方,Int占4个字节(我们以32位操纵系统为例),而Long则占8个字节。

我们在存储中只须要将数据经由历程二进制举行存储,然后在操纵中对二进制举行操纵即可。

下面我们简朴的来引见一下Long的各个代表操纵和头脑。

大抵步骤

数据存储

在Long型对象中,我们采用了高32位和低32位,以及加上一个标记位推断的值,用来举行数据的存储,详细花样以下:

function Long(low, high, unsigned) {
    this.low = low | 0;
    this.high = high | 0;
    this.unsigned = !!unsigned;
}

经由历程对上下位的存储,从而让两个Number来同时示意一个Long型的高位和低位,从而满足了数值的长度请求。

转换为Long型

我们现在只引见一个经由历程字符串来说数据从String型转换为Long型,其他的转换比方从Number转换为Long型是相似的,我们就不过量赘述了。

先看完成函数:

function fromString(str, unsigned, radix) {
    // 处置惩罚非常状况
    if (str.length === 0)
        throw Error('empty string');
        
    //处置惩罚为0的状况
    if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity")
        return ZERO;
        
    //处置惩罚只要两个参数的状况
    if (typeof unsigned === 'number') { 
        // For goog.math.long compatibility
        radix = unsigned,
        unsigned = false;
    } else {
        unsigned = !! unsigned;
    }
    radix = radix || 10;
    if (radix < 2 || 36 < radix)
        throw RangeError('radix');

    var p;
    if ((p = str.indexOf('-')) > 0)
        throw Error('interior hyphen');
    else if (p === 0) {
        // 转为正值处置惩罚
        return fromString(str.substring(1), unsigned, radix).neg();
    }
    
    // 从最高位分8位处置惩罚一次,假如长度凌驾8位,则先处置惩罚高位,然后将高位直接乘以进制的8次方,再处置惩罚低后8位,轮回到最后8位为止
    var result = ZERO;
    for (var i = 0; i < str.length; i += 8) {
        var size = Math.min(8, str.length - i),
            value = parseInt(str.substring(i, i + size), radix);
        if (size < 8) {
            var power = fromNumber(pow_dbl(radix, size));
            result = result.mul(power).add(fromNumber(value));
        } else {
            result = result.mul(radixToPower);
            result = result.add(fromNumber(value));
        }
    }
    result.unsigned = unsigned;
    return result;
}

下面我们简朴的说下这个函数的完成:

  1. 对数据举行非常处置惩罚,消除一些边界条件。

  2. 假如字符串为一个带”-“号的值,则转换为正值举行处置惩罚。

  3. 假如字符串为一个通例的Long型值,则先从最前面的8位最先处置惩罚,将其经由历程指定的进制转换为Long型的值。

  4. 处置惩罚接下来的8位,而且将之前的效果乘以进制数的8次方,即数字高职位的兼并。比方:18 = 1 * 10^1 + 8。

  5. 轮回上面的操纵,直到盈余的字符串长度小于8为止,即可完毕,获得转换今后的Long型。

转换为字符串

Long型转换为字符串的体式格局,与字符串转换为Long型的步骤差不多,差不多是一个相反的历程。

LongPrototype.toString = function toString(radix) {
    radix = radix || 10;
    if (radix < 2 || 36 < radix)
        throw RangeError('radix');
    if (this.isZero())
        return '0';
    //假如是负值,Unsigned型的Long值永久不会为负值
    if (this.isNegative()) {
        if (this.eq(MIN_VALUE)) {
            // We need to change the Long value before it can be negated, so we remove
            // the bottom-most digit in this base and then recurse to do the rest.
            var radixLong = fromNumber(radix),
                div = this.div(radixLong),
                rem1 = div.mul(radixLong).sub(this);
            return div.toString(radix) + rem1.toInt().toString(radix);
        } else
            return '-' + this.neg().toString(radix);
    }

    //每次处置惩罚6位,处置惩罚体式格局与字符串转换过来是相似的,和数学中十进制转换为N进制要领雷同——相除法
    // Do several (6) digits each time through the loop, so as to
    // minimize the calls to the very expensive emulated div.
    var radixToPower = fromNumber(pow_dbl(radix, 6), this.unsigned),
        rem = this;
    var result = '';
    while (true) {
        var remDiv = rem.div(radixToPower),
            intval = rem.sub(remDiv.mul(radixToPower)).toInt() >>> 0,
            digits = intval.toString(radix);
        rem = remDiv;
        if (rem.isZero())
            return digits + result;
        else {
            while (digits.length < 6)
                digits = '0' + digits;
            result = '' + digits + result;
        }
    }
};

上面这个函数的完成步骤恰好相反:

  1. 处置惩罚种种边界条件

  2. 假如Long型为一个负值,则转换为正值举行处置惩罚,假如Long型为0x80000000时,则对它举行了零丁处置惩罚。

  3. 在处置惩罚正值Long型为字符串时,操纵要领与我们数学中教的转换进制的相除法相似,详细操纵为:先除以须要转换的进制数,获得效果和余数,将效果从新作为被除数相除直到被除数为0,再将余数拼接起来即可。比方:18(10进制)转换为8进制时,操纵是:18 = 2 * 8 + 2; 2 = 0 * 8 + 2;,因而效果为0x22。只是,在此函数中,一次相除的是进制数的6次方,其他步骤是相似的。

  4. 经由历程上面的操纵获得字符串后返回即可。

Long型相加

在晓得了Long型的存储实质是运用上下各32位今后,Long型的运算实在就已了解了。我们只须要针对特定的操纵举行相对应的二进制操纵,那末我们就可以获得相对应的效果,下面的实例是Long型相加的操纵,我们简朴了解下:

LongPrototype.add = function add(addend) {
    if (!isLong(addend))
        addend = fromValue(addend);
    // 将每一个数字分红4个16比特的块,然后将这些块加起来

    var a48 = this.high >>> 16;
    var a32 = this.high & 0xFFFF;
    var a16 = this.low >>> 16;
    var a00 = this.low & 0xFFFF;

    var b48 = addend.high >>> 16;
    var b32 = addend.high & 0xFFFF;
    var b16 = addend.low >>> 16;
    var b00 = addend.low & 0xFFFF;

    var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
    c00 += a00 + b00;
    c16 += c00 >>> 16;
    c00 &= 0xFFFF;
    c16 += a16 + b16;
    c32 += c16 >>> 16;
    c16 &= 0xFFFF;
    c32 += a32 + b32;
    c48 += c32 >>> 16;
    c32 &= 0xFFFF;
    c48 += a48 + b48;
    c48 &= 0xFFFF;
    return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned);
};

经由历程上面的操纵我们就可以晓得,Long型的四则运算等操纵实在都是经由历程二进制和位运算来完成的。并没有我们设想中的那末神奇。

总结

实在,经由历程浏览Long.js库的源码你就会发明,在JavaScript中完成一个Long型并不难,或许照样一个听简朴的事变,不过主要的是我们能够设想不到这类的完成体式格局。因而,这个也证明了我们在思索一个题目题目的同时,我们也应当多从事变的实质来斟酌,如许就有能够获得解决方案。

附录

  • 我在Long.js的代码中添加了一些中文的解释,假如有须要可以到我folk的堆栈举行浏览进修。

    原文作者:hjava
    原文地址: https://segmentfault.com/a/1190000009599204
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞