Protocol Buffers - Google's data interchange format (grpc依赖)
https://developers.google.com/protocol-buffers/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
403 lines
12 KiB
403 lines
12 KiB
/** |
|
* @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;
|
|
|