269 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			269 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | //     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(' ') + ']'; | ||
|  |   } | ||
|  | }; |