int64.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. /**
  2. * @fileoverview Protobufs Int64 representation.
  3. */
  4. goog.module('protobuf.Int64');
  5. const Long = goog.require('goog.math.Long');
  6. const {assert} = goog.require('goog.asserts');
  7. /**
  8. * A container for protobufs Int64/Uint64 data type.
  9. * @final
  10. */
  11. class Int64 {
  12. /** @return {!Int64} */
  13. static getZero() {
  14. return ZERO;
  15. }
  16. /** @return {!Int64} */
  17. static getMinValue() {
  18. return MIN_VALUE;
  19. }
  20. /** @return {!Int64} */
  21. static getMaxValue() {
  22. return MAX_VALUE;
  23. }
  24. /**
  25. * Constructs a Int64 given two 32 bit numbers
  26. * @param {number} lowBits
  27. * @param {number} highBits
  28. * @return {!Int64}
  29. */
  30. static fromBits(lowBits, highBits) {
  31. return new Int64(lowBits, highBits);
  32. }
  33. /**
  34. * Constructs an Int64 from a signed 32 bit number.
  35. * @param {number} value
  36. * @return {!Int64}
  37. */
  38. static fromInt(value) {
  39. // TODO: Use our own checking system here.
  40. assert(value === (value | 0), 'value should be a 32-bit integer');
  41. // Right shift 31 bits so all high bits are equal to the sign bit.
  42. // Note: cannot use >> 32, because (1 >> 32) = 1 (!).
  43. const signExtendedHighBits = value >> 31;
  44. return new Int64(value, signExtendedHighBits);
  45. }
  46. /**
  47. * Constructs an Int64 from a number (over 32 bits).
  48. * @param {number} value
  49. * @return {!Int64}
  50. */
  51. static fromNumber(value) {
  52. if (value > 0) {
  53. return new Int64(value, value / TWO_PWR_32_DBL);
  54. } else if (value < 0) {
  55. return negate(-value, -value / TWO_PWR_32_DBL);
  56. }
  57. return ZERO;
  58. }
  59. /**
  60. * Construct an Int64 from a signed decimal string.
  61. * @param {string} value
  62. * @return {!Int64}
  63. */
  64. static fromDecimalString(value) {
  65. // TODO: Use our own checking system here.
  66. assert(value.length > 0);
  67. // The basic Number conversion loses precision, but we can use it for
  68. // a quick validation that the format is correct and it is an integer.
  69. assert(Math.floor(Number(value)).toString().length == value.length);
  70. return decimalStringToInt64(value);
  71. }
  72. /**
  73. * Construct an Int64 from a signed hexadecimal string.
  74. * @param {string} value
  75. * @return {!Int64}
  76. */
  77. static fromHexString(value) {
  78. // TODO: Use our own checking system here.
  79. assert(value.length > 0);
  80. assert(value.slice(0, 2) == '0x' || value.slice(0, 3) == '-0x');
  81. const minus = value[0] === '-';
  82. // Strip the 0x or -0x prefix.
  83. value = value.slice(minus ? 3 : 2);
  84. const lowBits = parseInt(value.slice(-8), 16);
  85. const highBits = parseInt(value.slice(-16, -8) || '', 16);
  86. return (minus ? negate : Int64.fromBits)(lowBits, highBits);
  87. }
  88. // Note to the reader:
  89. // goog.math.Long suffers from a code size issue. JsCompiler almost always
  90. // considers toString methods to be alive in a program. So if you are
  91. // constructing a Long instance the toString method is assumed to be live.
  92. // Unfortunately Long's toString method makes a large chunk of code alive
  93. // of the entire class adding 1.3kB (gzip) of extra code size.
  94. // Callers that are sensitive to code size and are not using Long already
  95. // should avoid calling this method.
  96. /**
  97. * Creates an Int64 instance from a Long value.
  98. * @param {!Long} value
  99. * @return {!Int64}
  100. */
  101. static fromLong(value) {
  102. return new Int64(value.getLowBits(), value.getHighBits());
  103. }
  104. /**
  105. * @param {number} lowBits
  106. * @param {number} highBits
  107. * @private
  108. */
  109. constructor(lowBits, highBits) {
  110. /** @const @private {number} */
  111. this.lowBits_ = lowBits | 0;
  112. /** @const @private {number} */
  113. this.highBits_ = highBits | 0;
  114. }
  115. /**
  116. * Returns the int64 value as a JavaScript number. This will lose precision
  117. * if the number is outside of the safe range for JavaScript of 53 bits
  118. * precision.
  119. * @return {number}
  120. */
  121. asNumber() {
  122. const result = this.highBits_ * TWO_PWR_32_DBL + this.getLowBitsUnsigned();
  123. // TODO: Use our own checking system here.
  124. assert(
  125. Number.isSafeInteger(result), 'conversion to number loses precision.');
  126. return result;
  127. }
  128. // Note to the reader:
  129. // goog.math.Long suffers from a code size issue. JsCompiler almost always
  130. // considers toString methods to be alive in a program. So if you are
  131. // constructing a Long instance the toString method is assumed to be live.
  132. // Unfortunately Long's toString method makes a large chunk of code alive
  133. // of the entire class adding 1.3kB (gzip) of extra code size.
  134. // Callers that are sensitive to code size and are not using Long already
  135. // should avoid calling this method.
  136. /** @return {!Long} */
  137. asLong() {
  138. return Long.fromBits(this.lowBits_, this.highBits_);
  139. }
  140. /** @return {number} Signed 32-bit integer value. */
  141. getLowBits() {
  142. return this.lowBits_;
  143. }
  144. /** @return {number} Signed 32-bit integer value. */
  145. getHighBits() {
  146. return this.highBits_;
  147. }
  148. /** @return {number} Unsigned 32-bit integer. */
  149. getLowBitsUnsigned() {
  150. return this.lowBits_ >>> 0;
  151. }
  152. /** @return {number} Unsigned 32-bit integer. */
  153. getHighBitsUnsigned() {
  154. return this.highBits_ >>> 0;
  155. }
  156. /** @return {string} */
  157. toSignedDecimalString() {
  158. return joinSignedDecimalString(this);
  159. }
  160. /** @return {string} */
  161. toUnsignedDecimalString() {
  162. return joinUnsignedDecimalString(this);
  163. }
  164. /**
  165. * Returns an unsigned hexadecimal string representation of the Int64.
  166. * @return {string}
  167. */
  168. toHexString() {
  169. let nibbles = new Array(16);
  170. let lowBits = this.lowBits_;
  171. let highBits = this.highBits_;
  172. for (let highIndex = 7, lowIndex = 15; lowIndex > 7;
  173. highIndex--, lowIndex--) {
  174. nibbles[highIndex] = HEX_DIGITS[highBits & 0xF];
  175. nibbles[lowIndex] = HEX_DIGITS[lowBits & 0xF];
  176. highBits = highBits >>> 4;
  177. lowBits = lowBits >>> 4;
  178. }
  179. // Always leave the least significant hex digit.
  180. while (nibbles.length > 1 && nibbles[0] == '0') {
  181. nibbles.shift();
  182. }
  183. return `0x${nibbles.join('')}`;
  184. }
  185. /**
  186. * @param {*} other object to compare against.
  187. * @return {boolean} Whether this Int64 equals the other.
  188. */
  189. equals(other) {
  190. if (this === other) {
  191. return true;
  192. }
  193. if (!(other instanceof Int64)) {
  194. return false;
  195. }
  196. // Compare low parts first as there is higher chance they are different.
  197. const otherInt64 = /** @type{!Int64} */ (other);
  198. return (this.lowBits_ === otherInt64.lowBits_) &&
  199. (this.highBits_ === otherInt64.highBits_);
  200. }
  201. /**
  202. * Returns a number (int32) that is suitable for using in hashed structures.
  203. * @return {number}
  204. */
  205. hashCode() {
  206. return (31 * this.lowBits_ + 17 * this.highBits_) | 0;
  207. }
  208. }
  209. /**
  210. * Losslessly converts a 64-bit unsigned integer in 32:32 split representation
  211. * into a decimal string.
  212. * @param {!Int64} int64
  213. * @return {string} The binary number represented as a string.
  214. */
  215. const joinUnsignedDecimalString = (int64) => {
  216. const lowBits = int64.getLowBitsUnsigned();
  217. const highBits = int64.getHighBitsUnsigned();
  218. // Skip the expensive conversion if the number is small enough to use the
  219. // built-in conversions.
  220. // Number.MAX_SAFE_INTEGER = 0x001FFFFF FFFFFFFF, thus any number with
  221. // highBits <= 0x1FFFFF can be safely expressed with a double and retain
  222. // integer precision.
  223. // Proven by: Number.isSafeInteger(0x1FFFFF * 2**32 + 0xFFFFFFFF) == true.
  224. if (highBits <= 0x1FFFFF) {
  225. return String(TWO_PWR_32_DBL * highBits + lowBits);
  226. }
  227. // What this code is doing is essentially converting the input number from
  228. // base-2 to base-1e7, which allows us to represent the 64-bit range with
  229. // only 3 (very large) digits. Those digits are then trivial to convert to
  230. // a base-10 string.
  231. // The magic numbers used here are -
  232. // 2^24 = 16777216 = (1,6777216) in base-1e7.
  233. // 2^48 = 281474976710656 = (2,8147497,6710656) in base-1e7.
  234. // Split 32:32 representation into 16:24:24 representation so our
  235. // intermediate digits don't overflow.
  236. const low = lowBits & LOW_24_BITS;
  237. const mid = ((lowBits >>> 24) | (highBits << 8)) & LOW_24_BITS;
  238. const high = (highBits >> 16) & LOW_16_BITS;
  239. // Assemble our three base-1e7 digits, ignoring carries. The maximum
  240. // value in a digit at this step is representable as a 48-bit integer, which
  241. // can be stored in a 64-bit floating point number.
  242. let digitA = low + (mid * 6777216) + (high * 6710656);
  243. let digitB = mid + (high * 8147497);
  244. let digitC = (high * 2);
  245. // Apply carries from A to B and from B to C.
  246. const base = 10000000;
  247. if (digitA >= base) {
  248. digitB += Math.floor(digitA / base);
  249. digitA %= base;
  250. }
  251. if (digitB >= base) {
  252. digitC += Math.floor(digitB / base);
  253. digitB %= base;
  254. }
  255. // If digitC is 0, then we should have returned in the trivial code path
  256. // at the top for non-safe integers. Given this, we can assume both digitB
  257. // and digitA need leading zeros.
  258. // TODO: Use our own checking system here.
  259. assert(digitC);
  260. return digitC + decimalFrom1e7WithLeadingZeros(digitB) +
  261. decimalFrom1e7WithLeadingZeros(digitA);
  262. };
  263. /**
  264. * @param {number} digit1e7 Number < 1e7
  265. * @return {string} Decimal representation of digit1e7 with leading zeros.
  266. */
  267. const decimalFrom1e7WithLeadingZeros = (digit1e7) => {
  268. const partial = String(digit1e7);
  269. return '0000000'.slice(partial.length) + partial;
  270. };
  271. /**
  272. * Losslessly converts a 64-bit signed integer in 32:32 split representation
  273. * into a decimal string.
  274. * @param {!Int64} int64
  275. * @return {string} The binary number represented as a string.
  276. */
  277. const joinSignedDecimalString = (int64) => {
  278. // If we're treating the input as a signed value and the high bit is set, do
  279. // a manual two's complement conversion before the decimal conversion.
  280. const negative = (int64.getHighBits() & 0x80000000);
  281. if (negative) {
  282. int64 = negate(int64.getLowBits(), int64.getHighBits());
  283. }
  284. const result = joinUnsignedDecimalString(int64);
  285. return negative ? '-' + result : result;
  286. };
  287. /**
  288. * @param {string} dec
  289. * @return {!Int64}
  290. */
  291. const decimalStringToInt64 = (dec) => {
  292. // Check for minus sign.
  293. const minus = dec[0] === '-';
  294. if (minus) {
  295. dec = dec.slice(1);
  296. }
  297. // Work 6 decimal digits at a time, acting like we're converting base 1e6
  298. // digits to binary. This is safe to do with floating point math because
  299. // Number.isSafeInteger(ALL_32_BITS * 1e6) == true.
  300. const base = 1e6;
  301. let lowBits = 0;
  302. let highBits = 0;
  303. function add1e6digit(begin, end = undefined) {
  304. // Note: Number('') is 0.
  305. const digit1e6 = Number(dec.slice(begin, end));
  306. highBits *= base;
  307. lowBits = lowBits * base + digit1e6;
  308. // Carry bits from lowBits to
  309. if (lowBits >= TWO_PWR_32_DBL) {
  310. highBits = highBits + ((lowBits / TWO_PWR_32_DBL) | 0);
  311. lowBits = lowBits % TWO_PWR_32_DBL;
  312. }
  313. }
  314. add1e6digit(-24, -18);
  315. add1e6digit(-18, -12);
  316. add1e6digit(-12, -6);
  317. add1e6digit(-6);
  318. return (minus ? negate : Int64.fromBits)(lowBits, highBits);
  319. };
  320. /**
  321. * @param {number} lowBits
  322. * @param {number} highBits
  323. * @return {!Int64} Two's compliment negation of input.
  324. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Signed_32-bit_integers
  325. */
  326. const negate = (lowBits, highBits) => {
  327. highBits = ~highBits;
  328. if (lowBits) {
  329. lowBits = ~lowBits + 1;
  330. } else {
  331. // If lowBits is 0, then bitwise-not is 0xFFFFFFFF,
  332. // adding 1 to that, results in 0x100000000, which leaves
  333. // the low bits 0x0 and simply adds one to the high bits.
  334. highBits += 1;
  335. }
  336. return Int64.fromBits(lowBits, highBits);
  337. };
  338. /** @const {!Int64} */
  339. const ZERO = new Int64(0, 0);
  340. /** @const @private {number} */
  341. const LOW_16_BITS = 0xFFFF;
  342. /** @const @private {number} */
  343. const LOW_24_BITS = 0xFFFFFF;
  344. /** @const @private {number} */
  345. const LOW_31_BITS = 0x7FFFFFFF;
  346. /** @const @private {number} */
  347. const ALL_32_BITS = 0xFFFFFFFF;
  348. /** @const {!Int64} */
  349. const MAX_VALUE = Int64.fromBits(ALL_32_BITS, LOW_31_BITS);
  350. /** @const {!Int64} */
  351. const MIN_VALUE = Int64.fromBits(0, 0x80000000);
  352. /** @const {number} */
  353. const TWO_PWR_32_DBL = 0x100000000;
  354. /** @const {string} */
  355. const HEX_DIGITS = '0123456789abcdef';
  356. exports = Int64;