Int64.js
1 // Int64.js 2 // 3 // Copyright (c) 2012 Robert Kieffer 4 // MIT License - http://opensource.org/licenses/mit-license.php 5 6 /** 7 * Support for handling 64-bit int numbers in Javascript (node.js) 8 * 9 * JS Numbers are IEEE-754 binary double-precision floats, which limits the 10 * range of values that can be represented with integer precision to: 11 * 12 * 2^^53 <= N <= 2^53 13 * 14 * Int64 objects wrap a node Buffer that holds the 8-bytes of int64 data. These 15 * objects operate directly on the buffer which means that if they are created 16 * using an existing buffer then setting the value will modify the Buffer, and 17 * vice-versa. 18 * 19 * Internal Representation 20 * 21 * The internal buffer format is Big Endian. I.e. the most-significant byte is 22 * at buffer[0], the least-significant at buffer[7]. For the purposes of 23 * converting to/from JS native numbers, the value is assumed to be a signed 24 * integer stored in 2's complement form. 25 * 26 * For details about IEEE-754 see: 27 * http://en.wikipedia.org/wiki/Double_precision_floating-point_format 28 */ 29 30 // Useful masks and values for bit twiddling 31 var MASK31 = 0x7fffffff, VAL31 = 0x80000000; 32 var MASK32 = 0xffffffff, VAL32 = 0x100000000; 33 34 // Map for converting hex octets to strings 35 var _HEX = []; 36 for (var i = 0; i < 256; i++) { 37 _HEX[i] = (i > 0xF ? '' : '0') + i.toString(16); 38 } 39 40 // 41 // Int64 42 // 43 44 /** 45 * Constructor accepts any of the following argument types: 46 * 47 * new Int64(buffer[, offset=0]) - Existing Buffer with byte offset 48 * new Int64(Uint8Array[, offset=0]) - Existing Uint8Array with a byte offset 49 * new Int64(string) - Hex string (throws if n is outside int64 range) 50 * new Int64(number) - Number (throws if n is outside int64 range) 51 * new Int64(hi, lo) - Raw bits as two 32-bit values 52 */ 53 var Int64 = module.exports = function(a1, a2) { 54 if (a1 instanceof Buffer) { 55 this.buffer = a1; 56 this.offset = a2 || 0; 57 } else if (Object.prototype.toString.call(a1) == '[object Uint8Array]') { 58 // Under Browserify, Buffers can extend Uint8Arrays rather than an 59 // instance of Buffer. We could assume the passed in Uint8Array is actually 60 // a buffer but that won't handle the case where a raw Uint8Array is passed 61 // in. We construct a new Buffer just in case. 62 this.buffer = new Buffer(a1); 63 this.offset = a2 || 0; 64 } else { 65 this.buffer = this.buffer || new Buffer(8); 66 this.offset = 0; 67 this.setValue.apply(this, arguments); 68 } 69 }; 70 71 72 // Max integer value that JS can accurately represent 73 Int64.MAX_INT = Math.pow(2, 53); 74 75 // Min integer value that JS can accurately represent 76 Int64.MIN_INT = -Math.pow(2, 53); 77 78 Int64.prototype = { 79 80 constructor: Int64, 81 82 /** 83 * Do in-place 2's compliment. See 84 * http://en.wikipedia.org/wiki/Two's_complement 85 */ 86 _2scomp: function() { 87 var b = this.buffer, o = this.offset, carry = 1; 88 for (var i = o + 7; i >= o; i--) { 89 var v = (b[i] ^ 0xff) + carry; 90 b[i] = v & 0xff; 91 carry = v >> 8; 92 } 93 }, 94 95 /** 96 * Set the value. Takes any of the following arguments: 97 * 98 * setValue(string) - A hexidecimal string 99 * setValue(number) - Number (throws if n is outside int64 range) 100 * setValue(hi, lo) - Raw bits as two 32-bit values 101 */ 102 setValue: function(hi, lo) { 103 var negate = false; 104 if (arguments.length == 1) { 105 if (typeof(hi) == 'number') { 106 // Simplify bitfield retrieval by using abs() value. We restore sign 107 // later 108 negate = hi < 0; 109 hi = Math.abs(hi); 110 lo = hi % VAL32; 111 hi = hi / VAL32; 112 if (hi > VAL32) throw new RangeError(hi + ' is outside Int64 range'); 113 hi = hi | 0; 114 } else if (typeof(hi) == 'string') { 115 hi = (hi + '').replace(/^0x/, ''); 116 lo = hi.substr(-8); 117 hi = hi.length > 8 ? hi.substr(0, hi.length - 8) : ''; 118 hi = parseInt(hi, 16); 119 lo = parseInt(lo, 16); 120 } else { 121 throw new Error(hi + ' must be a Number or String'); 122 } 123 } 124 125 // Technically we should throw if hi or lo is outside int32 range here, but 126 // it's not worth the effort. Anything past the 32'nd bit is ignored. 127 128 // Copy bytes to buffer 129 var b = this.buffer, o = this.offset; 130 for (var i = 7; i >= 0; i--) { 131 b[o+i] = lo & 0xff; 132 lo = i == 4 ? hi : lo >>> 8; 133 } 134 135 // Restore sign of passed argument 136 if (negate) this._2scomp(); 137 }, 138 139 /** 140 * Convert to a native JS number. 141 * 142 * WARNING: Do not expect this value to be accurate to integer precision for 143 * large (positive or negative) numbers! 144 * 145 * @param allowImprecise If true, no check is performed to verify the 146 * returned value is accurate to integer precision. If false, imprecise 147 * numbers (very large positive or negative numbers) will be forced to +/- 148 * Infinity. 149 */ 150 toNumber: function(allowImprecise) { 151 var b = this.buffer, o = this.offset; 152 153 // Running sum of octets, doing a 2's complement 154 var negate = b[o] & 0x80, x = 0, carry = 1; 155 for (var i = 7, m = 1; i >= 0; i--, m *= 256) { 156 var v = b[o+i]; 157 158 // 2's complement for negative numbers 159 if (negate) { 160 v = (v ^ 0xff) + carry; 161 carry = v >> 8; 162 v = v & 0xff; 163 } 164 165 x += v * m; 166 } 167 168 // Return Infinity if we've lost integer precision 169 if (!allowImprecise && x >= Int64.MAX_INT) { 170 return negate ? -Infinity : Infinity; 171 } 172 173 return negate ? -x : x; 174 }, 175 176 /** 177 * Convert to a JS Number. Returns +/-Infinity for values that can't be 178 * represented to integer precision. 179 */ 180 valueOf: function() { 181 return this.toNumber(false); 182 }, 183 184 /** 185 * Return string value 186 * 187 * @param radix Just like Number#toString()'s radix 188 */ 189 toString: function(radix) { 190 return this.valueOf().toString(radix || 10); 191 }, 192 193 /** 194 * Return a string showing the buffer octets, with MSB on the left. 195 * 196 * @param sep separator string. default is '' (empty string) 197 */ 198 toOctetString: function(sep) { 199 var out = new Array(8); 200 var b = this.buffer, o = this.offset; 201 for (var i = 0; i < 8; i++) { 202 out[i] = _HEX[b[o+i]]; 203 } 204 return out.join(sep || ''); 205 }, 206 207 /** 208 * Returns the int64's 8 bytes in a buffer. 209 * 210 * @param {bool} [rawBuffer=false] If no offset and this is true, return the internal buffer. Should only be used if 211 * you're discarding the Int64 afterwards, as it breaks encapsulation. 212 */ 213 toBuffer: function(rawBuffer) { 214 if (rawBuffer && this.offset === 0) return this.buffer; 215 216 var out = new Buffer(8); 217 this.buffer.copy(out, 0, this.offset, this.offset + 8); 218 return out; 219 }, 220 221 /** 222 * Copy 8 bytes of int64 into target buffer at target offset. 223 * 224 * @param {Buffer} targetBuffer Buffer to copy into. 225 * @param {number} [targetOffset=0] Offset into target buffer. 226 */ 227 copy: function(targetBuffer, targetOffset) { 228 this.buffer.copy(targetBuffer, targetOffset || 0, this.offset, this.offset + 8); 229 }, 230 231 /** 232 * Returns a number indicating whether this comes before or after or is the 233 * same as the other in sort order. 234 * 235 * @param {Int64} other Other Int64 to compare. 236 */ 237 compare: function(other) { 238 239 // If sign bits differ ... 240 if ((this.buffer[this.offset] & 0x80) != (other.buffer[other.offset] & 0x80)) { 241 return other.buffer[other.offset] - this.buffer[this.offset]; 242 } 243 244 // otherwise, compare bytes lexicographically 245 for (var i = 0; i < 8; i++) { 246 if (this.buffer[this.offset+i] !== other.buffer[other.offset+i]) { 247 return this.buffer[this.offset+i] - other.buffer[other.offset+i]; 248 } 249 } 250 return 0; 251 }, 252 253 /** 254 * Returns a boolean indicating if this integer is equal to other. 255 * 256 * @param {Int64} other Other Int64 to compare. 257 */ 258 equals: function(other) { 259 return this.compare(other) === 0; 260 }, 261 262 /** 263 * Pretty output in console.log 264 */ 265 inspect: function() { 266 return '[Int64 value:' + this + ' octets:' + this.toOctetString(' ') + ']'; 267 } 268 };