diff --git a/packages/bigint-constrained/README.md b/packages/bigint-constrained/README.md
new file mode 100644
index 0000000..0f47eb7
--- /dev/null
+++ b/packages/bigint-constrained/README.md
@@ -0,0 +1,90 @@
+# @vekexasia/bigint-constrained: BigInt wrapper for boundaries checking
+
+
+
+This project is part of the [bigint-swissknife](https://github.com/vekexasia/bigint-swissknife) project. It aims to monkeypatch the Buffer native class adding
+support for BigInts. This is useful when working with Node.js.
+
+## Why?
+
+Sometimes you need to work with a bounded BigInt. This library provides a simple wrapper around BigInt that allows you to specify a minimum and maximum value for the BigInt.
+
+For example, if you need to make sure the bigint you work with is at max 255 (aka uint8), you can use the following:
+
+```typescript
+import { u8 } from '@vekexasia/bigint-constrained';
+
+const a = u8(255n);
+const b = u8(256n); // throws an error
+const c = u8(-1n); // throws an error
+
+a.add(1n); // throws an error
+```
+
+Please notice:
+
+ - Every operation performs boundaries checking and throws an error if the operation would result in a value outside the boundaries.
+ - Every operation is **idempotent** on the calling instance and returns a **new instance** of the bounded BigInt with the same boundaries.
+
+
+## Documentation
+
+You can find typedoc documentation [here](https://vekexasia.github.io/bigint-swissknife/modules/_vekexasia_bigint_constrained.html).
+
+## Installation
+
+Add the library to your project:
+
+```bash
+npm install @vekexasia/bigint-constrained
+```
+
+or
+
+```bash
+yarn add @vekexasia/bigint-constrained
+```
+
+## Usage
+
+Simply import the library like shown above and then you can start using the methods to work with bounded BigInts.
+
+Right now the library exposes the following bounded BigInts:
+
+- `u8` (uint8)
+- `u16` (uint16)
+- `u32` (uint32)
+- `u64` (uint64)
+- `u128` (uint64)
+- `u256` (uint64)
+- `i8` (int8)
+- ...
+- `i256` (int256)
+
+If these are not sufficient you can always create your own like so:
+
+```typescript
+import {CheckedBigInt} from '@vekexasia/bigint-constrained';
+
+const u1024 = new CheckedBigInt(0n /*value*/, 1024 /*bits*/, false /*unsigned*/);
+```
+
+or custom bounds:
+
+```typescript
+import {CheckedBigInt} from '@vekexasia/bigint-constrained';
+
+const between10And20 = new CheckedBigInt(10n, {min: 10n, max: 20n});
+
+```
+
+## TypeScript
+
+The library is entirely written in TypeScript and comes with its own type definitions.
+
+## License
+
+This project is licensed under the MIT License - see the [LICENSE](../LICENSE) file for details.
diff --git a/packages/bigint-constrained/src/CheckedBigInt.ts b/packages/bigint-constrained/src/CheckedBigInt.ts
index bd5a6a2..3604ab1 100644
--- a/packages/bigint-constrained/src/CheckedBigInt.ts
+++ b/packages/bigint-constrained/src/CheckedBigInt.ts
@@ -4,51 +4,58 @@ export interface Bounds { min: bigint, max: bigint }
* A class that represents a signed or unsigned integer of a specific bit size.
*/
export class CheckedBigInt {
- readonly #bits: number
public readonly value: bigint
readonly boundaries: Bounds
/**
- * Creates a new CheckedBigInt.
- * @param bits - bits size for this instance
+ * Creates a new CheckedBigInt. with custom boundaries
* @param value - initial value
- * @param signed - if true, the integer is signed, otherwise it is unsigned . If Bounds, it is used as boundaries.
+ * @param bounds - the set boundaries for the instance
* @example
* ```ts
- * const a = new CheckedBigInt(8, 1n, false) // u8
- * const b = new CheckedBigInt(8, 1n, true) // i8
- * const c = new CheckedBigInt(8, 1n, { min: 0n, max: 2n }) // u8 with custom boundaries
+ * const a = new CheckedBigInt(1n, {min: 1n, max: 10n})
* ```
*/
- constructor (bits: number, value: bigint, signed: boolean | Bounds) {
- if (bits <= 0) {
- throw new RangeError('CheckedBigInt: bits must be greater than 0')
- }
- this.#bits = bits
- this.value = value
- if (typeof (signed) === 'boolean') {
+ constructor(value:bigint, bounds: Bounds);
+ /**
+ * Creates a new CheckedBigInt. with a specific bit size and signedness
+ * @param value - initial value
+ * @param bits - the bit size of the integer
+ * @param signed - whether the integer is signed or not
+ * @example
+ * ```ts
+ * const a = new CheckedBigInt(1n /* value *\/, 8 /*bits*\/, false/*unsigned*\/)
+ * ```
+ */
+ constructor(value:bigint, bits: number, signed: boolean);
+
+ constructor (value: bigint, info: number|Bounds, signed?: boolean ) {
+ if (typeof (info ) === 'number') {
+ const bits = info;
+ if (bits <=0) {
+ throw new RangeError('CheckedBigInt: bit size must be positive')
+ }
if (signed) {
- if (bits < 2) {
- throw new RangeError('CheckedBigInt: signed bit size must be at least 2')
- }
this.boundaries = {
- min: -(2n ** BigInt(this.#bits - 1)),
- max: (2n ** BigInt(this.#bits - 1)) - 1n
+ min: -(2n ** BigInt(bits - 1)),
+ max: (2n ** BigInt(bits - 1)) - 1n
}
} else {
this.boundaries = {
min: 0n,
- max: (2n ** BigInt(this.#bits)) - 1n
+ max: (2n ** BigInt(bits)) - 1n
}
}
} else {
- this.boundaries = signed
+ this.boundaries = info;
}
+ this.value = value
+
if (this.value > this.boundaries.max) {
- throw new RangeError(`CheckedBigInt: value ${this.value} exceeds bit size ${this.#bits} - max ${this.boundaries.max}`)
+ throw new RangeError(`CheckedBigInt: value ${this.value} exceeds boundaries - > max ${this.boundaries.max}`)
}
if (this.value < this.boundaries.min) {
- throw new RangeError(`CheckedBigInt: value ${this.value} exceeds bit size ${this.#bits} - min ${this.boundaries.min}`)
+ throw new RangeError(`CheckedBigInt: value ${this.value} exceeds boundaries - < min ${this.boundaries.min}`)
}
}
@@ -59,12 +66,12 @@ export class CheckedBigInt {
* @throws RangeError If the result of the addition would exceed the bit size.
* @example
* ```ts
- * const a = i8(1n).checkedAdd(1n) // 2
- * const b = i8(127n).checkedAdd(1n) // throws RangeError
+ * const a = i8(1n).add(1n) // 2
+ * const b = i8(127n).add(1n) // throws RangeError
* ```
*/
- checkedAdd (other: bigint): CheckedBigInt {
- return new CheckedBigInt(this.#bits, this.value + other, this.boundaries)
+ add (other: bigint): CheckedBigInt {
+ return new CheckedBigInt(this.value + other, this.boundaries)
}
/**
@@ -73,8 +80,8 @@ export class CheckedBigInt {
* @returns A new CheckedBigInt with the result of the subtraction.
* @throws RangeError If the result of the subtraction would exceed the bit size.
*/
- checkedSub (other: bigint): CheckedBigInt {
- return new CheckedBigInt(this.#bits, this.value - other, this.boundaries)
+ sub (other: bigint): CheckedBigInt {
+ return new CheckedBigInt(this.value - other, this.boundaries)
}
/**
@@ -83,8 +90,8 @@ export class CheckedBigInt {
* @returns A new CheckedBigInt with the result of the multiplication.
* @throws RangeError If the result of the multiplication would exceed the bit size.
*/
- checkedMul (other: bigint): CheckedBigInt {
- return new CheckedBigInt(this.#bits, this.value * other, this.boundaries)
+ mul (other: bigint): CheckedBigInt {
+ return new CheckedBigInt(this.value * other, this.boundaries)
}
/**
@@ -94,14 +101,14 @@ export class CheckedBigInt {
* @throws RangeError If the result of the division would exceed the bit size.
* @throws RangeError If the divisor is zero.
*/
- checkedDiv (other: bigint): CheckedBigInt {
+ div (other: bigint): CheckedBigInt {
if (other === 0n) {
throw new RangeError('CheckedBigInt: division by zero')
}
if (this.boundaries.min !== 0n && this.value === this.boundaries.min && other === -1n) {
throw new RangeError('CheckedBigInt: division overflow')
}
- return new CheckedBigInt(this.#bits, this.value / other, this.boundaries)
+ return new CheckedBigInt(this.value / other, this.boundaries)
}
/**
@@ -111,14 +118,14 @@ export class CheckedBigInt {
* @throws RangeError If the result of the remainder would exceed the bit size.
* @throws RangeError If the divisor is zero.
*/
- checkedRem (other: bigint): CheckedBigInt {
+ rem (other: bigint): CheckedBigInt {
if (other === 0n) {
throw new RangeError('CheckedBigInt: division by zero')
}
if (this.boundaries.min !== 0n && this.value === this.boundaries.min && other === -1n) {
throw new RangeError('CheckedBigInt: division overflow')
}
- return new CheckedBigInt(this.#bits, this.value % other, this.boundaries)
+ return new CheckedBigInt(this.value % other, this.boundaries)
}
/**
@@ -127,8 +134,8 @@ export class CheckedBigInt {
* @returns A new CheckedBigInt with the result of the power.
* @throws RangeError If the result of the power would exceed the bit size.
*/
- checkedPow (exponent: bigint): CheckedBigInt {
- return new CheckedBigInt(this.#bits, this.value ** exponent, this.boundaries)
+ pow (exponent: bigint): CheckedBigInt {
+ return new CheckedBigInt(this.value ** exponent, this.boundaries)
}
/**
@@ -138,11 +145,11 @@ export class CheckedBigInt {
* @throws RangeError If the number of bits is negative.
* @throws RangeError If the resulting number would exceed the bit size.
*/
- checkedShl (bits: number | bigint): CheckedBigInt {
+ shl (bits: number | bigint): CheckedBigInt {
if (bits < 0n) {
throw new RangeError('CheckedBigInt: shift count must be non-negative')
}
- return new CheckedBigInt(this.#bits, this.value << BigInt(bits), this.boundaries)
+ return new CheckedBigInt(this.value << BigInt(bits), this.boundaries)
}
/**
@@ -152,13 +159,11 @@ export class CheckedBigInt {
* @throws RangeError If the number of bits is negative.
* @throws RangeError If bits is greater than the bit size.
*/
- checkedShr (bits: number | bigint): CheckedBigInt {
+ shr (bits: number | bigint): CheckedBigInt {
if (bits < 0n) {
throw new RangeError('CheckedBigInt: shift count must be non-negative')
}
- if (bits > this.#bits) {
- throw new RangeError('CheckedBigInt: shift count must be less than bit size')
- }
- return new CheckedBigInt(this.#bits, this.value >> BigInt(bits), this.boundaries)
+
+ return new CheckedBigInt(this.value >> BigInt(bits), this.boundaries)
}
}
diff --git a/packages/bigint-constrained/src/index.ts b/packages/bigint-constrained/src/index.ts
index 89b9a7e..98f791c 100644
--- a/packages/bigint-constrained/src/index.ts
+++ b/packages/bigint-constrained/src/index.ts
@@ -8,7 +8,7 @@ export * from './CheckedBigInt.js'
* @throws RangeError - If the value exceeds the bit size.
* @returns A new CheckedBigInt constrained to u8
*/
-export const u8 = (value: bigint): CheckedBigInt => new CheckedBigInt(8, value, false)
+export const u8 = (value: bigint): CheckedBigInt => new CheckedBigInt(value,8, false)
/**
* Creates an u16 CheckedBigInt.
@@ -16,7 +16,7 @@ export const u8 = (value: bigint): CheckedBigInt => new CheckedBigInt(8, value,
* @throws RangeError - If the value exceeds the bit size.
* @returns A new CheckedBigInt constrained to u16
*/
-export const u16 = (value: bigint): CheckedBigInt => new CheckedBigInt(16, value, false)
+export const u16 = (value: bigint): CheckedBigInt => new CheckedBigInt(value,16, false)
/**
* Creates an u32 CheckedBigInt.
@@ -24,7 +24,7 @@ export const u16 = (value: bigint): CheckedBigInt => new CheckedBigInt(16, value
* @throws RangeError - If the value exceeds the bit size.
* @returns A new CheckedBigInt constrained to u32
*/
-export const u32 = (value: bigint): CheckedBigInt => new CheckedBigInt(32, value, false)
+export const u32 = (value: bigint): CheckedBigInt => new CheckedBigInt(value,32, false)
/**
* Creates an u64 CheckedBigInt.
@@ -32,7 +32,7 @@ export const u32 = (value: bigint): CheckedBigInt => new CheckedBigInt(32, value
* @throws RangeError - If the value exceeds the bit size.
* @returns A new CheckedBigInt constrained to u64
*/
-export const u64 = (value: bigint): CheckedBigInt => new CheckedBigInt(64, value, false)
+export const u64 = (value: bigint): CheckedBigInt => new CheckedBigInt(value,64, false)
/**
* Creates an u128 CheckedBigInt.
@@ -40,7 +40,7 @@ export const u64 = (value: bigint): CheckedBigInt => new CheckedBigInt(64, value
* @throws RangeError - If the value exceeds the bit size.
* @returns A new CheckedBigInt constrained to u128
*/
-export const u128 = (value: bigint): CheckedBigInt => new CheckedBigInt(128, value, false)
+export const u128 = (value: bigint): CheckedBigInt => new CheckedBigInt(value,128, false)
/**
* Creates an u256 CheckedBigInt.
@@ -48,7 +48,7 @@ export const u128 = (value: bigint): CheckedBigInt => new CheckedBigInt(128, val
* @throws RangeError - If the value exceeds the bit size.
* @returns A new CheckedBigInt constrained to u256
*/
-export const u256 = (value: bigint): CheckedBigInt => new CheckedBigInt(256, value, false)
+export const u256 = (value: bigint): CheckedBigInt => new CheckedBigInt(value,256, false)
/**
* Creates an i8 CheckedBigInt.
@@ -56,7 +56,7 @@ export const u256 = (value: bigint): CheckedBigInt => new CheckedBigInt(256, val
* @throws RangeError - If the value exceeds the bit size.
* @returns A new CheckedBigInt constrained to i8
*/
-export const i8 = (value: bigint): CheckedBigInt => new CheckedBigInt(8, value, true)
+export const i8 = (value: bigint): CheckedBigInt => new CheckedBigInt(value,8, true)
/**
* Creates an i16 CheckedBigInt.
@@ -64,7 +64,7 @@ export const i8 = (value: bigint): CheckedBigInt => new CheckedBigInt(8, value,
* @throws RangeError - If the value exceeds the bit size.
* @returns A new CheckedBigInt constrained to i16
*/
-export const i16 = (value: bigint): CheckedBigInt => new CheckedBigInt(16, value, true)
+export const i16 = (value: bigint): CheckedBigInt => new CheckedBigInt(value,16, true)
/**
* Creates an i32 CheckedBigInt.
@@ -72,7 +72,7 @@ export const i16 = (value: bigint): CheckedBigInt => new CheckedBigInt(16, value
* @throws RangeError - If the value exceeds the bit size.
* @returns A new CheckedBigInt constrained to i32
*/
-export const i32 = (value: bigint): CheckedBigInt => new CheckedBigInt(32, value, true)
+export const i32 = (value: bigint): CheckedBigInt => new CheckedBigInt(value,32, true)
/**
* Creates an i64 CheckedBigInt.
@@ -80,7 +80,7 @@ export const i32 = (value: bigint): CheckedBigInt => new CheckedBigInt(32, value
* @throws RangeError - If the value exceeds the bit size.
* @returns A new CheckedBigInt constrained to i64
*/
-export const i64 = (value: bigint): CheckedBigInt => new CheckedBigInt(64, value, true)
+export const i64 = (value: bigint): CheckedBigInt => new CheckedBigInt(value,64, true)
/**
* Creates an i128 CheckedBigInt.
@@ -88,7 +88,7 @@ export const i64 = (value: bigint): CheckedBigInt => new CheckedBigInt(64, value
* @throws RangeError - If the value exceeds the bit size.
* @returns A new CheckedBigInt constrained to i128
*/
-export const i128 = (value: bigint): CheckedBigInt => new CheckedBigInt(128, value, true)
+export const i128 = (value: bigint): CheckedBigInt => new CheckedBigInt(value,128, true)
/**
* Creates an i256 CheckedBigInt.
@@ -96,4 +96,4 @@ export const i128 = (value: bigint): CheckedBigInt => new CheckedBigInt(128, val
* @throws RangeError - If the value exceeds the bit size.
* @returns A new CheckedBigInt constrained to i256
*/
-export const i256 = (value: bigint): CheckedBigInt => new CheckedBigInt(256, value, true)
+export const i256 = (value: bigint): CheckedBigInt => new CheckedBigInt(value,256, true)
diff --git a/packages/bigint-constrained/test/CheckedBigInt.test.ts b/packages/bigint-constrained/test/CheckedBigInt.test.ts
index 0ce48c6..ae9e40a 100644
--- a/packages/bigint-constrained/test/CheckedBigInt.test.ts
+++ b/packages/bigint-constrained/test/CheckedBigInt.test.ts
@@ -17,114 +17,114 @@ describe('CheckedBigInt', () => {
expect(u8(127n).boundaries).to.deep.eq({ min: 0n, max: 255n })
})
it('should throw on invalid bit size', () => {
- expect(() => new CheckedBigInt(0, 0n, false)).to.throw(RangeError)
- expect(() => new CheckedBigInt(-1, 0n, false)).to.throw(RangeError)
+ expect(() => new CheckedBigInt(0n, 0, false)).to.throw(RangeError)
+ expect(() => new CheckedBigInt(0n, -1, false)).to.throw(RangeError)
})
it('should allow custom boundaries', () => {
- const a = new CheckedBigInt(8, 0n, { min: 0n, max: 2n })
+ const a = new CheckedBigInt(0n, { min: 0n, max: 2n })
expect(a.boundaries).to.deep.eq({ min: 0n, max: 2n })
})
})
- describe('checkedAdd', () => {
+ describe('add', () => {
it('should should return added value', () => {
- const a = i8(1n).checkedAdd(1n)
+ const a = i8(1n).add(1n)
expect(a.value).to.eq(2n)
})
it('should throw on overflow', () => {
- expect(() => i8(127n).checkedAdd(1n)).to.throw(RangeError)
- expect(() => i8(-128n).checkedAdd(-1n)).to.throw(RangeError)
+ expect(() => i8(127n).add(1n)).to.throw(RangeError)
+ expect(() => i8(-128n).add(-1n)).to.throw(RangeError)
})
})
- describe('checkedSub', () => {
+ describe('sub', () => {
it('should should return subtracted value', () => {
- const a = i8(1n).checkedSub(1n)
+ const a = i8(1n).sub(1n)
expect(a.value).to.eq(0n)
})
it('should throw on overflow', () => {
- expect(() => i8(-128n).checkedSub(1n)).to.throw(RangeError)
- expect(() => i8(127n).checkedSub(-1n)).to.throw(RangeError)
+ expect(() => i8(-128n).sub(1n)).to.throw(RangeError)
+ expect(() => i8(127n).sub(-1n)).to.throw(RangeError)
})
})
- describe('checkedRem', () => {
+ describe('rem', () => {
it('should return remainder', () => {
- const a = i8(5n).checkedRem(3n)
+ const a = i8(5n).rem(3n)
expect(a.value).to.eq(2n)
})
it('should throw on division by zero', () => {
- expect(() => i8(5n).checkedRem(0n)).to.throw(RangeError)
+ expect(() => i8(5n).rem(0n)).to.throw(RangeError)
})
it('should throw on overflow', () => {
- expect(() => i8(-128n).checkedRem(-1n)).to.throw(RangeError)
+ expect(() => i8(-128n).rem(-1n)).to.throw(RangeError)
})
})
- describe('checkedMul', () => {
+ describe('mul', () => {
it('should return multiplication', () => {
- const a = i8(5n).checkedMul(3n)
+ const a = i8(5n).mul(3n)
expect(a.value).to.eq(15n)
})
it('should throw on overflow', () => {
- expect(() => i8(15n).checkedMul(8n)).to.not.throw(RangeError)
- expect(() => i8(16n).checkedMul(8n)).to.throw(RangeError)
+ expect(() => i8(15n).mul(8n)).to.not.throw(RangeError)
+ expect(() => i8(16n).mul(8n)).to.throw(RangeError)
})
it('should throw if multiplication turns to negative in UINT', () => {
- expect(() => u8(1n).checkedMul(-1n)).to.throw(RangeError)
+ expect(() => u8(1n).mul(-1n)).to.throw(RangeError)
})
})
- describe('checkedDiv', () => {
+ describe('div', () => {
it('should return division', () => {
- const a = i8(5n).checkedDiv(3n)
+ const a = i8(5n).div(3n)
expect(a.value).to.eq(1n)
})
it('should throw on division by zero', () => {
- expect(() => i8(5n).checkedDiv(0n)).to.throw(RangeError)
+ expect(() => i8(5n).div(0n)).to.throw(RangeError)
})
it('should throw on overflow', () => {
- expect(() => i8(-128n).checkedDiv(-1n)).to.throw(RangeError)
+ expect(() => i8(-128n).div(-1n)).to.throw(RangeError)
})
})
- describe('checkedPow', () => {
+ describe('pow', () => {
it('should return power', () => {
- const a = i8(2n).checkedPow(3n)
+ const a = i8(2n).pow(3n)
expect(a.value).to.eq(8n)
})
it('should throw on overflow', () => {
- expect(() => i8(2n).checkedPow(7n)).to.throw(RangeError)
+ expect(() => i8(2n).pow(7n)).to.throw(RangeError)
})
it('should throw on negative exponent', () => {
- expect(() => i8(2n).checkedPow(-1n)).to.throw(RangeError)
+ expect(() => i8(2n).pow(-1n)).to.throw(RangeError)
})
})
- describe('checkedShl', () => {
+ describe('shl', () => {
it('should shift left', () => {
- const a = i8(1n).checkedShl(1n)
+ const a = i8(1n).shl(1n)
expect(a.value).to.eq(2n)
})
it('should throw on negative shift', () => {
- expect(() => i8(1n).checkedShl(-1n)).to.throw(RangeError)
+ expect(() => i8(1n).shl(-1n)).to.throw(RangeError)
})
it('should throw on overflow', () => {
- expect(() => i8(64n).checkedShl(1n)).to.throw(RangeError)
+ expect(() => i8(64n).shl(1n)).to.throw(RangeError)
})
})
- describe('checkedShr', () => {
+ describe('shr', () => {
it('should shift right', () => {
- const a = i8(2n).checkedShr(1n)
+ const a = i8(2n).shr(1n)
expect(a.value).to.eq(1n)
})
it('should throw on negative shift', () => {
- expect(() => i8(1n).checkedShr(-1n)).to.throw(RangeError)
+ expect(() => i8(1n).shr(-1n)).to.throw(RangeError)
})
it('should throw on more bits', () => {
- expect(() => i8(-128n).checkedShr(8n)).to.not.throw(RangeError)
- expect(() => i8(-128n).checkedShr(9n)).to.throw(RangeError)
+ expect(() => i8(-128n).shr(8n)).to.not.throw(RangeError)
+
})
})