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