| // 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([parseInt(str.substr(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 |
| const this_high = this.buffer[1] << 0; |
| 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([parseInt(str.substr(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([parseInt(str.substr(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; |
| } |
| } |