You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
7.9 KiB
268 lines
7.9 KiB
// 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 |
|
*/ |
|
|
|
// Useful masks and values for bit twiddling |
|
var MASK31 = 0x7fffffff, VAL31 = 0x80000000; |
|
var MASK32 = 0xffffffff, VAL32 = 0x100000000; |
|
|
|
// Map for converting hex octets to strings |
|
var _HEX = []; |
|
for (var i = 0; i < 256; i++) { |
|
_HEX[i] = (i > 0xF ? '' : '0') + i.toString(16); |
|
} |
|
|
|
// |
|
// Int64 |
|
// |
|
|
|
/** |
|
* Constructor accepts any of the following argument types: |
|
* |
|
* new Int64(buffer[, offset=0]) - Existing Buffer with byte offset |
|
* new Int64(Uint8Array[, offset=0]) - Existing Uint8Array with a byte offset |
|
* new Int64(string) - Hex string (throws if n is outside int64 range) |
|
* new Int64(number) - Number (throws if n is outside int64 range) |
|
* new Int64(hi, lo) - Raw bits as two 32-bit values |
|
*/ |
|
var Int64 = module.exports = function(a1, a2) { |
|
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); |
|
} |
|
}; |
|
|
|
|
|
// Max integer value that JS can accurately represent |
|
Int64.MAX_INT = Math.pow(2, 53); |
|
|
|
// Min integer value that JS can accurately represent |
|
Int64.MIN_INT = -Math.pow(2, 53); |
|
|
|
Int64.prototype = { |
|
|
|
constructor: Int64, |
|
|
|
/** |
|
* Do in-place 2's compliment. See |
|
* http://en.wikipedia.org/wiki/Two's_complement |
|
*/ |
|
_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; |
|
} |
|
}, |
|
|
|
/** |
|
* Set the value. Takes any of the following arguments: |
|
* |
|
* setValue(string) - A hexidecimal string |
|
* setValue(number) - Number (throws if n is outside int64 range) |
|
* setValue(hi, lo) - Raw bits as two 32-bit values |
|
*/ |
|
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 JS number. |
|
* |
|
* WARNING: Do not expect this value to be accurate to integer precision for |
|
* large (positive or negative) numbers! |
|
* |
|
* @param allowImprecise If true, no check is performed to verify the |
|
* returned value is accurate to integer precision. If false, imprecise |
|
* numbers (very large positive or negative numbers) will be forced to +/- |
|
* Infinity. |
|
*/ |
|
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 JS Number. Returns +/-Infinity for values that can't be |
|
* represented to integer precision. |
|
*/ |
|
valueOf: function() { |
|
return this.toNumber(false); |
|
}, |
|
|
|
/** |
|
* Return string value |
|
* |
|
* @param radix Just like Number#toString()'s radix |
|
*/ |
|
toString: function(radix) { |
|
return this.valueOf().toString(radix || 10); |
|
}, |
|
|
|
/** |
|
* Return a string showing the buffer octets, with MSB on the left. |
|
* |
|
* @param 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 the int64's 8 bytes in a buffer. |
|
* |
|
* @param {bool} [rawBuffer=false] If no offset and this is true, 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 number indicating whether this comes before or after or is the |
|
* same as the other in sort order. |
|
* |
|
* @param {Int64} other Other Int64 to compare. |
|
*/ |
|
compare: function(other) { |
|
|
|
// If sign bits differ ... |
|
if ((this.buffer[this.offset] & 0x80) != (other.buffer[other.offset] & 0x80)) { |
|
return other.buffer[other.offset] - this.buffer[this.offset]; |
|
} |
|
|
|
// otherwise, compare bytes lexicographically |
|
for (var i = 0; i < 8; i++) { |
|
if (this.buffer[this.offset+i] !== other.buffer[other.offset+i]) { |
|
return this.buffer[this.offset+i] - other.buffer[other.offset+i]; |
|
} |
|
} |
|
return 0; |
|
}, |
|
|
|
/** |
|
* Returns a boolean indicating if this integer is equal to other. |
|
* |
|
* @param {Int64} other Other Int64 to compare. |
|
*/ |
|
equals: function(other) { |
|
return this.compare(other) === 0; |
|
}, |
|
|
|
/** |
|
* Pretty output in console.log |
|
*/ |
|
inspect: function() { |
|
return '[Int64 value:' + this + ' octets:' + this.toOctetString(' ') + ']'; |
|
} |
|
};
|
|
|