From 55c5f19ea85d2c43af5e88e0416c39051bbe326b Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Fri, 12 Jul 2024 16:28:09 -0400 Subject: [PATCH] feat: add `clamp` function --- benchmarks/number/clamp.bench.ts | 10 +++++++++ docs/number/clamp.mdx | 27 ++++++++++++++++++++++++ src/mod.ts | 1 + src/number/clamp.ts | 29 ++++++++++++++++++++++++++ tests/number/clamp.test.ts | 35 ++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+) create mode 100644 benchmarks/number/clamp.bench.ts create mode 100644 docs/number/clamp.mdx create mode 100644 src/number/clamp.ts create mode 100644 tests/number/clamp.test.ts diff --git a/benchmarks/number/clamp.bench.ts b/benchmarks/number/clamp.bench.ts new file mode 100644 index 00000000..32621244 --- /dev/null +++ b/benchmarks/number/clamp.bench.ts @@ -0,0 +1,10 @@ +import * as _ from 'radashi' +import { bench } from 'vitest' + +describe('clamp', () => { + bench('with no arguments', () => { + _.clamp(100, 0, 10) + _.clamp(0, 10, 100) + _.clamp(5, 0, 10) + }) +}) diff --git a/docs/number/clamp.mdx b/docs/number/clamp.mdx new file mode 100644 index 00000000..58e8b85c --- /dev/null +++ b/docs/number/clamp.mdx @@ -0,0 +1,27 @@ +--- +title: clamp +description: Limit the range of a variable number +--- + +### Usage + +The `clamp` function restricts a number to be within a specified +range. + +- It takes three arguments: the number to clamp, the minimum value, + and the maximum value. +- If the number is less than the minimum, it returns the minimum. +- If the number is greater than the maximum, it returns the + maximum. +- Otherwise, it returns the number itself. + +```ts +import * as _ from 'radashi' + +_.clamp(5, 1, 10) // returns 5 +_.clamp(0, 1, 10) // returns 1 +_.clamp(15, 1, 10) // returns 10 + +// Invalid range +_.clamp(1, 10, 1) // throws +``` diff --git a/src/mod.ts b/src/mod.ts index a628c901..efd5622e 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -50,6 +50,7 @@ export * from './curry/partob.ts' export * from './curry/proxied.ts' export * from './curry/throttle.ts' +export * from './number/clamp.ts' export * from './number/inRange.ts' export * from './number/lerp.ts' export * from './number/max.ts' diff --git a/src/number/clamp.ts b/src/number/clamp.ts new file mode 100644 index 00000000..99f231f3 --- /dev/null +++ b/src/number/clamp.ts @@ -0,0 +1,29 @@ +/** + * The `clamp` function restricts a number to be within a specified + * range. + * + * - It takes three arguments: the number to clamp, the minimum value, + * and the maximum value. + * - If the number is less than the minimum, it returns the minimum. + * - If the number is greater than the maximum, it returns the + * maximum. + * - Otherwise, it returns the number itself. + * + * @see https://radashi-org.github.io/reference/number/clamp + * @example + * ```ts + * clamp(5, 1, 10) // returns 5 + * clamp(0, 1, 10) // returns 1 + * clamp(15, 1, 10) // returns 10 + * ``` + */ +export function clamp( + n: number, + min: number | null | undefined, + max: number | null | undefined, +): number { + if (max != null && min != null && min > max) { + throw new Error('invalid clamp range') + } + return max != null && n > max ? max : min != null && n < min ? min : n +} diff --git a/tests/number/clamp.test.ts b/tests/number/clamp.test.ts new file mode 100644 index 00000000..e7b02ce2 --- /dev/null +++ b/tests/number/clamp.test.ts @@ -0,0 +1,35 @@ +import * as _ from 'radashi' + +describe('clamp', () => { + test('clamps a number within the given range', () => { + expect(_.clamp(5, 1, 10)).toBe(5) + expect(_.clamp(0, 1, 10)).toBe(1) + expect(_.clamp(15, 1, 10)).toBe(10) + }) + test('handles min being null or undefined', () => { + expect(_.clamp(5, null, 10)).toBe(5) + expect(_.clamp(15, null, 10)).toBe(10) + expect(_.clamp(5, undefined, 10)).toBe(5) + expect(_.clamp(15, undefined, 10)).toBe(10) + }) + test('handles max being null or undefined', () => { + expect(_.clamp(5, 1, null)).toBe(5) + expect(_.clamp(0, 1, null)).toBe(1) + expect(_.clamp(5, 1, undefined)).toBe(5) + expect(_.clamp(0, 1, undefined)).toBe(1) + }) + test('handles both min and max being null or undefined', () => { + expect(_.clamp(5, null, null)).toBe(5) + expect(_.clamp(5, undefined, undefined)).toBe(5) + expect(_.clamp(-10, null, undefined)).toBe(-10) + expect(_.clamp(100, undefined, null)).toBe(100) + }) + test('handles edge cases', () => { + expect(_.clamp(Number.POSITIVE_INFINITY, 1, 10)).toBe(10) + expect(_.clamp(Number.NEGATIVE_INFINITY, 1, 10)).toBe(1) + expect(_.clamp(Number.NaN, 1, 10)).toBe(Number.NaN) + }) + test('throw on invalid range', () => { + expect(() => _.clamp(5, 10, 1)).toThrow('invalid clamp range') + }) +})