@neumatter/big-integer
Version:
An extendable class representing JavaScript's bigint data type.
224 lines (182 loc) • 4.73 kB
JavaScript
/** @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)
}
}
}