blob: 45585b752519f13c4fd741f8aca9fba0f33bb18b [file] [log] [blame]
/**
* @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;