T-PWK/biguint-format

View on GitHub
index.js

Summary

Maintainability
A
1 hr
Test Coverage
/**
 * Support arbitrary lenght unsigned integer number convertion to string representation 
 * in the specified radix (base) in Node.js.
 *
 * JavaScript Numbers are IEEE-754 binary double-precision floats, which limits the
 * range of values that can be represented with integer precision to:
 *
 * 2^53 <= N <= 2^53
 *
 * For more details about IEEE-754 see: http://en.wikipedia.org/wiki/IEEE_floating_point
 */
 /*jslint bitwise: true */
(function () {
    "use strict";

    var FORMATS = {
        'dec': toDecimalString,
        'hex': toHexString,
        'bin': toBinaryString,
        'oct': toOctetString
    };

    module.exports = function (buffer, base, options) {
        base = base || 'dec';
        var buf = _toBuffer(buffer), format = FORMATS[base];

        if (format) {
            return format(buf, options);
        }
    };

    function createBuffer(size) {
        var buf;
        if (Buffer.alloc) {
            buf = Buffer.alloc(size);
        } else {
            buf = new Buffer(size);
            buf.fill(0);
        }
        return buf;
    }

    function createBufferFrom(object) {
        var buf;
        if (Buffer.from) {
            buf = Buffer.from(object);
        } else {
            buf = new Buffer(object);
        }
        return buf;
    }

    function toDecimalString(buffer, options) {
        options = options || {};
        var bits = buffer.length * 8,                               // number of bits in the buffer
            lastBit = buffer.length - 1,                            // last bit index
            digits = createBuffer(Math.floor(bits / 3 + 1 + 1)),    // digits buffer
            lastDigit = digits.length - 1, carry;                   // last digit index, digit index, carry flag

        // reset digits buffer
        digits.fill(0);

        // reverse buffer if not in LE format
        if ((options.format || 'BE') !== 'LE') {
            _reverseBuffer(buffer);
        }

        for (var i = 0; i < bits; i++) {
            carry = buffer[lastBit] >= 0x80;

            _leftShift(buffer);  // shift buffer bits

            for (var d = lastDigit; d >= 0; d--) {
                digits[d] += digits[d] + (carry ? 1 : 0);
                carry = (digits[d] > 9);
                if (carry) {
                    digits[d] -= 10;
                }
            }
        }

        // get rid of leading 0's; reuse d for the first non-zero value index
        var idx = _lastHeadIndex(digits, 0);

        // if there are only 0's use the last digit
        idx = idx >= 0 ? idx : lastDigit;

        // convert numbers to ascii digits
        _toAsciiDigits(digits, idx);

        return _pad(
            _split(digits.toString('ascii', idx), options.groupsize, options.delimiter),
            '', options.padstr, options.size);
    }

    function toBinaryString(buffer, options) {
        options = options || {};
        var digits = new Array(buffer.length),
            size = options.groupsize || -1, num,
            prefix = options.prefix || '',
            output;

        if ((options.format || 'BE') !== 'BE') {
            _reverseBuffer(buffer);
        }

        for (var i = 0; i < buffer.length; i++) {
            num = buffer[i].toString(2);
            digits[i] = '00000000'.slice(0, 8 - num.length) + buffer[i].toString(2);
        }

        output = digits.join('');

        if (options.trim) {
            output = output.substr(output.indexOf('1'));
        }

        if (size > 0) {
            output = _split(output, size, options.delimiter);
        }

        return prefix + _pad(output, prefix, options.padstr, options.size);
    }

    /*
     * Converts given input (node Buffer or array of bytes) to hexadecimal string 0xDDDD where D is [0-9a-f].
     * All leading 0's are stripped out i.e. [0x00, 0x00, 0x00, 0x01] -> '0x1'
     */
    function toHexString(buffer, options) {
        options = options || {};
        var prefix = options.prefix || '', digits, idx;

        if ((options.format || 'BE') !== 'BE') {
            _reverseBuffer(buffer);
        }

        digits = buffer.toString('hex');
        idx = _lastHeadIndex(digits, '0');

        // if there are only 0's use the last digit
        idx = idx >= 0 ? idx : digits.length - 1;

        return prefix + _pad(
            _split(digits.slice(idx), options.groupsize, options.delimiter), prefix, options.padstr, options.size);
    }

    function toOctetString(buffer, options) {
        options = options || {};
        var shifts = Math.floor(buffer.length * 8 / 3),
            lastIdx = buffer.length - 1,
            digits = createBuffer(shifts),
            prefix = options.prefix || '', idx;

        digits.fill(0); // reset digits buffer
        if ((options.format || 'BE') !== 'BE') {
            _reverseBuffer(buffer);
        }

        for (var i = digits.length - 1; i >= 0; i--) {
            digits[i] = buffer[lastIdx] & 0x7;

            // right shift buffer by 3 bits
            _rightShift(buffer);
            _rightShift(buffer);
            _rightShift(buffer);
        }

        // get rid of leading 0's; reuse d for the first non-zero value index
        idx = _lastHeadIndex(digits, 0);
        idx = idx >= 0 ? idx : lastIdx;

        // convert numbers to ascii digits
        _toAsciiDigits(digits, idx);

        return prefix + _pad(
            _split(digits.toString('ascii', idx), options.groupsize, options.delimiter), prefix, options.padstr, options.size);
    }

    function _split(string, size, delim) {
        if (typeof delim === 'undefined') {
            delim = ' ';
        }
        if (typeof string !== 'undefined' && +size > 0) {
            string = string.replace(new RegExp('(.)(?=(.{' + +size + '})+(?!.))', 'g'), "$1" + delim);
        }
        return string;
    }

    function _pad(str, prefix, pad, size) {
        if ('undefined' === typeof pad || 'undefined' === typeof size || pad.length === 0 || str.length + prefix.length >= size) {
            return str;
        }
        var padlen = size - str.length - prefix.length;
        return new Array(Math.ceil(padlen / pad.length) + 1).join(pad).substr(0, padlen) + str;
    }

    function _toAsciiDigits(buffer, offset) {
        for (var i = offset; i < buffer.length; i++) {
            buffer[i] += 48;
        }
    }

    /*
     * Finds last head index of the given value in the given buffer
     * Otherwise it returns -1.
     */
    function _lastHeadIndex(buffer, value) {
        for (var i = 0; i < buffer.length; i++) {
            if (buffer[i] !== value) {
                return i;
            }
        }
        return -1;
    }

    /*
     * Checks type of data and perform conversion if necessary
     */
    function _toBuffer(buffer) {
        var _buffer, nums;

        if (Buffer.isBuffer(buffer) || Array.isArray(buffer)) {
            _buffer = createBufferFrom(buffer);
        }
        else if (typeof buffer === 'string') {
            nums = buffer.replace(/^0x/i, '').match(/.{1,2}(?=(..)+(?!.))|..?$/g);
            _buffer = createBuffer(nums.length);

            for (var i = nums.length - 1; i >= 0; i--) {
                _buffer.writeUInt8(parseInt(nums[i], 16), i);
            }
        }

        return _buffer;
    }

    /*
     * Performs byte order reverse
     */
    function _reverseBuffer(buffer) {
        var tmp, len = buffer.length - 1, half = Math.floor(buffer.length / 2);
        for (var i = len; i >= half; i--) {
            tmp = buffer[i];
            buffer[i] = buffer[len - i];
            buffer[len - i] = tmp;
        }
    }

    /*
     * Performs buffer left bits shift
     */
    function _leftShift(buffer) {
        var carry;
        for (var i = buffer.length; i >= 0; i--) {
            carry = (buffer[i] & 0x80) !== 0;
            buffer[i] = (buffer[i] << 1) & 0xFF;
            if (carry && i >= 0) {
                buffer[i + 1] |= 0x01;
            }
        }
    }

    /*
     * Performs buffer right bits shift
     */
    function _rightShift(buffer) {
        var carry, prevcarry;
        for (var i = 0; i < buffer.length; i++) {
            carry = prevcarry;
            prevcarry = (buffer[i] & 0x1) !== 0;
            buffer[i] >>= 1;
            if (carry && i > 0) {
                buffer[i] |= 0x80;
            }
        }
    }

}());