// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. /** @ignore */ const carryBit16 = 1 << 16; /** @ignore */ function intAsHex(value: number): string { if (value < 0) { value = 0xFFFFFFFF + value + 1; } return `0x${value.toString(16)}`; } /** @ignore */ const kInt32DecimalDigits = 8; /** @ignore */ const kPowersOfTen = [ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 ]; /** @ignore */ export class BaseInt64 { constructor(protected buffer: Uint32Array) { } public high(): number { return this.buffer[1]; } public low(): number { return this.buffer[0]; } protected _times(other: BaseInt64) { // Break the left and right numbers into 16 bit chunks // so that we can multiply them without overflow. const L = new Uint32Array([ this.buffer[1] >>> 16, this.buffer[1] & 0xFFFF, this.buffer[0] >>> 16, this.buffer[0] & 0xFFFF ]); const R = new Uint32Array([ other.buffer[1] >>> 16, other.buffer[1] & 0xFFFF, other.buffer[0] >>> 16, other.buffer[0] & 0xFFFF ]); let product = L[3] * R[3]; this.buffer[0] = product & 0xFFFF; let sum = product >>> 16; product = L[2] * R[3]; sum += product; product = (L[3] * R[2]) >>> 0; sum += product; this.buffer[0] += sum << 16; this.buffer[1] = (sum >>> 0 < product ? carryBit16 : 0); this.buffer[1] += sum >>> 16; this.buffer[1] += L[1] * R[3] + L[2] * R[2] + L[3] * R[1]; this.buffer[1] += (L[0] * R[3] + L[1] * R[2] + L[2] * R[1] + L[3] * R[0]) << 16; return this; } protected _plus(other: BaseInt64) { const sum = (this.buffer[0] + other.buffer[0]) >>> 0; this.buffer[1] += other.buffer[1]; if (sum < (this.buffer[0] >>> 0)) { ++this.buffer[1]; } this.buffer[0] = sum; } public lessThan(other: BaseInt64): boolean { return this.buffer[1] < other.buffer[1] || (this.buffer[1] === other.buffer[1] && this.buffer[0] < other.buffer[0]); } public equals(other: BaseInt64): boolean { return this.buffer[1] === other.buffer[1] && this.buffer[0] == other.buffer[0]; } public greaterThan(other: BaseInt64): boolean { return other.lessThan(this); } public hex(): string { return `${intAsHex(this.buffer[1])} ${intAsHex(this.buffer[0])}`; } } /** @ignore */ export class Uint64 extends BaseInt64 { public times(other: Uint64): Uint64 { this._times(other); return this; } public plus(other: Uint64): Uint64 { this._plus(other); return this; } /** @nocollapse */ public static from(val: any, out_buffer = new Uint32Array(2)): Uint64 { return Uint64.fromString( typeof (val) === 'string' ? val : val.toString(), out_buffer ); } /** @nocollapse */ public static fromNumber(num: number, out_buffer = new Uint32Array(2)): Uint64 { // Always parse numbers as strings - pulling out high and low bits // directly seems to lose precision sometimes // For example: // > -4613034156400212000 >>> 0 // 721782784 // The correct lower 32-bits are 721782752 return Uint64.fromString(num.toString(), out_buffer); } /** @nocollapse */ public static fromString(str: string, out_buffer = new Uint32Array(2)): Uint64 { const length = str.length; const out = new Uint64(out_buffer); for (let posn = 0; posn < length;) { const group = kInt32DecimalDigits < length - posn ? kInt32DecimalDigits : length - posn; const chunk = new Uint64(new Uint32Array([Number.parseInt(str.slice(posn, posn + group), 10), 0])); const multiple = new Uint64(new Uint32Array([kPowersOfTen[group], 0])); out.times(multiple); out.plus(chunk); posn += group; } return out; } /** @nocollapse */ public static convertArray(values: (string | number)[]): Uint32Array { const data = new Uint32Array(values.length * 2); for (let i = -1, n = values.length; ++i < n;) { Uint64.from(values[i], new Uint32Array(data.buffer, data.byteOffset + 2 * i * 4, 2)); } return data; } /** @nocollapse */ public static multiply(left: Uint64, right: Uint64): Uint64 { const rtrn = new Uint64(new Uint32Array(left.buffer)); return rtrn.times(right); } /** @nocollapse */ public static add(left: Uint64, right: Uint64): Uint64 { const rtrn = new Uint64(new Uint32Array(left.buffer)); return rtrn.plus(right); } } /** @ignore */ export class Int64 extends BaseInt64 { public negate(): Int64 { this.buffer[0] = ~this.buffer[0] + 1; this.buffer[1] = ~this.buffer[1]; if (this.buffer[0] == 0) { ++this.buffer[1]; } return this; } public times(other: Int64): Int64 { this._times(other); return this; } public plus(other: Int64): Int64 { this._plus(other); return this; } public lessThan(other: Int64): boolean { // force high bytes to be signed // eslint-disable-next-line unicorn/prefer-math-trunc const this_high = this.buffer[1] << 0; // eslint-disable-next-line unicorn/prefer-math-trunc const other_high = other.buffer[1] << 0; return this_high < other_high || (this_high === other_high && this.buffer[0] < other.buffer[0]); } /** @nocollapse */ public static from(val: any, out_buffer = new Uint32Array(2)): Int64 { return Int64.fromString( typeof (val) === 'string' ? val : val.toString(), out_buffer ); } /** @nocollapse */ public static fromNumber(num: number, out_buffer = new Uint32Array(2)): Int64 { // Always parse numbers as strings - pulling out high and low bits // directly seems to lose precision sometimes // For example: // > -4613034156400212000 >>> 0 // 721782784 // The correct lower 32-bits are 721782752 return Int64.fromString(num.toString(), out_buffer); } /** @nocollapse */ public static fromString(str: string, out_buffer = new Uint32Array(2)): Int64 { // TODO: Assert that out_buffer is 0 and length = 2 const negate = str.startsWith('-'); const length = str.length; const out = new Int64(out_buffer); for (let posn = negate ? 1 : 0; posn < length;) { const group = kInt32DecimalDigits < length - posn ? kInt32DecimalDigits : length - posn; const chunk = new Int64(new Uint32Array([Number.parseInt(str.slice(posn, posn + group), 10), 0])); const multiple = new Int64(new Uint32Array([kPowersOfTen[group], 0])); out.times(multiple); out.plus(chunk); posn += group; } return negate ? out.negate() : out; } /** @nocollapse */ public static convertArray(values: (string | number)[]): Uint32Array { const data = new Uint32Array(values.length * 2); for (let i = -1, n = values.length; ++i < n;) { Int64.from(values[i], new Uint32Array(data.buffer, data.byteOffset + 2 * i * 4, 2)); } return data; } /** @nocollapse */ public static multiply(left: Int64, right: Int64): Int64 { const rtrn = new Int64(new Uint32Array(left.buffer)); return rtrn.times(right); } /** @nocollapse */ public static add(left: Int64, right: Int64): Int64 { const rtrn = new Int64(new Uint32Array(left.buffer)); return rtrn.plus(right); } } /** @ignore */ export class Int128 { constructor(private buffer: Uint32Array) { // buffer[3] MSB (high) // buffer[2] // buffer[1] // buffer[0] LSB (low) } public high(): Int64 { return new Int64(new Uint32Array(this.buffer.buffer, this.buffer.byteOffset + 8, 2)); } public low(): Int64 { return new Int64(new Uint32Array(this.buffer.buffer, this.buffer.byteOffset, 2)); } public negate(): Int128 { this.buffer[0] = ~this.buffer[0] + 1; this.buffer[1] = ~this.buffer[1]; this.buffer[2] = ~this.buffer[2]; this.buffer[3] = ~this.buffer[3]; if (this.buffer[0] == 0) { ++this.buffer[1]; } if (this.buffer[1] == 0) { ++this.buffer[2]; } if (this.buffer[2] == 0) { ++this.buffer[3]; } return this; } public times(other: Int128): Int128 { // Break the left and right numbers into 32 bit chunks // so that we can multiply them without overflow. const L0 = new Uint64(new Uint32Array([this.buffer[3], 0])); const L1 = new Uint64(new Uint32Array([this.buffer[2], 0])); const L2 = new Uint64(new Uint32Array([this.buffer[1], 0])); const L3 = new Uint64(new Uint32Array([this.buffer[0], 0])); const R0 = new Uint64(new Uint32Array([other.buffer[3], 0])); const R1 = new Uint64(new Uint32Array([other.buffer[2], 0])); const R2 = new Uint64(new Uint32Array([other.buffer[1], 0])); const R3 = new Uint64(new Uint32Array([other.buffer[0], 0])); let product = Uint64.multiply(L3, R3); this.buffer[0] = product.low(); const sum = new Uint64(new Uint32Array([product.high(), 0])); product = Uint64.multiply(L2, R3); sum.plus(product); product = Uint64.multiply(L3, R2); sum.plus(product); this.buffer[1] = sum.low(); this.buffer[3] = (sum.lessThan(product) ? 1 : 0); this.buffer[2] = sum.high(); const high = new Uint64(new Uint32Array(this.buffer.buffer, this.buffer.byteOffset + 8, 2)); high.plus(Uint64.multiply(L1, R3)) .plus(Uint64.multiply(L2, R2)) .plus(Uint64.multiply(L3, R1)); this.buffer[3] += Uint64.multiply(L0, R3) .plus(Uint64.multiply(L1, R2)) .plus(Uint64.multiply(L2, R1)) .plus(Uint64.multiply(L3, R0)).low(); return this; } public plus(other: Int128): Int128 { const sums = new Uint32Array(4); sums[3] = (this.buffer[3] + other.buffer[3]) >>> 0; sums[2] = (this.buffer[2] + other.buffer[2]) >>> 0; sums[1] = (this.buffer[1] + other.buffer[1]) >>> 0; sums[0] = (this.buffer[0] + other.buffer[0]) >>> 0; if (sums[0] < (this.buffer[0] >>> 0)) { ++sums[1]; } if (sums[1] < (this.buffer[1] >>> 0)) { ++sums[2]; } if (sums[2] < (this.buffer[2] >>> 0)) { ++sums[3]; } this.buffer[3] = sums[3]; this.buffer[2] = sums[2]; this.buffer[1] = sums[1]; this.buffer[0] = sums[0]; return this; } public hex(): string { return `${intAsHex(this.buffer[3])} ${intAsHex(this.buffer[2])} ${intAsHex(this.buffer[1])} ${intAsHex(this.buffer[0])}`; } /** @nocollapse */ public static multiply(left: Int128, right: Int128): Int128 { const rtrn = new Int128(new Uint32Array(left.buffer)); return rtrn.times(right); } /** @nocollapse */ public static add(left: Int128, right: Int128): Int128 { const rtrn = new Int128(new Uint32Array(left.buffer)); return rtrn.plus(right); } /** @nocollapse */ public static from(val: any, out_buffer = new Uint32Array(4)): Int128 { return Int128.fromString( typeof (val) === 'string' ? val : val.toString(), out_buffer ); } /** @nocollapse */ public static fromNumber(num: number, out_buffer = new Uint32Array(4)): Int128 { // Always parse numbers as strings - pulling out high and low bits // directly seems to lose precision sometimes // For example: // > -4613034156400212000 >>> 0 // 721782784 // The correct lower 32-bits are 721782752 return Int128.fromString(num.toString(), out_buffer); } /** @nocollapse */ public static fromString(str: string, out_buffer = new Uint32Array(4)): Int128 { // TODO: Assert that out_buffer is 0 and length = 4 const negate = str.startsWith('-'); const length = str.length; const out = new Int128(out_buffer); for (let posn = negate ? 1 : 0; posn < length;) { const group = kInt32DecimalDigits < length - posn ? kInt32DecimalDigits : length - posn; const chunk = new Int128(new Uint32Array([Number.parseInt(str.slice(posn, posn + group), 10), 0, 0, 0])); const multiple = new Int128(new Uint32Array([kPowersOfTen[group], 0, 0, 0])); out.times(multiple); out.plus(chunk); posn += group; } return negate ? out.negate() : out; } /** @nocollapse */ public static convertArray(values: (string | number)[]): Uint32Array { // TODO: Distinguish between string and number at compile-time const data = new Uint32Array(values.length * 4); for (let i = -1, n = values.length; ++i < n;) { Int128.from(values[i], new Uint32Array(data.buffer, data.byteOffset + 4 * 4 * i, 4)); } return data; } }