diff --git a/Maths/ClampNumber.js b/Maths/ClampNumber.js new file mode 100644 index 0000000000..87e8ce044b --- /dev/null +++ b/Maths/ClampNumber.js @@ -0,0 +1,33 @@ +/** + * @function clampNumber + * @description Clamps a number between a minimum and maximum value. + * If the number is less than min, returns min. + * If the number is greater than max, returns max. + * Otherwise returns the number itself. + * @param {number} num - The number to clamp + * @param {number} min - The minimum value + * @param {number} max - The maximum value + * @returns {number} - The clamped number + * @see https://en.wikipedia.org/wiki/Clamping_(graphics) + * @example clampNumber(5, 1, 10) => 5 + * @example clampNumber(-5, 1, 10) => 1 + * @example clampNumber(15, 1, 10) => 10 + */ +const clampNumber = (num, min, max) => { + if ( + typeof num !== 'number' || + typeof min !== 'number' || + typeof max !== 'number' || + isNaN(num) || + isNaN(min) || + isNaN(max) + ) { + throw new TypeError('All inputs must be numbers') + } + if (min > max) { + throw new RangeError('min must be less than or equal to max') + } + return Math.min(Math.max(num, min), max) +} + +export { clampNumber } diff --git a/Maths/test/ClampNumber.test.js b/Maths/test/ClampNumber.test.js new file mode 100644 index 0000000000..482efe74ed --- /dev/null +++ b/Maths/test/ClampNumber.test.js @@ -0,0 +1,47 @@ +import { clampNumber } from '../ClampNumber.js' + +describe('clampNumber', () => { + test('clampNumber(5, 1, 10) returns 5', () => { + expect(clampNumber(5, 1, 10)).toBe(5) + }) + + test('clampNumber(-5, 1, 10) returns 1', () => { + expect(clampNumber(-5, 1, 10)).toBe(1) + }) + + test('clampNumber(15, 1, 10) returns 10', () => { + expect(clampNumber(15, 1, 10)).toBe(10) + }) + + test('clampNumber(1, 1, 10) returns 1 (at min)', () => { + expect(clampNumber(1, 1, 10)).toBe(1) + }) + + test('clampNumber(10, 1, 10) returns 10 (at max)', () => { + expect(clampNumber(10, 1, 10)).toBe(10) + }) + + test('clampNumber works with negative range', () => { + expect(clampNumber(-5, -10, -1)).toBe(-5) + }) + + test('clampNumber works with decimals', () => { + expect(clampNumber(1.5, 1.0, 2.0)).toBe(1.5) + }) + + test('clampNumber(0, 0, 0) returns 0', () => { + expect(clampNumber(0, 0, 0)).toBe(0) + }) + + test('throws TypeError for non-number input', () => { + expect(() => clampNumber('5', 1, 10)).toThrow(TypeError) + }) + + test('throws TypeError for NaN', () => { + expect(() => clampNumber(NaN, 1, 10)).toThrow(TypeError) + }) + + test('throws RangeError when min > max', () => { + expect(() => clampNumber(5, 10, 1)).toThrow(RangeError) + }) +})