Source: BaseValue.js

const bitmask = bitsNumber => -1 >>> (32 - bitsNumber);

const check = (byteSize, value, isSigned) => {
	let lowerBound;
	let upperBound;
	if (8 === byteSize) {
		if ('bigint' !== typeof value)
			throw new TypeError(`"value" (${value}) has invalid type, expected BigInt`);

		lowerBound = isSigned ? -0x80000000_00000000n : 0n;
		upperBound = isSigned ? 0x7FFFFFFF_FFFFFFFFn : 0xFFFFFFFF_FFFFFFFFn;
	} else {
		if (!Number.isInteger(value))
			throw new RangeError(`"value" (${value}) is not an integer`);

		// note: all expressions are tricky, they're used due to JS dumbness re 1<<32 == 0 and 1<<31 = min signed int
		lowerBound = isSigned ? -bitmask((8 * byteSize) - 1) - 1 : 0;
		upperBound = isSigned ? bitmask((8 * byteSize) - 1) : bitmask(8 * byteSize);
	}

	if (value < lowerBound || value > upperBound)
		throw RangeError(`"value" (${value}) is outside of valid ${8 * byteSize}-bit range`);

	return value;
};

/**
 * Represents a base integer.
 */
class BaseValue {
	/**
	 * Creates a base value.
	 * @param {number} size Size of the integer.
	 * @param {number|BigInt} value Value.
	 * @param {boolean} isSigned Should the value be treated as signed.
	 */
	constructor(size, value, isSigned = false) {
		this.size = size;
		this.isSigned = isSigned;
		this.value = check(size, value, isSigned);
	}

	/**
	 * Converts base value to string.
	 * @returns {string} String representation.
	 */
	toString() {
		let unsignedValue;
		if (!this.isSigned || 0 <= this.value) {
			unsignedValue = this.value;
		} else {
			const upperBoundPlusOne = (8 === this.size ? 0x1_00000000_00000000n : bitmask(this.size * 8) + 1);
			unsignedValue = this.value + upperBoundPlusOne;
		}

		return `0x${unsignedValue.toString(16).toUpperCase().padStart(this.size * 2, '0')}`;
	}
}

module.exports = { BaseValue };