Spaces:
Running
Running
// 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; | |
} | |
} | |