Math
Math utilities like lerp, clamp, etc.
Example Usage
import {
clamp,
lerp,
lerpInverse,
sum,
mean,
wrap,
numIsBetween,
calculateFrequenciesStats,
} from "./math"
// clamp
clamp({ min: 0, max: 100, value: 50 }) // 50
clamp({ min: 0, max: 100, value: 25 }) // 25
clamp({ min: 0, max: 100, value: 75 }) // 75
clamp({ min: 0, max: 100, value: 150 }) // 100
clamp({ min: 0, max: 100, value: -50 }) // 0
// lerp
// it automatically clamp value
lerp({ min: 0, max: 100, t: 0.5 }) // 50
lerp({ min: 0, max: 100, t: 0.25 }) // 25
lerp({ min: 0, max: 100, t: 0.75 }) // 75
lerp({ min: 0, max: 100, t: 1.5 }) // 100
lerp({ min: 0, max: 100, t: -0.5 }) // 0
// lerpInverse
// it automatically clamp value
lerpInverse({ min: 0, max: 100, value: 50 }) // 0.5
lerpInverse({ min: 0, max: 100, value: 25 }) // 0.25
lerpInverse({ min: 0, max: 100, value: 75 }) // 0.75
lerpInverse({ min: 0, max: 100, value: 150 }) // 1
lerpInverse({ min: 0, max: 100, value: -50 }) // 0
// sum
sum([]); // 0
sum([1, 2, 3]); // 6
// mean
mean([]); // 0
mean([1, 2, 3]); // 6 / 3 = 2
mean([1, 2, 3, 4]); // 10 / 4 = 2.5
mean([0, 0, 10]); // 3.3333333333333335
// wrap
wrap({ min: 0, max: 100, value: 200 }); // 0
wrap({ min: 0, max: 100, value: -10 }); // 100
wrap({ min: 0, max: 100, value: 34 }); // 34
// numIsBetween
numIsBetween({ min: 1, max: 10, num: 8, }); // true
numIsBetween({ min: 1, max: 10, num: 1, isInclusive: true }); // true
numIsBetween({ min: 1, max: 10, num: 1, }); // true (isInclusive defaults to true)
numIsBetween({ min: 1, max: 10, num: 1, isInclusive: false }); // false
// calculateFrequenciesStats
calculateFrequenciesStats({ A: 4, B: 6 }));
// ⏬
{
total: 10,
groups: {
A: { count: 4, percentageOnTotal: 0.4 },
B: { count: 6, percentageOnTotal: 0.6 },
}
}
calculateFrequenciesStats({ A: 1, B: 2 }));
// ⏬
{
total: 3,
groups: {
A: { count: 1, percentageOnTotal: 0.3333333333333333 },
B: { count: 2, percentageOnTotal: 0.6666666666666666 },
}
};
Dependencies
No dependencies
Auto Install
npx shadcn@latest add https://shadcn-registry-ts.vercel.app/r/util-math.json
Manual Install
math.ts
/**
* Source: http://localhost:3000
*/
/**
* Clamp function, constraints a value to be in a range.
* Outliers will be clamped to the relevant extreme of the range.
*/
export function clamp({ min, max, value }: {
/** Minimin possibile value. */
min: number,
/** Maximinum possible value. */
max: number,
/** Value you want to clamp */
value: number;
}) {
if (value < min) return min;
if (value > max) return max;
return value;
}
/**
* Lerp function, used to get a value in range based on a percentage.
* Outliers will be clamped.
*/
export function lerp({ min, max, t }: {
/** Lower part of the a-b range. Minumum value passibile. */
min: number,
/** Upper part of the a-b range. Maximum value possible. */
max: number,
/** Number, decimal, from 0.0 to 1.0, which rapresent where value lives between a-b range. */
t: number;
}) {
const value = (max - min) * t + min;
return clamp({ min, max, value });
}
/**
* Lerp Inversed function, used to get the percentage of a value in a range.
* Outliers will be clamped.
*/
export function lerpInverse({ min, max, value }: {
/** Lower part of the a-b range. Minumum value passibile. */
min: number,
/** Upper part of the a-b range. Maximum value possible. */
max: number,
/** Number that must be in range a-b, rapresent the value that you want to know where it sits in a-b range. */
value: number;
}): number {
const t = (value - min) / (max - min);
return clamp({ min: 0, max: 1, value: t });
}
/**
* Sum function. Accept array of numbers and return the sum.
*/
export const sum = (
/** Array of numbers */
nums: number[]
): number => {
return nums.reduce((acc, num) => acc + num, 0);
};
/**
* Mean function. Accept array of numbers and return the mean.
*/
export function mean(
/** Array of numbers */
nums: number[]
): number {
if (nums.length === 0) return 0;
return sum(nums) / nums.length;
}
/**
* Wrap value in range [min, max].
* If value is greater than max, min is returned.
* If value is lower than min, max is returned.
* If value is in between min and max, value is returned.
*/
export function wrap({ min, max, value }: {
/** Lower part of the range. */
min: number,
/** Upper part of the range. */
max: number,
/** Value to wrap. */
value: number;
}) {
if (value < min) return max;
if (value > max) return min;
return value;
}
/**
* Check if a number is between a range.
*/
export const numIsBetween = ({
min,
max,
num,
isInclusive = true,
}: {
/** Lower part of the range. */
min: number,
/** Upper part of the range. */
max: number,
/** Number to check. */
num: number,
/** If `true` min and max are allowed values, if `false` min and max are not allowed values. Deafult: `true` */
isInclusive?: boolean,
}) => {
if (isInclusive) {
return num >= min && num <= max;
}
return num > min && num < max;
};
/**
* Calculate percentages of an object that represents frequencies.
* @example
* ```ts
* const frequencies = calculateFrequenciesStats({ A: 4, B: 6 });
* console.log(frequencies);
* // output
* {
* total: 10,
* groups: {
* A: { count: 4, percentageOnTotal: 0.4 },
* B: { count: 6, percentageOnTotal: 0.6 },
* }
* }
* ```
*/
export const calculateFrequenciesStats = (calculations: { [k: string]: number; }) => {
type Data = {
/** total executions of all groups */
total: number,
/** stats for each group */
groups: Record<string, {
/** times this group was calculated */
count: number;
/** percentage of times this group was calculated against total executions of all groups. 0-1 range. */
percentageOnTotal: number;
}>;
};
const total = sum(Object.values(calculations));
const data: Data = {
total,
groups: Object.fromEntries(
Object.entries(calculations).map(([calcKey, calcCount]) => {
const percentage = (calcCount / total);
const data = {
count: calcCount,
percentageOnTotal: percentage,
};
return [calcKey, data];
}))
};
return data;
};
Test
math.test.ts
import { describe, it, expect } from 'vitest';
import {
clamp,
lerp,
lerpInverse,
sum,
mean,
wrap,
numIsBetween,
calculateFrequenciesStats,
} from './math';
describe('math - clamp', () => {
it('do it', () => {
// with value inside range
expect(clamp({ min: 0, max: 100, value: 50 })).toBe(50);
expect(clamp({ min: 0, max: 100, value: 25 })).toBe(25);
expect(clamp({ min: 0, max: 100, value: 75 })).toBe(75);
// with value outside range
expect(clamp({ min: 0, max: 100, value: -50 })).toBe(0);
expect(clamp({ min: 0, max: 100, value: 150 })).toBe(100);
});
});
describe('math - lerp', () => {
it('do it', () => {
// with t inside 0-1 range
expect(lerp({ min: 0, max: 100, t: 0.5 })).toBe(50);
expect(lerp({ min: 0, max: 100, t: 0.25 })).toBe(25);
expect(lerp({ min: 0, max: 100, t: 0.75 })).toBe(75);
// with t outise 0-1 range
expect(lerp({ min: 0, max: 100, t: -0.5 })).toBe(0);
expect(lerp({ min: 0, max: 100, t: 1.5 })).toBe(100);
});
});
describe('math - lerpInverse', () => {
it('do it', () => {
// with value inside range
expect(lerpInverse({ min: 0, max: 100, value: 50 })).toBe(0.5);
expect(lerpInverse({ min: 0, max: 100, value: 25 })).toBe(0.25);
expect(lerpInverse({ min: 0, max: 100, value: 75 })).toBe(0.75);
// with value outside range
expect(lerpInverse({ min: 0, max: 100, value: -50 })).toBe(0);
expect(lerpInverse({ min: 0, max: 100, value: 150 })).toBe(1);
});
});
describe('math - sum', () => {
it('do it', () => {
expect(sum([])).toBe(0);
expect(sum([1, 2, 3])).toBe(6);
});
});
describe('math - mean', () => {
it('do it', () => {
expect(mean([])).toBe(0);
expect(mean([1, 2, 3])).toBe(2);
expect(mean([1, 2, 3, 4])).toBe(2.5);
expect(mean([0, 0, 10])).toBe(3.3333333333333335);
});
});
describe('math - wrap', () => {
it('do it', () => {
// with value inside range
expect(wrap({ min: 0, max: 100, value: 34 })).toBe(34);
// with value outside range
expect(wrap({ min: 0, max: 100, value: -10 })).toBe(100);
expect(wrap({ min: 0, max: 100, value: 200 })).toBe(0);
});
});
describe('math - numIsBetween', () => {
it('do it', () => {
// with value inside range
expect(numIsBetween({ min: 0, max: 100, num: 0 })).toBe(true);
expect(numIsBetween({ min: 0, max: 100, num: 50 })).toBe(true);
expect(numIsBetween({ min: 0, max: 100, num: 100 })).toBe(true);
expect(numIsBetween({ min: 0, max: 100, num: 0, isInclusive: true })).toBe(true);
expect(numIsBetween({ min: 0, max: 100, num: 50, isInclusive: true })).toBe(true);
expect(numIsBetween({ min: 0, max: 100, num: 100, isInclusive: true })).toBe(true);
expect(numIsBetween({ min: 0, max: 100, num: 0, isInclusive: false })).toBe(false);
expect(numIsBetween({ min: 0, max: 100, num: 50, isInclusive: false })).toBe(true);
expect(numIsBetween({ min: 0, max: 100, num: 100, isInclusive: false })).toBe(false);
// with value outside range
expect(numIsBetween({ min: 0, max: 100, num: -1 })).toBe(false);
expect(numIsBetween({ min: 0, max: 100, num: 101 })).toBe(false);
});
});
describe('math - calculateFrequenciesStats', () => {
it('do it', () => {
expect(calculateFrequenciesStats({ A: 4, B: 6 })).toMatchObject({
total: 10,
groups: {
A: { count: 4, percentageOnTotal: 0.4 },
B: { count: 6, percentageOnTotal: 0.6 },
}
});
expect(calculateFrequenciesStats({ A: 1, B: 2 })).toMatchObject({
total: 3,
groups: {
A: { count: 1, percentageOnTotal: 0.3333333333333333 },
B: { count: 2, percentageOnTotal: 0.6666666666666666 },
}
});
});
});
Command Palette
Search for a command to run...