UNPKG

@neumatter/bvon

Version:

Serialize data into buffer. Browser or NodeJS.

857 lines (739 loc) 23.4 kB
import ByteView from 'byteview' import BVONError from './BVONError.js' /** @typedef {string | number | Int64} Int64Other */ // WebAssembly optimizations to do native i64 multiplication and divide let wasm = null try { wasm = new WebAssembly.Instance( new WebAssembly.Module( new Uint8Array([ 0, 97, 115, 109, 1, 0, 0, 0, 1, 13, 2, 96, 0, 1, 127, 96, 4, 127, 127, 127, 127, 1, 127, 3, 7, 6, 0, 1, 1, 1, 1, 1, 6, 6, 1, 127, 1, 65, 0, 11, 7, 50, 6, 3, 109, 117, 108, 0, 1, 5, 100, 105, 118, 95, 115, 0, 2, 5, 100, 105, 118, 95, 117, 0, 3, 5, 114, 101, 109, 95, 115, 0, 4, 5, 114, 101, 109, 95, 117, 0, 5, 8, 103, 101, 116, 95, 104, 105, 103, 104, 0, 0, 10, 191, 1, 6, 4, 0, 35, 0, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 126, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 127, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 128, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 129, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 130, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11 ]) ), {} ).exports } catch (e) { wasm = null // no wasm support } const K_SYMBOL_IS_INT64 = Symbol.for('neumatter.bvon.isInt64') const UINT_CACHE = new Map() const INT_CACHE = new Map() const TWO_PWR_16_DBL = 1 << 16 const TWO_PWR_24_DBL = 1 << 24 const TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL const TWO_PWR_64_DBL = TWO_PWR_32_DBL * TWO_PWR_32_DBL const TWO_PWR_63_DBL = TWO_PWR_64_DBL / 2 const MAX_INT64_STRING_LENGTH = 20 const INT64_REG_EX = /^(\+?0|(\+|-)?[1-9][0-9]*)$/ export default class Int64 { static ZERO = (() => new Int64(0, 0, false))() static UNSIGNED_ZERO = (() => new Int64(0, 0, true))() static ONE = (() => new Int64(1, 0, false))() static UNSIGNED_ONE = (() => new Int64(1, 0, true))() static NEGATIVE_ONE = (() => new Int64(-1, -1, false))() static MAX_VALUE = (() => new Int64(0xffffffff | 0, 0x7fffffff | 0, false))() static MAX_UNSIGNED_VALUE = (() => new Int64(0xffffffff | 0, 0xffffffff | 0, true))() static MIN_VALUE = (() => new Int64(0, 0x80000000 | 0, false))() static isInt64 (value) { return ( value != null && typeof value === 'object' && K_SYMBOL_IS_INT64 in value && value[K_SYMBOL_IS_INT64] === true ) } static isSafeInteger (value) { let res = false switch (typeof value) { case 'number': if (Number.isSafeInteger(value)) { res = true } break case 'string': { if (value.length > 20) break const match = value.match(INT64_REG_EX) if (!match) break const int64 = Int64.from(value) if ( int64.lessThanOrEqual(Int64.MAX_VALUE) && int64.greaterThanOrEqual(Int64.MIN_VALUE) ) { res = true } break } } return res } /** * * @param {number | string | Uint8Array | Array<number> | Int64} value * @param {boolean} unsigned * @returns {Int64} */ static from (value, unsigned) { switch (typeof value) { case 'number': return fromNumber(value, unsigned) case 'bigint': return fromString(value.toString(), unsigned) case 'string': return fromString(value, unsigned) case 'boolean': return fromNumber(Number(value), unsigned) case 'undefined': return Int64.ZERO case 'object': if (value === null) return Int64.ZERO if (Array.isArray(value) || value instanceof Uint8Array) { return fromBytes(value, unsigned) } // Throws for non-objects, converts non-instanceof Long: return new Int64( value.low, value.high, typeof unsigned === 'boolean' ? unsigned : value.unsigned ) default: throw new TypeError('could not find valid type') } } #unsigned constructor (low, high, unsigned) { this[0] = low | 0 this[1] = high | 0 this.#unsigned = Boolean(unsigned) } get low () { return this[0] } get high () { return this[1] } get absBitLength () { if (this.isNegative) { // Unsigned Longs are never negative return this.equals(Int64.MIN_VALUE) ? 64 : this.negate().absBitLength } const val = this.high !== 0 ? this.high : this.low let bit for (bit = 31; bit > 0; bit--) if ((val & (1 << bit)) !== 0) break return this.high !== 0 ? bit + 33 : bit + 1 } get unsigned () { return this.#unsigned } get isValueSafe () { const value = this.valueOf() if ( value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER ) { return false } return true } get [K_SYMBOL_IS_INT64] () { return true } get isEven () { return (this.low & 1) === 0 } get isNegative () { return !this.unsigned && this.high < 0 } get isOdd () { return (this.low & 1) === 1 } get isPositive () { return this.unsigned || this.high >= 0 } get isZero () { return this.high === 0 && this.low === 0 } /** * * @param {string | number | Long} addend * @returns {Int64} */ add (addend) { if (!Int64.isInt64(addend)) { addend = Int64.from(addend) } // Divide each number into 4 chunks of 16 bits, and then sum the chunks. const a48 = this.high >>> 16 const a32 = this.high & 0xffff const a16 = this.low >>> 16 const a00 = this.low & 0xffff const b48 = addend.high >>> 16 const b32 = addend.high & 0xffff const b16 = addend.low >>> 16 const b00 = addend.low & 0xffff let c48 = 0 let c32 = 0 let c16 = 0 let c00 = 0 c00 += a00 + b00 c16 += c00 >>> 16 c00 &= 0xffff c16 += a16 + b16 c32 += c16 >>> 16 c16 &= 0xffff c32 += a32 + b32 c48 += c32 >>> 16 c32 &= 0xffff c48 += a48 + b48 c48 &= 0xffff const low = (c16 << 16) | c00 const high = (c48 << 16) | c32 return new Int64(low, high, this.unsigned) } /** * * @param {Int64Other} other * @returns {Int64} */ and (other) { if (!Int64.isInt64(other)) { other = Int64.from(other) } return new Int64( this.low & other.low, this.high & other.high, this.unsigned ) } /** * * @param {Int64Other} other * @returns {0 | 1 | -1} */ compare (other) { if (!Int64.isInt64(other)) { other = Int64.from(other) } if (this.equals(other)) return 0 const thisNeg = this.isNegative const otherNeg = other.isNegative if (thisNeg && !otherNeg) return -1 if (!thisNeg && otherNeg) return 1 // At this point the sign bits are the same if (!this.unsigned) return this.subtract(other).isNegative ? -1 : 1 // Both are positive if at least one is unsigned return other.high >>> 0 > this.high >>> 0 || (other.high === this.high && other.low >>> 0 > this.low >>> 0) ? -1 : 1 } /** * * @param {Int64Other} divisor * @returns {Int64} */ divide (divisor) { if (!Int64.isInt64(divisor)) { divisor = Int64.from(divisor) } if (divisor.isZero) throw new BVONError('division by zero') // use wasm support if present if (wasm) { // guard against signed division overflow: the largest // negative number / -1 would be 1 larger than the largest // positive number, due to two's complement. if ( !this.unsigned && this.high === -0x80000000 && divisor.low === -1 && divisor.high === -1 ) { // be consistent with non-wasm code path return this } const low = (this.unsigned ? wasm.div_u : wasm.div_s)( this.low, this.high, divisor.low, divisor.high ) return new Int64(low, wasm.get_high(), this.unsigned) } if (this.isZero) { return this.unsigned ? Int64.UNSIGNED_ZERO : Int64.ZERO } let approx, rem, res if (!this.unsigned) { // This section is only relevant for signed longs and is derived from the // closure library as a whole. if (this.equals(Int64.MIN_VALUE)) { if ( divisor.equals(Int64.ONE) || divisor.equals(Int64.NEGATIVE_ONE) ) { return Int64.MIN_VALUE } else if (divisor.equals(Int64.MIN_VALUE)) { return Int64.ONE } else { // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. const halfThis = this.shiftRight(1) approx = halfThis.divide(divisor).shiftLeft(1) if (approx.equals(Int64.ZERO)) { return divisor.isNegative ? Int64.ONE : Int64.NEGATIVE_ONE } else { rem = this.subtract(divisor.multiply(approx)) res = approx.add(rem.divide(divisor)) return res } } } else if (divisor.equals(Int64.MIN_VALUE)) { return this.unsigned ? Int64.UNSIGNED_ZERO : Int64.ZERO } if (this.isNegative) { if (divisor.isNegative) return this.negate().divide(divisor.negate()) return this.negate().divide(divisor).negate() } else if (divisor.isNegative) return this.divide(divisor.negate()).negate() res = Int64.ZERO } else { // The algorithm below has not been made for unsigned longs. It's therefore // required to take special care of the MSB prior to running it. if (!divisor.unsigned) divisor = divisor.toUnsigned() if (divisor.greaterThan(this)) return Int64.UNSIGNED_ZERO if (divisor.greaterThan(this.shiftRightUnsigned(1))) { // 15 >>> 1 = 7 ; with divisor = 8 ; true return Int64.UNSIGNED_ONE } res = Int64.UNSIGNED_ZERO } // Repeat the following until the remainder is less than other: find a // floating-point that approximates remainder / other *from below*, add this // into the result, and subtract it from the remainder. It is critical that // the approximate value is less than or equal to the real value so that the // remainder never becomes negative. rem = this while (rem.greaterThanOrEquals(divisor)) { // Approximate the result of division. This may be a little greater or // smaller than the actual value. approx = Math.max(1, Math.floor(rem / divisor)) // We will tweak the approximate result by changing it in the 48-th digit or // the smallest non-fractional digit, whichever is larger. const log2 = Math.ceil(Math.log(approx) / Math.LN2) const delta = log2 <= 48 ? 1 : Math.pow(2, log2 - 48) // Decrease the approximation until it is smaller than the remainder. Note // that if it is too large, the product overflows and is negative. let approxRes = fromNumber(approx) let approxRem = approxRes.multiply(divisor) while (approxRem.isNegative || approxRem.greaterThan(rem)) { approx -= delta approxRes = fromNumber(approx, this.unsigned) approxRem = approxRes.multiply(divisor) } // We know the answer can't be zero... and actually, zero would cause // infinite recursion since we would make no progress. if (approxRes.isZero) approxRes = Int64.ONE res = res.add(approxRes) rem = rem.subtract(approxRem) } return res } /** * * @param {Int64Other} other * @returns {boolean} */ equals (other) { if (!Int64.isInt64(other)) { other = Int64.from(other) } if ( this.unsigned !== other.unsigned && this.high >>> 31 === 1 && other.high >>> 31 === 1 ) { return false } return this.high === other.high && this.low === other.low } greaterThan (other) { return this.compare(other) > 0 } greaterThanOrEqual (other) { return this.compare(other) >= 0 } lessThan (other) { return this.compare(other) < 0 } lessThanOrEqual (other) { return this.compare(other) <= 0 } mod (divisor) { if (!Int64.isInt64(divisor)) { divisor = Int64.from(divisor) } // use wasm support if present if (wasm) { const low = (this.unsigned ? wasm.rem_u : wasm.rem_s)( this.low, this.high, divisor.low, divisor.high ) return new Int64(low, wasm.get_high(), this.unsigned) } return this.subtract(this.divide(divisor).multiply(divisor)) } /** * Returns the product of this and the specified Long. * @param {Int64Other} multiplier - Multiplier * @returns {Int64} */ multiply (multiplier) { if (this.isZero) return Int64.ZERO if (!Int64.isInt64(multiplier)) { multiplier = Int64.from(multiplier) } // use wasm support if present if (wasm) { const low = wasm.mul(this.low, this.high, multiplier.low, multiplier.high) return new Int64(low, wasm.get_high(), this.unsigned) } if (multiplier.isZero) return Int64.ZERO if (this.equals(Int64.MIN_VALUE)) { return multiplier.isOdd ? Int64.MIN_VALUE : Int64.ZERO } if (multiplier.equals(Int64.MIN_VALUE)) { return this.isOdd ? Int64.MIN_VALUE : Int64.ZERO } if (this.isNegative) { return multiplier.isNegative ? this.negate().multiply(multiplier.negate()) : this.negate().multiply(multiplier).negate() } else if (multiplier.isNegative) { return this.multiply(multiplier.negate()).negate() } // If both longs are small, use float multiplication if (this.lessThan(TWO_PWR_24) && multiplier.lessThan(TWO_PWR_24)) { return fromNumber(this * multiplier, this.unsigned) } // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products. // We can skip products that would overflow. const a48 = this.high >>> 16 const a32 = this.high & 0xffff const a16 = this.low >>> 16 const a00 = this.low & 0xffff const b48 = multiplier.high >>> 16 const b32 = multiplier.high & 0xffff const b16 = multiplier.low >>> 16 const b00 = multiplier.low & 0xffff let c48 = 0 let c32 = 0 let c16 = 0 let c00 = 0 c00 += a00 * b00 c16 += c00 >>> 16 c00 &= 0xffff c16 += a16 * b00 c32 += c16 >>> 16 c16 &= 0xffff c16 += a00 * b16 c32 += c16 >>> 16 c16 &= 0xffff c32 += a32 * b00 c48 += c32 >>> 16 c32 &= 0xffff c32 += a16 * b16 c48 += c32 >>> 16 c32 &= 0xffff c32 += a00 * b32 c48 += c32 >>> 16 c32 &= 0xffff c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48 c48 &= 0xffff return new Int64((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned) } negate () { if (!this.unsigned && this.equals(Int64.MIN_VALUE)) { return Int64.MIN_VALUE } return this.not().add(Int64.ONE) } not () { return new Int64(~this.low, ~this.high, this.unsigned) } notEquals (other) { return !this.equals(other) } or (other) { if (!Int64.isInt64(other)) { other = Int64.from(other) } return new Int64( this.low | other.low, this.high | other.high, this.unsigned ) } remainder (divisor) { return this.mod(divisor) } shiftLeft (numBits) { if (Int64.isInt64(numBits)) { numBits = numBits.toInt32() } if ((numBits &= 63) === 0) { return this } else if (numBits < 32) { return new Int64( this.low << numBits, (this.high << numBits) | (this.low >>> (32 - numBits)), this.unsigned ) } else { return new Int64(0, this.low << (numBits - 32), this.unsigned) } } shiftRight (numBits) { if (Int64.isInt64(numBits)) { numBits = numBits.toInt32() } if ((numBits &= 63) === 0) { return this } else if (numBits < 32) { return new Int64( (this.low >>> numBits) | (this.high << (32 - numBits)), this.high >> numBits, this.unsigned ) } else { return new Int64( this.high >> (numBits - 32), this.high >= 0 ? 0 : -1, this.unsigned ) } } shiftRightUnsigned (numBits) { if (Int64.isInt64(numBits)) { numBits = numBits.toInt32() } if ((numBits &= 63) === 0) { return this } else if (numBits < 32) { return new Int64( (this.low >>> numBits) | (this.high << (32 - numBits)), this.high >>> numBits, this.unsigned ) } else if (numBits === 32) { return new Int64(this.high, 0, this.unsigned) } else { return new Int64(this.high >> (numBits - 32), 0, this.unsigned) } } subtract (subtrahend) { if (!Int64.isInt64(subtrahend)) { subtrahend = Int64.from(subtrahend) } return this.add(subtrahend.negate()) } toBigInt () { return BigInt(this.toString()) } toBytes (isLittleEndian = false) { const hi = this.high const lo = this.low if (isLittleEndian) { return new ByteView([ lo & 0xff, (lo >>> 8) & 0xff, (lo >>> 16) & 0xff, lo >>> 24, hi & 0xff, (hi >>> 8) & 0xff, (hi >>> 16) & 0xff, hi >>> 24 ]) } else { return new ByteView([ hi >>> 24, (hi >>> 16) & 0xff, (hi >>> 8) & 0xff, hi & 0xff, lo >>> 24, (lo >>> 16) & 0xff, (lo >>> 8) & 0xff, lo & 0xff ]) } } toInt32 () { return this.unsigned ? this.low >>> 0 : this.low } toSigned () { if (!this.unsigned) return this return new Int64(this.low, this.high, false) } toString (radix = 10) { radix = radix || 10 if (radix < 2 || radix > 36) throw new BVONError('radix') if (this.isZero) return '0' if (this.isNegative) { // Unsigned Longs are never negative if (this.equals(Int64.MIN_VALUE)) { // 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. const radixLong = fromNumber(radix) const div = this.divide(radixLong) const rem1 = div.multiply(radixLong).subtract(this) return div.toString(radix) + rem1.toInt32().toString(radix) } else return '-' + this.negate().toString(radix) } // Do several (6) digits each time through the loop, so as to // minimize the calls to the very expensive emulated div. const radixToPower = fromNumber(Math.pow(radix, 6), this.unsigned) let rem = this let result = '' // eslint-disable-next-line no-constant-condition while (true) { const remDiv = rem.divide(radixToPower) const intval = rem.subtract(remDiv.multiply(radixToPower)).toInt32() >>> 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 } } } toUnsigned () { if (this.unsigned) return this return new Int64(this.low, this.high, true) } valueOf () { return this.unsigned ? (this.high >>> 0) * TWO_PWR_32_DBL + (this.low >>> 0) : this.high * TWO_PWR_32_DBL + (this.low >>> 0) } xor (other) { if (!Int64.isInt64(other)) { other = Int64.from(other) } return new Int64( this.low ^ other.low, this.high ^ other.high, this.unsigned ) } [Symbol.toPrimitive] (hint) { switch (hint) { case 'string': return this.toString() case 'number': default: return this.valueOf() } } [Symbol.for('nodejs.util.inspect.custom')] () { return `<Int64(\x1b[32m'${this.toString()}'\x1b[0m) \x1b[33m${this.low}\x1b[0m \x1b[33m${this.high}\x1b[0m \x1b[33m${this.unsigned}\x1b[0m />` } inspect () { return `<Int64('${this.toString()}') ${this.low} ${this.high} ${this.unsigned} />` } } const TWO_PWR_24 = (() => fromInt(TWO_PWR_24_DBL))() function fromInt (value, unsigned) { let obj, cachedObj, cache if (unsigned) { value >>>= 0 if ((cache = value >= 0 && value < 256)) { cachedObj = UINT_CACHE.get(value) if (cachedObj) return cachedObj } obj = new Int64(value, (value | 0) < 0 ? -1 : 0, true) if (cache) UINT_CACHE.set(value, obj) return obj } else { value |= 0 if ((cache = value >= -128 && value < 128)) { cachedObj = INT_CACHE.get(value) if (cachedObj) return cachedObj } obj = new Int64(value, value < 0 ? -1 : 0, false) if (cache) INT_CACHE.set(value, obj) return obj } } function fromNumber (value, unsigned) { if (isNaN(value)) { return unsigned ? Int64.UNSIGNED_ZERO : Int64.ZERO } if (unsigned) { if (value < 0) return Int64.UNSIGNED_ZERO if (value >= TWO_PWR_64_DBL) return Int64.MAX_UNSIGNED_VALUE } else { if (value <= -TWO_PWR_63_DBL) return Int64.MIN_VALUE if (value + 1 >= TWO_PWR_63_DBL) return Int64.MAX_VALUE } if (value < 0) return fromNumber(-value, unsigned).negate() return new Int64((value % TWO_PWR_32_DBL) | 0, (value / TWO_PWR_32_DBL) | 0, unsigned) } function fromBytes (bytes, unsigned, isLittleEndian = false) { if (isLittleEndian) { return new Int64( bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24), bytes[4] | (bytes[5] << 8) | (bytes[6] << 16) | (bytes[7] << 24), unsigned ) } else { return new Int64( (bytes[4] << 24) | (bytes[5] << 16) | (bytes[6] << 8) | bytes[7], (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3], unsigned ) } } function fromString (str, unsigned, radix) { if (str.length === 0) throw new BVONError('empty string') if ( str === 'NaN' || str === 'Infinity' || str === '+Infinity' || str === '-Infinity' ) { return Int64.ZERO } if (typeof unsigned === 'number') { // For goog.math.long compatibility radix = unsigned unsigned = false } else { unsigned = !!unsigned } radix = radix || 10 if (radix < 2 || radix > 36) throw new BVONError('radix') let p if ((p = str.indexOf('-')) > 0) throw new BVONError('interior hyphen') else if (p === 0) { return fromString(str.substring(1), unsigned, radix).negate() } // Do several (8) digits each time through the loop, so as to // minimize the calls to the very expensive emulated div. const radixToPower = fromNumber(Math.pow(radix, 8)) let result = Int64.ZERO let index = 0 while (index < str.length) { const size = Math.min(8, str.length - index) const value = parseInt(str.substring(index, index + size), radix) if (size < 8) { const power = fromNumber(Math.pow(radix, size)) result = result.multiply(power).add(fromNumber(value)) } else { result = result.multiply(radixToPower) result = result.add(fromNumber(value)) } index += 8 } return new Int64(result[0], result[1], unsigned) }