| 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 | };
|
|---|