Int64.js 7.9 KB

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