// Int64.js
//
// Copyright (c) 2012 Robert Kieffer
// MIT License - http://opensource.org/licenses/mit-license.php
/*
* Support for handling 64-bit int numbers in Javascript (node.js)
*
* JS 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
*
* Int64 objects wrap a node Buffer that holds the 8-bytes of int64 data. These
* objects operate directly on the buffer which means that if they are created
* using an existing buffer then setting the value will modify the Buffer, and
* vice-versa.
*
* Internal Representation
*
* The internal buffer format is Big Endian. I.e. the most-significant byte is
* at buffer[0], the least-significant at buffer[7]. For the purposes of
* converting to/from JS native numbers, the value is assumed to be a signed
* integer stored in 2's complement form.
*
* For details about IEEE-754 see:
* http://en.wikipedia.org/wiki/Double_precision_floating-point_format
*/
var MASK31 = 0x7fffffff, VAL31 = 0x80000000;
var MASK32 = 0xffffffff, VAL32 = 0x100000000;
var _HEX = [];
for (var i = 0; i < 256; i++) {
_HEX[i] = (i > 0xF ? '' : '0') + i.toString(16);
}
//
// Int64
//
/**
* Javascript Numbers are IEEE-754 binary double-precision floats, which limits
* the range of values that can be represented with integer precision to:
* <pre>
* -(2^53) <= N <= 2^53
* </pre>
* Int64 objects wrap a node Buffer that holds the 8-bytes of int64 data. These
* objects operate directly on the buffer which means that if they are created
* using an existing buffer then setting the value will modify the Buffer, and
* vice-versa.
* <p/>
* <h4>Internal Representation</h4>
*
* The internal buffer byte order is big-endian (i.e. the most-significant byte
* is at {@link Int64#buffer buffer}[{@link Int64#offset offset}+0], the least
* significant at {@link Int64#buffer buffer}[{@link Int64#offset offset}+7]).
* For the purposes of converting to/from Javascript native numbers, the value
* is assumed to be a signed integer stored in 2's complement form.
* <p/>
* For details about IEEE-754 see:
* <a href="http://en.wikipedia.org/wiki/Double-precision_floating-point_format">
* Double-precision floating-point format</a>
*
* Constructor accepts any of the following argument types:
* <p/>
* <table>
* <tr>
* <td>new Int64(buffer[, offset=0]) </td>
* <td>Buffer, with byte offset, holding 64-bit, big-endian encoded integer</td>
* </tr>
* <tr>
* <td>new Int64(Uint8Array[, offset=0]) </td>
* <td>Uint8Array, with byte offset, holding 64-bit, big-endian encoded integer</td>
* </tr>
* <tr>
* <td>new Int64(string) </td>
* <td>Hex string (throws if n is outside int64 range)</td>
* </tr>
* <tr>
* <td>new Int64(number) </td>
* <td>Number (throws if n is outside int64 range)</td>
* </tr>
* <tr>
* <td>new Int64(high, low) </td>
* <td>Raw bits as two 32-bit values</td>
* </tr>
* </table>
* @class
* @classdesc Holds a 64-bit integer.
*/
var Int64 = module.exports = function(a1, a2) {
/**
* Holds 8 byte integer in big-endian order.
* @name Int64#buffer
* @type Buffer
*/
/**
* Offset within {@link Int64#buffer buffer} of beginning of integer bytes.
* @name Int64#offset
* @type Number
*/
if (a1 instanceof Buffer) {
this.buffer = a1;
this.offset = a2 || 0;
} else if (Object.prototype.toString.call(a1) == '[object Uint8Array]') {
// Under Browserify, Buffers can extend Uint8Arrays rather than an
// instance of Buffer. We could assume the passed in Uint8Array is actually
// a buffer but that won't handle the case where a raw Uint8Array is passed
// in. We construct a new Buffer just in case.
this.buffer = new Buffer(a1);
this.offset = a2 || 0;
} else {
this.buffer = this.buffer || new Buffer(8);
this.offset = 0;
this.setValue.apply(this, arguments);
}
};
/**
* Maximum integer value that JS can accurately represent (2^52).
* @type {Number}
* @constant
* @default
*/
Int64.MAX_INT = Math.pow(2, 53);
/**
* Maximum integer value that JS can accurately represent -(2^52).
* @type {Number}
* @constant
* @default
*/
Int64.MIN_INT = -Math.pow(2, 53);
Int64.prototype = {
/**
* Performs in-place two's compliment of 8 byte number stored in
* {@link Int64#buffer buffer}, starting at {@link Int64#offset offset}. See
* <a href="http://en.wikipedia.org/wiki/Two's_complement">Two's complement</a>.
*/
_2scomp: function() {
var b = this.buffer, o = this.offset, carry = 1;
for (var i = o + 7; i >= o; i--) {
var v = (b[i] ^ 0xff) + carry;
b[i] = v & 0xff;
carry = v >> 8;
}
},
/**
* Sets the integer value. Takes any of the following arguments:
* <p/>
* <table>
* <tr><td>setValue(string) </td><td>- A hexidecimal string</td></tr>
* <tr><td>setValue(number) </td><td>- Number (throws if n is outside int64 range)</td></tr>
* <tr><td>setValue(high, low) </td><td>- Raw bits as two 32-bit values</td></tr>
* </table>
*/
setValue: function(hi, lo) {
var negate = false;
if (arguments.length == 1) {
if (typeof(hi) == 'number') {
// Simplify bitfield retrieval by using abs() value. We restore sign
// later
negate = hi < 0;
hi = Math.abs(hi);
lo = hi % VAL32;
hi = hi / VAL32;
if (hi > VAL32) throw new RangeError(hi + ' is outside Int64 range');
hi = hi | 0;
} else if (typeof(hi) == 'string') {
hi = (hi + '').replace(/^0x/, '');
lo = hi.substr(-8);
hi = hi.length > 8 ? hi.substr(0, hi.length - 8) : '';
hi = parseInt(hi, 16);
lo = parseInt(lo, 16);
} else {
throw new Error(hi + ' must be a Number or String');
}
}
// Technically we should throw if hi or lo is outside int32 range here, but
// it's not worth the effort. Anything past the 32'nd bit is ignored.
// Copy bytes to buffer
var b = this.buffer, o = this.offset;
for (var i = 7; i >= 0; i--) {
b[o+i] = lo & 0xff;
lo = i == 4 ? hi : lo >>> 8;
}
// Restore sign of passed argument
if (negate) this._2scomp();
},
/**
* Convert to a native Javascript number.
* <p/>
* WARNING: Do not expect this value to be accurate to integer precision for
* large (positive or negative) numbers!
* @param {Boolean} allowImprecise If <i>true</i>, no check is performed to
* verify the returned value is accurate to integer precision. If
* <i>false</i>, imprecise numbers (very large positive or negative numbers)
* will be forced to +/-Infinity.
* @return {Number} Javascript number representing the integer, or +/-Infinity
* if not enough precision available and <code>allowImprecise</code> is set to
* <i>true</i>.
*/
toNumber: function(allowImprecise) {
var b = this.buffer, o = this.offset;
// Running sum of octets, doing a 2's complement
var negate = b[o] & 0x80, x = 0, carry = 1;
for (var i = 7, m = 1; i >= 0; i--, m *= 256) {
var v = b[o+i];
// 2's complement for negative numbers
if (negate) {
v = (v ^ 0xff) + carry;
carry = v >> 8;
v = v & 0xff;
}
x += v * m;
}
// Return Infinity if we've lost integer precision
if (!allowImprecise && x >= Int64.MAX_INT) {
return negate ? -Infinity : Infinity;
}
return negate ? -x : x;
},
/**
* Convert to a Javascript Number. Does the conversion with a call to
* {@link Int64#toNumber toNumber}<code>(false)</code>.
* @see {@link Int64#toNumber toNumber()}
* @return {Number} Javascript number representing the integer, or +/-Infinity
* if not enough precision to hold the integer.
*/
valueOf: function() {
return this.toNumber(false);
},
/**
* Return string representation of integer. Conversion is performed with
* {@link Int64#valueOf valueOf()}<code>.toString(radix)</code>.
*
* @param {Number} [radix=10] Just like <code>Number.toString()</code> radix
*/
toString: function(radix) {
return this.valueOf().toString(radix || 10);
},
/**
* Return a string showing the buffer octets, with MSB on the left.
*
* @param {String} sep Separator string. default is '' (empty string)
*/
toOctetString: function(sep) {
var out = new Array(8);
var b = this.buffer, o = this.offset;
for (var i = 0; i < 8; i++) {
out[i] = _HEX[b[o+i]];
}
return out.join(sep || '');
},
/**
* Returns newly allocated Buffer holding integer encoded as 8 bytes in
* big-endian order.
*
* @param {bool} [rawBuffer=false] If no offset and this is <i>true</i>,
* return the internal buffer. Should only be used if you're discarding the
* Int64 afterwards, as it breaks encapsulation.
*/
toBuffer: function(rawBuffer) {
if (rawBuffer && this.offset === 0) return this.buffer;
var out = new Buffer(8);
this.buffer.copy(out, 0, this.offset, this.offset + 8);
return out;
},
/**
* Copy 8 bytes of int64 into target buffer at target offset.
*
* @param {Buffer} targetBuffer Buffer to copy into.
* @param {number} [targetOffset=0] Offset into target buffer.
*/
copy: function(targetBuffer, targetOffset) {
this.buffer.copy(targetBuffer, targetOffset || 0, this.offset, this.offset + 8);
},
/**
* Returns a string represenation of the Int64 object.
* <pre>
* [Int64 value:1006414150992247 octets:00 03 93 54 0d f7 81 77]
* </pre>
* @return {String} String representation of object
*/
inspect: function() {
return '[Int64 value:' + this + ' octets:' + this.toOctetString(' ') + ']';
}
};