From 975ca98f9a4471c682ea49766efbdabad1e0c3ad Mon Sep 17 00:00:00 2001 From: robonen Date: Mon, 30 Sep 2024 06:20:09 +0700 Subject: [PATCH] feat(packages/stdlib): add bigint math utils --- .../src/math/bigint/clampBigInt/index.test.ts | 35 ++++++++ .../src/math/bigint/clampBigInt/index.ts | 16 ++++ .../src/math/bigint/lerpBigInt/index.test.ts | 83 +++++++++++++++++++ .../src/math/bigint/lerpBigInt/index.ts | 27 ++++++ .../src/math/bigint/maxBigInt/index.test.ts | 39 +++++++++ .../stdlib/src/math/bigint/maxBigInt/index.ts | 15 ++++ .../src/math/bigint/minBigInt/index.test.ts | 39 +++++++++ .../stdlib/src/math/bigint/minBigInt/index.ts | 15 ++++ .../src/math/bigint/remapBigInt/index.test.ts | 32 +++++++ .../src/math/bigint/remapBigInt/index.ts | 23 +++++ packages/stdlib/src/math/index.ts | 11 ++- 11 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 packages/stdlib/src/math/bigint/clampBigInt/index.test.ts create mode 100644 packages/stdlib/src/math/bigint/clampBigInt/index.ts create mode 100644 packages/stdlib/src/math/bigint/lerpBigInt/index.test.ts create mode 100644 packages/stdlib/src/math/bigint/lerpBigInt/index.ts create mode 100644 packages/stdlib/src/math/bigint/maxBigInt/index.test.ts create mode 100644 packages/stdlib/src/math/bigint/maxBigInt/index.ts create mode 100644 packages/stdlib/src/math/bigint/minBigInt/index.test.ts create mode 100644 packages/stdlib/src/math/bigint/minBigInt/index.ts create mode 100644 packages/stdlib/src/math/bigint/remapBigInt/index.test.ts create mode 100644 packages/stdlib/src/math/bigint/remapBigInt/index.ts diff --git a/packages/stdlib/src/math/bigint/clampBigInt/index.test.ts b/packages/stdlib/src/math/bigint/clampBigInt/index.test.ts new file mode 100644 index 0000000..9a59501 --- /dev/null +++ b/packages/stdlib/src/math/bigint/clampBigInt/index.test.ts @@ -0,0 +1,35 @@ +import {describe, it, expect} from 'vitest'; +import {clampBigInt} from './index'; + +describe('clampBigInt', () => { + it('clamp a value within the given range', () => { + // value < min + expect(clampBigInt(-10n, 0n, 100n)).toBe(0n); + + // value > max + expect(clampBigInt(200n, 0n, 100n)).toBe(100n); + + // value within range + expect(clampBigInt(50n, 0n, 100n)).toBe(50n); + + // value at min + expect(clampBigInt(0n, 0n, 100n)).toBe(0n); + + // value at max + expect(clampBigInt(100n, 0n, 100n)).toBe(100n); + + // value at midpoint + expect(clampBigInt(50n, 100n, 100n)).toBe(100n); + }); + + it('handle edge cases', () => { + // all values are the same + expect(clampBigInt(5n, 5n, 5n)).toBe(5n); + + // min > max + expect(clampBigInt(10n, 100n, 50n)).toBe(50n); + + // negative range and value + expect(clampBigInt(-10n, -100n, -5n)).toBe(-10n); + }); +}); \ No newline at end of file diff --git a/packages/stdlib/src/math/bigint/clampBigInt/index.ts b/packages/stdlib/src/math/bigint/clampBigInt/index.ts new file mode 100644 index 0000000..42a3fac --- /dev/null +++ b/packages/stdlib/src/math/bigint/clampBigInt/index.ts @@ -0,0 +1,16 @@ +import {minBigInt} from '../minBigInt'; +import {maxBigInt} from '../maxBigInt'; + +/** + * Clamps a bigint between a minimum and maximum value + * + * @param {bigint} value The number to clamp + * @param {bigint} min Minimum value + * @param {bigint} max Maximum value + * @returns {bigint} The clamped number + * + * @since 0.0.2 + */ +export function clampBigInt(value: bigint, min: bigint, max: bigint) { + return minBigInt(maxBigInt(value, min), max); +} diff --git a/packages/stdlib/src/math/bigint/lerpBigInt/index.test.ts b/packages/stdlib/src/math/bigint/lerpBigInt/index.test.ts new file mode 100644 index 0000000..4d40c94 --- /dev/null +++ b/packages/stdlib/src/math/bigint/lerpBigInt/index.test.ts @@ -0,0 +1,83 @@ +import {describe, it, expect} from 'vitest'; +import {inverseLerpBigInt, lerpBigInt} from './index'; + +const MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER); + +describe('lerpBigInt', () => { + it('interpolates between two bigint values', () => { + const result = lerpBigInt(0n, 10n, 0.5); + expect(result).toBe(5n); + }); + + it('returns start value when t is 0', () => { + const result = lerpBigInt(0n, 10n, 0); + expect(result).toBe(0n); + }); + + it('returns end value when t is 1', () => { + const result = lerpBigInt(0n, 10n, 1); + expect(result).toBe(10n); + }); + + it('handles negative interpolation values', () => { + const result = lerpBigInt(0n, 10n, -0.5); + expect(result).toBe(-5n); + }); + + it('handles interpolation values greater than 1', () => { + const result = lerpBigInt(0n, 10n, 1.5); + expect(result).toBe(15n); + }); +}); + +describe('inverseLerpBigInt', () => { + it('returns 0 when value is start', () => { + const result = inverseLerpBigInt(0n, 10n, 0n); + expect(result).toBe(0); + }); + + it('returns 1 when value is end', () => { + const result = inverseLerpBigInt(0n, 10n, 10n); + expect(result).toBe(1); + }); + + it('interpolates correctly between two bigint values', () => { + const result = inverseLerpBigInt(0n, 10n, 5n); + expect(result).toBe(0.5); + }); + + it('handles values less than start', () => { + const result = inverseLerpBigInt(0n, 10n, -5n); + expect(result).toBe(-0.5); + }); + + it('handles values greater than end', () => { + const result = inverseLerpBigInt(0n, 10n, 15n); + expect(result).toBe(1.5); + }); + + it('handles same start and end values', () => { + const result = inverseLerpBigInt(10n, 10n, 10n); + expect(result).toBe(0); + }); + + it('handles the maximum safe integer correctly', () => { + const result = inverseLerpBigInt(0n, MAX_SAFE_INTEGER, MAX_SAFE_INTEGER); + expect(result).toBe(1); + }); + + it('handles values just above the maximum safe integer correctly', () => { + const result = inverseLerpBigInt(0n, MAX_SAFE_INTEGER, 0n); + expect(result).toBe(0); + }); + + it('handles values just below the maximum safe integer correctly', () => { + const result = inverseLerpBigInt(0n, MAX_SAFE_INTEGER, MAX_SAFE_INTEGER); + expect(result).toBe(1); + }); + + it('handles values just above the maximum safe integer correctly', () => { + const result = inverseLerpBigInt(0n, 2n ** 128n, 2n ** 127n); + expect(result).toBe(0.5); + }); +}); diff --git a/packages/stdlib/src/math/bigint/lerpBigInt/index.ts b/packages/stdlib/src/math/bigint/lerpBigInt/index.ts new file mode 100644 index 0000000..5006038 --- /dev/null +++ b/packages/stdlib/src/math/bigint/lerpBigInt/index.ts @@ -0,0 +1,27 @@ +/** + * Linearly interpolates between bigint values + * + * @param {bigint} start The start value + * @param {bigint} end The end value + * @param {number} t The interpolation value + * @returns {bigint} The interpolated value + * + * @since 0.0.2 + */ +export function lerpBigInt(start: bigint, end: bigint, t: number) { + return start + ((end - start) * BigInt(t * 10000)) / 10000n; +} + +/** + * Inverse linear interpolation between two bigint values + * + * @param {bigint} start The start value + * @param {bigint} end The end value + * @param {bigint} value The value to interpolate + * @returns {number} The interpolated value + * + * @since 0.0.2 + */ +export function inverseLerpBigInt(start: bigint, end: bigint, value: bigint) { + return start === end ? 0 : Number((value - start) * 10000n / (end - start)) / 10000; +} \ No newline at end of file diff --git a/packages/stdlib/src/math/bigint/maxBigInt/index.test.ts b/packages/stdlib/src/math/bigint/maxBigInt/index.test.ts new file mode 100644 index 0000000..8f4dbe5 --- /dev/null +++ b/packages/stdlib/src/math/bigint/maxBigInt/index.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from 'vitest'; +import { maxBigInt } from './index'; + +describe('maxBigInt', () => { + it('returns -Infinity when no values are provided', () => { + expect(() => maxBigInt()).toThrow(new TypeError('maxBigInt requires at least one argument')); + }); + + it('returns the largest value from a list of positive bigints', () => { + const result = maxBigInt(10n, 20n, 5n, 15n); + expect(result).toBe(20n); + }); + + it('returns the largest value from a list of negative bigints', () => { + const result = maxBigInt(-10n, -20n, -5n, -15n); + expect(result).toBe(-5n); + }); + + it('returns the largest value from a list of mixed positive and negative bigints', () => { + const result = maxBigInt(10n, -20n, 5n, -15n); + expect(result).toBe(10n); + }); + + it('returns the value itself when only one bigint is provided', () => { + const result = maxBigInt(10n); + expect(result).toBe(10n); + }); + + it('returns the largest value when all values are the same', () => { + const result = maxBigInt(10n, 10n, 10n); + expect(result).toBe(10n); + }); + + it('handles a large number of bigints', () => { + const values = Array.from({ length: 1000 }, (_, i) => BigInt(i)); + const result = maxBigInt(...values); + expect(result).toBe(999n); + }); +}); \ No newline at end of file diff --git a/packages/stdlib/src/math/bigint/maxBigInt/index.ts b/packages/stdlib/src/math/bigint/maxBigInt/index.ts new file mode 100644 index 0000000..f47f015 --- /dev/null +++ b/packages/stdlib/src/math/bigint/maxBigInt/index.ts @@ -0,0 +1,15 @@ +/** + * Like `Math.max` but for BigInts + * + * @param {...bigint} values The values to compare + * @returns {bigint} The largest value + * @throws {TypeError} If no arguments are provided + * + * @since 0.0.2 + */ +export function maxBigInt(...values: bigint[]) { + if (!values.length) + throw new TypeError('maxBigInt requires at least one argument'); + + return values.reduce((acc, val) => val > acc ? val : acc); +} \ No newline at end of file diff --git a/packages/stdlib/src/math/bigint/minBigInt/index.test.ts b/packages/stdlib/src/math/bigint/minBigInt/index.test.ts new file mode 100644 index 0000000..fbfb73a --- /dev/null +++ b/packages/stdlib/src/math/bigint/minBigInt/index.test.ts @@ -0,0 +1,39 @@ +import {describe, it, expect} from 'vitest'; +import {minBigInt} from './index'; + +describe('minBigInt', () => { + it('returns Infinity when no values are provided', () => { + expect(() => minBigInt()).toThrow(new TypeError('minBigInt requires at least one argument')); + }); + + it('returns the smallest value from a list of positive bigints', () => { + const result = minBigInt(10n, 20n, 5n, 15n); + expect(result).toBe(5n); + }); + + it('returns the smallest value from a list of negative bigints', () => { + const result = minBigInt(-10n, -20n, -5n, -15n); + expect(result).toBe(-20n); + }); + + it('returns the smallest value from a list of mixed positive and negative bigints', () => { + const result = minBigInt(10n, -20n, 5n, -15n); + expect(result).toBe(-20n); + }); + + it('returns the value itself when only one bigint is provided', () => { + const result = minBigInt(10n); + expect(result).toBe(10n); + }); + + it('returns the smallest value when all values are the same', () => { + const result = minBigInt(10n, 10n, 10n); + expect(result).toBe(10n); + }); + + it('handles a large number of bigints', () => { + const values = Array.from({length: 1000}, (_, i) => BigInt(i)); + const result = minBigInt(...values); + expect(result).toBe(0n); + }); +}); \ No newline at end of file diff --git a/packages/stdlib/src/math/bigint/minBigInt/index.ts b/packages/stdlib/src/math/bigint/minBigInt/index.ts new file mode 100644 index 0000000..03af83e --- /dev/null +++ b/packages/stdlib/src/math/bigint/minBigInt/index.ts @@ -0,0 +1,15 @@ +/** + * Like `Math.min` but for BigInts + * + * @param {...bigint} values The values to compare + * @returns {bigint} The smallest value + * @throws {TypeError} If no arguments are provided + * + * @since 0.0.2 + */ +export function minBigInt(...values: bigint[]) { + if (!values.length) + throw new TypeError('minBigInt requires at least one argument'); + + return values.reduce((acc, val) => val < acc ? val : acc); +} diff --git a/packages/stdlib/src/math/bigint/remapBigInt/index.test.ts b/packages/stdlib/src/math/bigint/remapBigInt/index.test.ts new file mode 100644 index 0000000..c3cfc29 --- /dev/null +++ b/packages/stdlib/src/math/bigint/remapBigInt/index.test.ts @@ -0,0 +1,32 @@ +import {describe, expect, it} from 'vitest'; +import {remapBigInt} from './index'; + +describe('remapBigInt', () => { + it('map values from one range to another', () => { + // value at midpoint + expect(remapBigInt(5n, 0n, 10n, 0n, 100n)).toBe(50n); + + // value at min + expect(remapBigInt(0n, 0n, 10n, 0n, 100n)).toBe(0n); + + // value at max + expect(remapBigInt(10n, 0n, 10n, 0n, 100n)).toBe(100n); + + // value outside range (below) + expect(remapBigInt(-5n, 0n, 10n, 0n, 100n)).toBe(0n); + + // value outside range (above) + expect(remapBigInt(15n, 0n, 10n, 0n, 100n)).toBe(100n); + + // value at midpoint of negative range + expect(remapBigInt(75n, 50n, 100n, -50n, 50n)).toBe(0n); + + // value at midpoint of negative range + expect(remapBigInt(-25n, -50n, 0n, 0n, 100n)).toBe(50n); + }); + + it('handle edge cases', () => { + // input range is zero (should return output min) + expect(remapBigInt(5n, 0n, 0n, 0n, 100n)).toBe(0n); + }); +}); \ No newline at end of file diff --git a/packages/stdlib/src/math/bigint/remapBigInt/index.ts b/packages/stdlib/src/math/bigint/remapBigInt/index.ts new file mode 100644 index 0000000..0530ed8 --- /dev/null +++ b/packages/stdlib/src/math/bigint/remapBigInt/index.ts @@ -0,0 +1,23 @@ +import { clampBigInt } from '../clampBigInt'; +import {inverseLerpBigInt, lerpBigInt} from '../lerpBigInt'; + +/** + * Map a bigint value from one range to another + * + * @param {bigint} value The value to map + * @param {bigint} in_min The minimum value of the input range + * @param {bigint} in_max The maximum value of the input range + * @param {bigint} out_min The minimum value of the output range + * @param {bigint} out_max The maximum value of the output range + * @returns {bigint} The mapped value + * + * @since 0.0.1 + */ +export function remapBigInt(value: bigint, in_min: bigint, in_max: bigint, out_min: bigint, out_max: bigint) { + if (in_min === in_max) + return out_min; + + const clampedValue = clampBigInt(value, in_min, in_max); + + return lerpBigInt(out_min, out_max, inverseLerpBigInt(in_min, in_max, clampedValue)); +} \ No newline at end of file diff --git a/packages/stdlib/src/math/index.ts b/packages/stdlib/src/math/index.ts index 701b72a..96e0fd7 100644 --- a/packages/stdlib/src/math/index.ts +++ b/packages/stdlib/src/math/index.ts @@ -1,2 +1,9 @@ -export * from './clamp'; -export * from './mapRange'; \ No newline at end of file +export * from './basic/clamp'; +export * from './basic/lerp'; +export * from './basic/remap'; + +export * from './bigint/clampBigInt'; +export * from './bigint/lerpBigInt'; +export * from './bigint/maxBigInt'; +export * from './bigint/minBigInt'; +export * from './bigint/remapBigInt';