How to Implement a Long Type-Long.js Source Code Learning and Analysis in JavaScript

Posted by mchannel on Wed, 26 Jun 2019 21:38:40 +0200

background

Because of the use of WebSocket's custom binary protocol in the project, it is necessary to convert the binary to Long type defined in the back-end service. The Number type in JavaScript, for its own reasons, does not fully represent Long-type numbers, so we need to store Long-type values in other ways.

target

In GitHub, there is an implementation that stores Long-type objects in JavaScript. Specific code can Poke this . Next, we'll show you how to implement a Long in JavaScript by simply explaining the implementation of this library. If you understand this implementation principle, it's similar to implementing a Long Long Long in JavaScript or any other type of method.

Specific realization

In fact, Long's implementation is very simple. Now we just need to return to the nature of the computer. In computer, in fact, the storage is 01 strings. For example, Int takes up 4 bytes (let's take 32-bit operating system for example), while Long takes up 8 bytes.

In storage, we only need to store data through binary, and then operate on binary in operation.

Let's briefly introduce Long's representative operations and ideas.

Rough steps

data storage

In Long object, we use high 32 bits and low 32 bits, and add a symbol bit judgment value to store data. The format is as follows:

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

By storing high and low bits, two Number s are used to represent both high and low bits of a Long type, thus satisfying the length requirement of the numerical value.

Converting to Long Type

At present, we only introduce a string-based conversion of data from String to Long. Other transformations, such as Number to Long, are similar. We will not go into more details.

First look at the implementation function:

function fromString(str, unsigned, radix) {
    // Handling exceptions
    if (str.length === 0)
        throw Error('empty string');
        
    //In case of zero
    if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity")
        return ZERO;
        
    //Handling cases with only two parameters
    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) {
        // Conversion to Value Processing
        return fromString(str.substring(1), unsigned, radix).neg();
    }
    
    // If the length exceeds 8 bits, the higher bits are processed first, then the higher bits are multiplied directly into the 8-th power of the system, then the lower 8 bits are processed, and the last 8 bits are recycled until the last 8 bits are processed.
    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;
}

Let's briefly talk about the implementation of this function.

  1. Data are processed abnormally, and some boundary conditions are excluded.

  2. If the string is a value with a "-" sign, it is converted to a positive value for processing.

  3. If the string is a regular Long value, it is first processed from the front 8 bits and converted to a Long value by the specified binary.

  4. Processing the next 8 bits, and multiplying the previous results into the eighth power of the system, that is, the merging of high status numbers. For example: 18 = 1 * 10 ^ 1 + 8.

  5. The operation above the loop is completed until the remaining string length is less than 8, resulting in the Long type after conversion.

Convert to String

The conversion of Long type to string is almost the same as the conversion of string to Long type. It is almost the opposite process.

LongPrototype.toString = function toString(radix) {
    radix = radix || 10;
    if (radix < 2 || 36 < radix)
        throw RangeError('radix');
    if (this.isZero())
        return '0';
    //If the value is negative, the Unsigned Long value will never be negative.
    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);
    }

    //Each time 6 bits are processed, the processing method is similar to that of string conversion, and the method of decimal conversion to N-decimal conversion in mathematics is the same - division method.
    // 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;
        }
    }
};

The implementation steps of the above function are just the opposite:

  1. Dealing with various boundary conditions

  2. If the Long type is a negative value, it is converted to a positive value for processing, and if the Long type is 0x80000000, it is treated separately.

  3. When dealing with the positive Long type as a string, the operation method is similar to the division method of the converted binary system taught in our mathematics. The specific operation is: divide the processed number that needs to be converted first, get the result and the remainder, divide the result again as the dividend until the dividend is 0, and then stitch the remainder together. For example, when 18 (10-ary) is converted to 8-ary, the operation is: 18 = 2 * 8 + 2; 2 = 0 * 8 + 2; so the result is 0x22. However, in this function, the first division is the sixth power of the decimal number, and the rest of the steps are similar.

  4. Get the string through the above operation and return it.

Long-type addition

After knowing that the storage essence of Long type is to use 32 bits of high and low, the operation of Long type has already been understood. We only need to do binary operations for specific operations, then we can get the corresponding results. The following example is Long-type addition operation. Let's have a brief understanding of it.

LongPrototype.add = function add(addend) {
    if (!isLong(addend))
        addend = fromValue(addend);
    // Divide each number into four 16-bit blocks and add them up.

    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);
};

Through the above operations, we can know that the four operations of Long type are actually realized by binary and bitwise operations. It's not as mysterious as we thought.

summary

In fact, by reading the source code of the Long.js library, you will find that it is not difficult to implement a Long type in JavaScript, maybe it is a simple thing to listen to, but the important thing is that we may not imagine this way of implementation. Therefore, this also proves that while we are thinking about a problem, we should also think more about the nature of things, so that we may get a solution.

appendix

  • I added some Chinese comments to Long.js code, and if you need to, you can go to my folk s. Warehouse Read and learn.

Topics: Javascript REST github less