UNPKG

intmath

Version:

A package to perform precise 64 and 128-bit math operations in typescript.

822 lines (698 loc) 24 kB
/* * A library to perform precise integer math operations on 64 and 128-bit signed * numbers. * * Copyright (c) 2017 Venelin Efremov * * License: * MIT, see LICENSE file for details. * */ const TwoPow32: number = Math.pow(2, 32); const TwoPow64: number = Math.pow(2, 64); const TwoPow96: number = Math.pow(2, 96); const MaxValue64AsDbl:number = Math.pow(2, 63); const MinValue64AsDbl:number = -Math.pow(2, 63); const MaxValue128AsDbl:number = Math.pow(2, 127); const MinValue128AsDbl:number = -Math.pow(2, 127); export class Int64 { private high_:number; private low_:number; constructor(low: number, high: number) { this.low_ = low >>> 0; this.high_ = high | 0; } public get high():number { return this.high_; } public get low():number { return this.low_; } public clone():Int64 { return new Int64(this.low_, this.high_); } public isZero():boolean { return this.high_ == 0 && this.low_ == 0; } public isNegative():boolean { return this.high_ < 0; } public isPositive():boolean { return this.high_ > 0 || (this.high_ == 0 && this.low_ != 0); } public isOdd():boolean { return (this.low_ & 1) == 1; } public equals(other:Int64):boolean { return (this.high_ == other.high_) && (this.low_ == other.low_); } public notEquals(other:Int64):boolean { return (this.high_ != other.high_) || (this.low_ != other.low_); } public not():Int64 { return LongIntImpl.function64_64(this, "not64"); } public neg():Int64 { return LongIntImpl.function64_64(this, "neg64"); } public negate():Int64 { return this.neg(); } public and(other:Int64):Int64 { return LongIntImpl.function64_64_64(this, other, "and64"); } public or(other:Int64):Int64 { return LongIntImpl.function64_64_64(this, other, "or64"); } public xor(other:Int64):Int64 { return LongIntImpl.function64_64_64(this, other, "xor64"); } public add(b: Int64|number): Int64 { if (typeof b === "number") { if (Math.trunc(b) != b) { return Int64.fromRoundNumber(this.toNumber() + b); } b = Int64.fromRoundNumber(b); } return LongIntImpl.function64_64_64(this, b, "add64"); } public sub(b: Int64|number): Int64 { if (typeof b === "number") { if (Math.trunc(b) != b) { return Int64.fromRoundNumber(this.toNumber() - b); } b = Int64.fromRoundNumber(b); } return LongIntImpl.function64_64_64(this, b, "sub64"); } public subtract(b: Int64): Int64 { return this.sub(b); } public mul(b: Int64|number): Int64 { if (typeof b === "number") { if (Math.trunc(b) != b) { return Int64.fromRoundNumber(this.toNumber() * b); } b = Int64.fromRoundNumber(b); } return LongIntImpl.function64_64_64(this, b, "mul64"); } public multiply(b: Int64): Int64 { return this.mul(b); } public div(b: Int64|number): Int64 { if (typeof b === "number") { if (Math.trunc(b) != b) { return Int64.fromRoundNumber(this.toNumber() / b); } b = Int64.fromRoundNumber(b); } return LongIntImpl.function64_64_64(this, b, "divs64"); } public divide(b: Int64): Int64 { return this.div(b); } public mod(b: Int64): Int64 { return LongIntImpl.function64_64_64(this, b, "rems64"); } public modulo(b: Int64): Int64 { return this.mod(b); } public shr(b:number): Int64 { return LongIntImpl.function64_32_64(this, b, "shrs64"); } public shrUnsigned(b: number): Int64 { return LongIntImpl.function64_32_64(this, b, "shru64"); } public shl(b: number): Int64 { return LongIntImpl.function64_32_64(this, b, "shl64"); } public rotr(b: number): Int64 { return LongIntImpl.function64_32_64(this, b, "rotr64"); } public rotl(b: number): Int64 { return LongIntImpl.function64_32_64(this, b, "rotl64"); } public clz(): Int64 { return LongIntImpl.function64_64(this, "clz64"); } public ctz(): Int64 { return LongIntImpl.function64_64(this, "ctz64"); } public compare(other:Int64):number { if (this.equals(other)) { return 0; } let aNeg = this.isNegative(); let bNeg = other.isNegative(); if (aNeg && !bNeg) { return -1; } if (!aNeg && bNeg) { return 1; } // at this point, the signs are the same, so subtraction will not overflow if (this.sub(other).isNegative()) { return -1; } else { return 1; } } public lessThan(other:Int64):boolean { return LongIntImpl.function64_64_32(this, other, "lts64") != 0; } public lessThanOrEqual(other:Int64):boolean { return LongIntImpl.function64_64_32(this, other, "les64") != 0; } public greaterThan(other:Int64):boolean { return LongIntImpl.function64_64_32(this, other, "gts64") != 0; } public greaterThanOrEqual(other:Int64):boolean { return LongIntImpl.function64_64_32(this, other, "ges64") != 0; } public abs():Int64 { if (this.isNegative()) { return this.neg(); } return this; } public static fromNumber(value:number):Int64 { if (isNaN(value)) { return new Int64(0, 0); } else if (value < MinValue64AsDbl) { return MinValue64; } else if (value > MaxValue64AsDbl) { return MaxValue64; } else if (value < 0) { return this.fromNumber(-value).neg(); } else { return new Int64( (value % TwoPow32) | 0, (value / TwoPow32) | 0); } } public static fromRoundNumber(value:number):Int64 { return Int64.fromNumber(Math.round(value)); } public static fromInt(value:number):Int64 { let intValue = value | 0; if (!(intValue === value)) { throw new Error("Value is not an int value"); } return new Int64(intValue, intValue < 0 ? -1 : 0); } private static fromString(str:string, radix:number = 10):Int64 { if (str.length == 0) { throw Error('number format error: empty string'); } if (radix < 2 || 36 < radix) { throw Error('radix out of range: ' + radix); } if (str.charAt(0) == '-') { return Int64.fromString(str.substring(1), radix).neg(); } else if (str.indexOf('-') >= 0) { throw Error('number format error: interior "-" character: ' + str); } // Do several (8) digits each time through the loop, so as to // minimize the calls to the very expensive emulated multiplication. let radixToPower = Int64.fromNumber(Math.pow(radix, 8)); let result = new Int64(0, 0); for (let i = 0; i < str.length; i += 8) { let size = Math.min(8, str.length - i); let value = parseInt(str.substring(i, i + size), radix); if (size < 8) { let power = Int64.fromNumber(Math.pow(radix, size)); result = result.mul(power).add(Int64.fromNumber(value)); } else { result = result.mul(radixToPower); result = result.add(Int64.fromNumber(value)); } } return result; } public toNumber(): number { return (this.low_ >>> 0) + this.high_ * TwoPow32; } public toInt():number { return this.low_; } public toString(radix:number = 10):string { if (radix < 2 || 36 < radix) { throw Error('radix out of range: ' + radix); } if (this.isZero()) { return '0'; } if (this.isNegative()) { if (this.equals(MinValue64)) { // We need to change the Long value before it can be negated, so we remove // the bottom-most digit in this base and then recurse to do the rest. let radixLong = Int64.fromNumber(radix); let div = this.div(radixLong); let rem = div.mul(radixLong).sub(this); return div.toString(radix) + rem.toInt().toString(radix); } else { return '-' + this.neg().toString(radix); } } // Do several (6) digits each time through the loop, so as to // minimize the calls to the very expensive emulated div. let radixToPower = Int64.fromNumber(Math.pow(radix, 6)); let rem = new Int64(this.low_, this.high_); let result = ''; while (true) { let remDiv = rem.div(radixToPower); // The right shifting fixes negative values in the case when // intval >= 2^31; for more details see // https://github.com/google/closure-library/pull/498 let intval = rem.sub(remDiv.mul(radixToPower)).toInt() >>> 0; let digits = intval.toString(radix); rem = remDiv; if (rem.isZero()) { return digits + result; } else { while (digits.length < 6) { digits = '0' + digits; } result = '' + digits + result; } } } public static Swap(one:Int64, other:Int64):void { let tmpl = one.low_; let tmph = one.high_; one.low_ = other.low_; one.high_ = other.high_; other.low_ = tmpl; other.high_ = tmph; } public static max(a:Int64, b:Int64):Int64 { return a.greaterThan(b) ? a : b; } public static min(a:Int64, b:Int64):Int64 { return a.lessThan(b) ? a : b; } public static init():Promise<void> { return LongIntImpl.init(); } } export class Int128 { private d0_:number; private d1_:number; private d2_:number; private d3_:number; constructor(d0: number, d1: number, d2: number, d3: number) { this.d0_ = d0 >>> 0; this.d1_ = d1 >>> 0; this.d2_ = d2 >>> 0; this.d3_ = d3 | 0; } public get d0():number { return this.d0_; } public get d1():number { return this.d1_; } public get d2():number { return this.d2_; } public get d3():number { return this.d3_; } public clone():Int128 { return new Int128(this.d0_, this.d1_, this.d2_, this.d3_); } public isZero():boolean { return this.d3_ == 0 && this.d2_ == 0 && this.d1_ == 0 && this.d0_ == 0; } public isNegative():boolean { return this.d3_ < 0; } public isPositive():boolean { return this.d3_ > 0 || (this.d3_ == 0 && (this.d2_ != 0 || this.d1_ != 0 || this.d0_ != 0)); } public isOdd():boolean { return (this.d0_ & 1) == 1; } public equals(other:Int128):boolean { return (this.d3_ == other.d3_) && (this.d2_ == other.d2_) && (this.d1_ == other.d1_) && (this.d0_ == other.d0_); } public notEquals(other:Int128):boolean { return (this.d3_ != other.d3_) || (this.d2_ != other.d2_) || (this.d1_ != other.d1_) || (this.d0_ != other.d0_); } public not():Int128 { return LongIntImpl.function128_128(this, "not128"); } public neg():Int128 { return LongIntImpl.function128_128(this, "neg128"); } public negate():Int128 { return this.neg(); } public and(b: Int128): Int128 { return LongIntImpl.function128_128_128(this, b, "and128"); } public or(b: Int128): Int128 { return LongIntImpl.function128_128_128(this, b, "or128"); } public xor(b: Int128): Int128 { return LongIntImpl.function128_128_128(this, b, "xor128"); } public add(b: Int128): Int128 { return LongIntImpl.function128_128_128(this, b, "add128"); } public sub(b: Int128): Int128 { return LongIntImpl.function128_128_128(this, b, "sub128"); } public subtract(b: Int128): Int128 { return this.sub(b); } public compare(other:Int128):number { if (this.equals(other)) { return 0; } let aNeg = this.isNegative(); let bNeg = other.isNegative(); if (aNeg && !bNeg) { return -1; } if (!aNeg && bNeg) { return 1; } // at this point, the signs are the same, so subtraction will not overflow if (this.sub(other).isNegative()) { return -1; } else { return 1; } } public lessThan(b:Int128):boolean { return this.compare(b) < 0; } public lessThanOrEqual(b:Int128):boolean { return this.compare(b) <= 0; } public greaterThan(b:Int128):boolean { return this.compare(b) > 0; } public greaterThanOrEqual(b:Int128):boolean { return this.compare(b) >= 0; } public mul(b: Int128): Int128 { if (this.isZero() || b.isZero()) { return new Int128(0, 0, 0, 0); } if (this.isNegative()) { if (b.isNegative()) { return this.neg().mul(b.neg()); } else { return this.neg().mul(b).neg(); } } else if (b.isNegative()) { return this.mul(b.neg()).neg(); } return LongIntImpl.function128_128_128(this, b, "mul128"); } public multiply(b: Int128): Int128 { return this.mul(b); } public shiftLeft(numBits:number):Int128 { numBits &= 127; if (numBits == 0) { return this.clone(); } else { let value = this.clone(); if (numBits >= 96) { numBits -= 96 return new Int128(0, 0, 0, this.d0_ << numBits); } if (numBits >= 64) { numBits -= 64; return new Int128( 0, 0, this.d0_ << numBits, this.d1_ << numBits | this.d0_ >>> (32 - numBits)); } if (numBits >= 32) { numBits -= 32; return new Int128( 0, this.d0_ << numBits, this.d1_ << numBits | this.d0_ >>> (32 - numBits), this.d2_ << numBits | this.d1_ >>> (32 - numBits)); } return new Int128( this.d0_ << numBits, this.d1_ << numBits | this.d0_ >>> (32 - numBits), this.d2_ << numBits | this.d1_ >>> (32 - numBits), this.d3_ << numBits | this.d2_ >>> (32 - numBits)); } } public shiftRight(numBits:number):Int128 { numBits &= 127; if (numBits == 0) { return this.clone(); } else { let value = this.clone(); if (numBits >= 96) { numBits -= 96 return new Int128(this.d3_ >> numBits, 0, 0, 0); } if (numBits >= 64) { numBits -= 64; return new Int128( this.d3_ >> numBits | this.d2_ << (32 - numBits), this.d3_ >> numBits, 0, 0); } if (numBits >= 32) { numBits -= 32; return new Int128( this.d2_ >>> numBits | this.d1_ << (32 - numBits), this.d3_ >> numBits | this.d2_ << (32 - numBits), this.d3_ >> numBits, 0); } return new Int128( this.d1_ >>> numBits | this.d0_ << (32 - numBits), this.d2_ >>> numBits | this.d1_ << (32 - numBits), this.d3_ >> numBits | this.d2_ << (32 - numBits), this.d3_ >> numBits); } } public shiftRightUnsigned(numBits:number):Int128 { numBits &= 127; if (numBits == 0) { return this.clone(); } else { let value = this.clone(); if (numBits >= 96) { numBits -= 96 return new Int128(this.d3_ >>> numBits, 0, 0, 0); } if (numBits >= 64) { numBits -= 64; return new Int128( this.d3_ >>> numBits | this.d2_ << (32 - numBits), this.d3_ >>> numBits, 0, 0); } if (numBits >= 32) { numBits -= 32; return new Int128( this.d2_ >>> numBits | this.d1_ << (32 - numBits), this.d3_ >>> numBits | this.d2_ << (32 - numBits), this.d3_ >>> numBits, 0); } return new Int128( this.d1_ >>> numBits | this.d0_ << (32 - numBits), this.d2_ >>> numBits | this.d1_ << (32 - numBits), this.d3_ >>> numBits | this.d2_ << (32 - numBits), this.d3_ >>> numBits); } } public toNumber():number { return (this.d0_ >>> 0) + (this.d1_ >>> 0) * TwoPow32 + (this.d2_ >>> 0) * TwoPow64 + (this.d3_ >>> 0) * TwoPow96; } public abs():Int128 { if (this.isNegative()) { return this.neg(); } return this; } public static fromInt64(v:Int64):Int128 { let signExtent = v.high < 0 ? -1 : 0; return new Int128(v.low, v.high, signExtent, signExtent); } public static fromNumber(value:number):Int128 { if (isNaN(value)) { return new Int128(0, 0, 0, 0); } else if (value < MinValue128AsDbl) { return MinValue128; } else if (value > MaxValue128AsDbl) { return MaxValue128; } else if (value < 0) { return this.fromNumber(-value).neg(); } else { let d0 = value & TwoPow32; value /= TwoPow32; let d1 = value & TwoPow32; value /= TwoPow32; let d2 = value & TwoPow32; value /= TwoPow32; let d3 = value; return new Int128(d0, d1, d2, d3); } } public static fromRoundNumber(value:number):Int128 { return Int128.fromNumber(Math.round(value)); } public static fromInt(value:number):Int128 { let intValue = value | 0; if (!(intValue === value)) { throw new Error("Value is not an int value"); } let signExtent = value < 0 ? -1 : 0; return new Int128(intValue, signExtent, signExtent, signExtent); } public static mul64(a:Int64, b:Int64):Int128 { return Int128.fromInt64(a).mul(Int128.fromInt64(b)); } public static Swap(one:Int128, other:Int128):void { let tmp = one.clone(); one.d0_ = other.d0_; one.d1_ = other.d1_; one.d2_ = other.d2_; one.d3_ = other.d3_; other.d0_ = tmp.d0_; other.d1_ = tmp.d1_; other.d2_ = tmp.d2_; other.d3_ = tmp.d3_; } public static max(a:Int128, b:Int128):Int128 { return a.greaterThan(b) ? a : b; } public static min(a:Int128, b:Int128):Int128 { return a.lessThan(b) ? a : b; } } const MinValue64:Int64 = new Int64(0, 0x80000000); const MaxValue64:Int64 = new Int64(0xffffffff, 0x7fffffff); const MinValue128:Int128 = new Int128(0, 0, 0, 0x80000000); const MaxValue128:Int128 = new Int128(0xffffffff, 0xffffffff, 0xffffffff, 0x7fffffff); class LongIntImpl { private static instance: WebAssembly.Instance; private static mem32: Uint32Array; private static setArg128(n128: Int128, offset: number): void { LongIntImpl.mem32[offset] = n128.d0; LongIntImpl.mem32[offset + 1] = n128.d1; LongIntImpl.mem32[offset + 2] = n128.d2; LongIntImpl.mem32[offset + 3] = n128.d3; } private static setArg64(n64: Int64, offset: number): void { LongIntImpl.mem32[offset] = n64.low; LongIntImpl.mem32[offset + 1] = n64.high; } private static setArg32(n32: number, offset: number): void { LongIntImpl.mem32[offset] = n32|0; } public static function64_32_64(a: Int64, b: number, name: string): Int64 { LongIntImpl.setArg64(a, 0); LongIntImpl.setArg32(b, 2); LongIntImpl.instance.exports[name](); return this.result64(); } public static function64_64_64(a: Int64, b: Int64, name: string): Int64 { LongIntImpl.setArg64(a, 0); LongIntImpl.setArg64(b, 2); LongIntImpl.instance.exports[name](); return this.result64(); } public static function64_64(a: Int64, name: string): Int64 { LongIntImpl.setArg64(a, 0); LongIntImpl.instance.exports[name](); return this.result64(); } public static function64_64_32(a: Int64, b: Int64, name: string): number { LongIntImpl.setArg64(a, 0); LongIntImpl.setArg64(b, 2); LongIntImpl.instance.exports[name](); return this.result32(); } public static function128_128_128(a: Int128, b: Int128, name: string): Int128 { LongIntImpl.setArg128(a, 0); LongIntImpl.setArg128(b, 4); LongIntImpl.instance.exports[name](); return this.result128(); } public static function128_128(a: Int128, name: string): Int128 { LongIntImpl.setArg128(a, 0); LongIntImpl.instance.exports[name](); return this.result128(); } protected static result128(): Int128 { return new Int128( LongIntImpl.mem32[8], LongIntImpl.mem32[9], LongIntImpl.mem32[10], LongIntImpl.mem32[11]); } protected static result64(): Int64 { return new Int64(LongIntImpl.mem32[8], LongIntImpl.mem32[9]); } protected static result32(): number { return LongIntImpl.mem32[8]; } public static init(): Promise<void> { if (LongIntImpl.instance != null) { return Promise.resolve(); } return LongIntImpl.fetchAndInstantiate('intmath.wasm') .then(instance => { LongIntImpl.instance = instance; LongIntImpl.mem32 = new Uint32Array(instance.exports.mem.buffer); return; }); } private static fetchAndInstantiate(url: string): Promise<WebAssembly.Instance> { if (typeof fetch === 'function') { return fetch(url) .then(response => response.arrayBuffer()) .then(bytes => WebAssembly.instantiate(bytes, undefined)) .then(results => results.instance); } let fs = require('fs'); return new Promise((resolve, reject) => { try { fs.readFile(url, (err:NodeJS.ErrnoException, buffer:Buffer) => { if (err) reject(err); else resolve(buffer); }); } catch (err) { reject(err); } }) .then((bytes:Buffer) => WebAssembly.instantiate(bytes.buffer, undefined)) .then(results => results.instance); } }