Skip to content

Commit

Permalink
added contrained readme and refactored some methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
vekexasia committed Mar 16, 2024
1 parent 979beb9 commit 29a5d63
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 93 deletions.
90 changes: 90 additions & 0 deletions packages/bigint-constrained/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# @vekexasia/bigint-constrained: BigInt wrapper for boundaries checking

<img src="https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white"/> <img
src="https://img.shields.io/badge/rollup-323330?style=for-the-badge&logo=rollup.js&logoColor=Brown"/> <img
src="https://img.shields.io/badge/eslint-3A33D1?style=for-the-badge&logo=eslint&logoColor=white"/> <img
src="https://img.shields.io/badge/vitest-6E9F18?style=for-the-badge&logo=vitest&logoColor=white"/>

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.
93 changes: 49 additions & 44 deletions packages/bigint-constrained/src/CheckedBigInt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`)
}
}

Expand All @@ -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)
}

/**
Expand All @@ -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)
}

/**
Expand All @@ -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)
}

/**
Expand All @@ -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)
}

/**
Expand All @@ -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)
}

/**
Expand All @@ -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)
}

/**
Expand All @@ -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)
}

/**
Expand All @@ -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)
}
}
24 changes: 12 additions & 12 deletions packages/bigint-constrained/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,92 +8,92 @@ 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.
* @param value - initial 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.
* @param value - initial 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.
* @param value - initial 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.
* @param value - initial 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.
* @param value - initial value
* @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.
* @param value - initial value
* @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.
* @param value - initial 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.
* @param value - initial 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.
* @param value - initial 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.
* @param value - initial 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.
* @param value - initial value
* @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)
Loading

0 comments on commit 29a5d63

Please sign in to comment.