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