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