1
0
mirror of https://github.com/robonen/tools.git synced 2026-03-20 10:54:44 +00:00

refactor: change separate tools by category

This commit is contained in:
2025-05-19 17:43:42 +07:00
parent d55737df2f
commit 78fb4da82a
158 changed files with 32 additions and 24 deletions

View File

@@ -0,0 +1,81 @@
import { describe,it, expect } from 'vitest';
import { clamp } from '.';
describe('clamp', () => {
it('clamp a value within the given range', () => {
// value < min
expect(clamp(-10, 0, 100)).toBe(0);
// value > max
expect(clamp(200, 0, 100)).toBe(100);
// value within range
expect(clamp(50, 0, 100)).toBe(50);
// value at min
expect(clamp(0, 0, 100)).toBe(0);
// value at max
expect(clamp(100, 0, 100)).toBe(100);
// value at midpoint
expect(clamp(50, 100, 100)).toBe(100);
});
it('handle floating-point numbers correctly', () => {
// floating-point value within range
expect(clamp(3.14, 0, 5)).toBe(3.14);
// floating-point value < min
expect(clamp(-1.5, 0, 10)).toBe(0);
// floating-point value > max
expect(clamp(15.75, 0, 10)).toBe(10);
});
it('handle edge cases', () => {
// all values are the same
expect(clamp(5, 5, 5)).toBe(5);
// min > max
expect(clamp(10, 100, 50)).toBe(50);
// negative range and value
expect(clamp(-10, -100, -5)).toBe(-10);
});
it('handle NaN and Infinity', () => {
// value is NaN
expect(clamp(NaN, 0, 100)).toBe(NaN);
// min is NaN
expect(clamp(50, NaN, 100)).toBe(NaN);
// max is NaN
expect(clamp(50, 0, NaN)).toBe(NaN);
// value is Infinity
expect(clamp(Infinity, 0, 100)).toBe(100);
// min is Infinity
expect(clamp(50, Infinity, 100)).toBe(100);
// max is Infinity
expect(clamp(50, 0, Infinity)).toBe(50);
// min and max are Infinity
expect(clamp(50, Infinity, Infinity)).toBe(Infinity);
// value is -Infinity
expect(clamp(-Infinity, 0, 100)).toBe(0);
// min is -Infinity
expect(clamp(50, -Infinity, 100)).toBe(50);
// max is -Infinity
expect(clamp(50, 0, -Infinity)).toBe(-Infinity);
// min and max are -Infinity
expect(clamp(50, -Infinity, -Infinity)).toBe(-Infinity);
});
});

View File

@@ -0,0 +1,15 @@
/**
* @name clamp
* @category Math
* @description Clamps a number between a minimum and maximum value
*
* @param {number} value The number to clamp
* @param {number} min Minimum value
* @param {number} max Maximum value
* @returns {number} The clamped number
*
* @since 0.0.1
*/
export function clamp(value: number, min: number, max: number) {
return Math.min(Math.max(value, min), max);
}

View File

@@ -0,0 +1,61 @@
import {describe, it, expect} from 'vitest';
import {inverseLerp, lerp} from '.';
describe('lerp', () => {
it('interpolates between two values', () => {
const result = lerp(0, 10, 0.5);
expect(result).toBe(5);
});
it('returns start value when t is 0', () => {
const result = lerp(0, 10, 0);
expect(result).toBe(0);
});
it('returns end value when t is 1', () => {
const result = lerp(0, 10, 1);
expect(result).toBe(10);
});
it('handles negative interpolation values', () => {
const result = lerp(0, 10, -0.5);
expect(result).toBe(-5);
});
it('handles interpolation values greater than 1', () => {
const result = lerp(0, 10, 1.5);
expect(result).toBe(15);
});
});
describe('inverseLerp', () => {
it('returns 0 when value is start', () => {
const result = inverseLerp(0, 10, 0);
expect(result).toBe(0);
});
it('returns 1 when value is end', () => {
const result = inverseLerp(0, 10, 10);
expect(result).toBe(1);
});
it('interpolates correctly between two values', () => {
const result = inverseLerp(0, 10, 5);
expect(result).toBe(0.5);
});
it('handles values less than start', () => {
const result = inverseLerp(0, 10, -5);
expect(result).toBe(-0.5);
});
it('handles values greater than end', () => {
const result = inverseLerp(0, 10, 15);
expect(result).toBe(1.5);
});
it('handles same start and end values', () => {
const result = inverseLerp(10, 10, 10);
expect(result).toBe(0);
});
});

View File

@@ -0,0 +1,31 @@
/**
* @name lerp
* @category Math
* @description Linearly interpolates between two values
*
* @param {number} start The start value
* @param {number} end The end value
* @param {number} t The interpolation value
* @returns {number} The interpolated value
*
* @since 0.0.2
*/
export function lerp(start: number, end: number, t: number) {
return start + t * (end - start);
}
/**
* @name inverseLerp
* @category Math
* @description Inverse linear interpolation between two values
*
* @param {number} start The start value
* @param {number} end The end value
* @param {number} value The value to interpolate
* @returns {number} The interpolated value
*
* @since 0.0.2
*/
export function inverseLerp(start: number, end: number, value: number) {
return start === end ? 0 : (value - start) / (end - start);
}

View File

@@ -0,0 +1,46 @@
import {describe, expect, it} from 'vitest';
import {remap} from '.';
describe('remap', () => {
it('map values from one range to another', () => {
// value at midpoint
expect(remap(5, 0, 10, 0, 100)).toBe(50);
// value at min
expect(remap(0, 0, 10, 0, 100)).toBe(0);
// value at max
expect(remap(10, 0, 10, 0, 100)).toBe(100);
// value outside range (below)
expect(remap(-5, 0, 10, 0, 100)).toBe(0);
// value outside range (above)
expect(remap(15, 0, 10, 0, 100)).toBe(100);
// value at midpoint of negative range
expect(remap(75, 50, 100, -50, 50)).toBe(0);
// value at midpoint of negative range
expect(remap(-25, -50, 0, 0, 100)).toBe(50);
});
it('handle floating-point numbers correctly', () => {
// floating-point value
expect(remap(3.5, 0, 10, 0, 100)).toBe(35);
// positive floating-point ranges
expect(remap(1.25, 0, 2.5, 0, 100)).toBe(50);
// negative floating-point value
expect(remap(-2.5, -5, 0, 0, 100)).toBe(50);
// negative floating-point ranges
expect(remap(-1.25, -2.5, 0, 0, 100)).toBe(50);
});
it('handle edge cases', () => {
// input range is zero (should return output min)
expect(remap(5, 0, 0, 0, 100)).toBe(0);
});
});

View File

@@ -0,0 +1,25 @@
import { clamp } from '../clamp';
import {inverseLerp, lerp} from '../lerp';
/**
* @name remap
* @category Math
* @description Map a value from one range to another
*
* @param {number} value The value to map
* @param {number} in_min The minimum value of the input range
* @param {number} in_max The maximum value of the input range
* @param {number} out_min The minimum value of the output range
* @param {number} out_max The maximum value of the output range
* @returns {number} The mapped value
*
* @since 0.0.1
*/
export function remap(value: number, in_min: number, in_max: number, out_min: number, out_max: number) {
if (in_min === in_max)
return out_min;
const clampedValue = clamp(value, in_min, in_max);
return lerp(out_min, out_max, inverseLerp(in_min, in_max, clampedValue));
}

View File

@@ -0,0 +1,35 @@
import {describe, it, expect} from 'vitest';
import {clampBigInt} from '.';
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);
});
});

View File

@@ -0,0 +1,18 @@
import {minBigInt} from '../minBigInt';
import {maxBigInt} from '../maxBigInt';
/**
* @name clampBigInt
* @category Math
* @description 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);
}

View File

@@ -0,0 +1,83 @@
import {describe, it, expect} from 'vitest';
import {inverseLerpBigInt, lerpBigInt} from '.';
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);
});
});

View File

@@ -0,0 +1,31 @@
/**
* @name lerpBigInt
* @category Math
* @description 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;
}
/**
* @name inverseLerpBigInt
* @category Math
* @description 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;
}

View File

@@ -0,0 +1,39 @@
import { describe, it, expect } from 'vitest';
import { maxBigInt } from '.';
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);
});
});

View File

@@ -0,0 +1,17 @@
/**
* @name maxBigInt
* @category Math
* @description 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);
}

View File

@@ -0,0 +1,39 @@
import {describe, it, expect} from 'vitest';
import {minBigInt} from '.';
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);
});
});

View File

@@ -0,0 +1,17 @@
/**
* @name minBigInt
* @category Math
* @description 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);
}

View File

@@ -0,0 +1,32 @@
import {describe, expect, it} from 'vitest';
import {remapBigInt} from '.';
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);
});
});

View File

@@ -0,0 +1,25 @@
import { clampBigInt } from '../clampBigInt';
import {inverseLerpBigInt, lerpBigInt} from '../lerpBigInt';
/**
* @name remapBigInt
* @category Math
* @description 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));
}

View File

@@ -0,0 +1,9 @@
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';