UNPKG

@neumatter/big-integer

Version:

An extendable class representing JavaScript's bigint data type.

224 lines (182 loc) 4.73 kB
/** @typedef {number | string | bigint | boolean | BigInteger} BigIntegerLike */ import ObjectCache from '@neumatter/object-cache' import randomBytes from '@neumatter/random-bytes' import ByteView from 'byteview' const MAX_INTEGER_BIGINT = 2n ** 53n - 1n function roundBitLength (bitLength) { let high = 8 while (bitLength > high) { high += 8 } const low = high - 8 const highRange = high - bitLength const lowRange = bitLength - low return highRange <= lowRange ? high : low } function fromBytes (byteView) { if (!ByteView.isByteView(byteView)) { byteView = ByteView.from(byteView) } let index = 0 const { length } = byteView let res = BigInt(byteView[index] & 0x7f) if (byteView[index] & 0x80) { res -= BigInt(0x80) // Treat most-significant bit as -2^i instead of 2^i } ++index while (index + 4 <= length) { const number = byteView.getUint32(index, false) res = (res << 32n) + BigInt(number) index += 4 } while (index < length) { const number = byteView.getUint8(index) res = (res << 8n) + BigInt(number) ++index } return res } export default class BigInteger extends BigInt { static #testersCoeff = [] static #testersBigCoeff = [] static #testers = [] static #testersN = 0 static isPrime (p) { p = p.valueOf() for (let i = 2n; i * i <= p; i++) { if (p % i === 0n) return false } return true } static nthPrime (nth) { nth = nth.valueOf() let maybePrime = 2n let prime = 0n while (nth >= 0n) { if (BigInteger.isPrime(maybePrime)) { nth-- prime = maybePrime } maybePrime++ } return prime } static from (value) { if (typeof value === 'object') { if (value instanceof Uint8Array) { return new this(fromBytes(value)) } else if ( 'type' in value && 'data' in value && value.type === 'BigInteger' ) { return new this(value.data) } else if (value instanceof BigInteger) { return new this(value.toString()) } } else { return new this(value) } } static random (bitLength) { return BigInteger.from(randomBytes(Math.ceil(bitLength / 8))) } /** * * @param {BigIntegerLike} value */ constructor (value) { const self = Object(BigInt(value)) Object.setPrototypeOf(self, new.target.prototype) return self } get bitLength () { const cache = ObjectCache.from(this) let bitLength = cache.get('bitLength') if (bitLength !== undefined) { return bitLength } let x = this.valueOf() let addBackIn = 0 if (x < BigInt(0)) { x = -x addBackIn = 1 } // find upper bound let k = 0 while (true) { if (BigInteger.#testersN === k) { BigInteger.#testersCoeff.push(32 << BigInteger.#testersN) BigInteger.#testersBigCoeff.push( BigInt(BigInteger.#testersCoeff[BigInteger.#testersN]) ) BigInteger.#testers.push( 1n << BigInteger.#testersBigCoeff[BigInteger.#testersN] ) BigInteger.#testersN++ } if (x < BigInteger.#testers[k]) break k++ } if (!k) { bitLength = 32 - Math.clz32(Number(x)) + addBackIn cache.set('bitLength', bitLength) return bitLength } // determine length by bisection k-- let i = BigInteger.#testersCoeff[k] let a = x >> BigInteger.#testersBigCoeff[k] while (k--) { let b = a >> BigInteger.#testersBigCoeff[k] if (b) (i += BigInteger.#testersCoeff[k]), (a = b) } bitLength = i + 32 - Math.clz32(Number(a)) + addBackIn cache.set('bitLength', bitLength) return bitLength } get roundedBitLength () { return roundBitLength(this.bitLength) } /** * * @returns {bigint} */ valueOf () { return super.valueOf() } /** * * @returns {number} Within `2 ** 53 - 1` range */ toNumber () { return Number(this & MAX_INTEGER_BIGINT) } /** * * @param {number} radix Specifies a radix for converting numeric values to strings. * @returns {string} */ toString (radix) { return super.toString(radix) } /** * Returns a string representation appropriate to the host environment's current locale. * * @param {Intl.LocalesArgument | undefined} locales * @param {BigIntToLocaleStringOptions | undefined} options * @returns {string} */ toLocaleString (locales, options) { return super.toLocaleString(locales, options) } get [Symbol.toStringTag] () { return this.constructor.name } toJSON () { return { type: 'BigInteger', data: '0x' + this.toString(16) } } }