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