smath
Version:
Small math function library
547 lines (546 loc) • 16.6 kB
JavaScript
;
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.approx = approx;
exports.clamp = clamp;
exports.normalize = normalize;
exports.expand = expand;
exports.translate = translate;
exports.linspace = linspace;
exports.logspace = logspace;
exports.factorial = factorial;
exports.factors = factors;
exports.round2 = round2;
exports.error = error;
exports.sum = sum;
exports.prod = prod;
exports.avg = avg;
exports.median = median;
exports.varp = varp;
exports.vars = vars;
exports.stdevp = stdevp;
exports.stdevs = stdevs;
exports.runif = runif;
exports.rint = rint;
exports.rnorm = rnorm;
exports.rdist = rdist;
exports.shuffle = shuffle;
exports.lim = lim;
exports.differentiate = differentiate;
exports.integrate = integrate;
exports.rat = rat;
exports.mixed = mixed;
exports.toHex = toHex;
/**
* @packageDocumentation
* Small math function library
*
* 
* 
*/
/**
* Check if two numbers are approximately equal with a maximum abolute error.
* @param a Any number
* @param b Any number
* @param epsilon Maximum absolute error
* @returns True if `a` is approximately `b`
* @example
* const b1 = SMath.approx(1 / 3, 0.33, 1e-6), // false
* b2 = SMath.approx(1 / 3, 0.33, 1e-2); // true
*/
function approx(a, b, epsilon) {
if (epsilon === void 0) { epsilon = 1e-6; }
return a - b < epsilon && b - a < epsilon;
}
/**
* Clamp a number within a range.
* @param n The number to clamp
* @param min The minimum value of the range
* @param max The maximum value of the range
* @returns A clamped number
* @example
* const n1 = SMath.clamp(5, 0, 10), // 5
* n2 = SMath.clamp(-2, 0, 10); // 0
*/
function clamp(n, min, max) {
if (n < min) {
return min;
}
if (n > max) {
return max;
}
return n;
}
/**
* Normalize the number `n` from the range `min, max` to the range `0, 1`
* @param n The number to normalize
* @param min The minimum value in the range
* @param max The maximum value in the range
* @returns A normalized value
* @example
* const y = SMath.normalize(18, 9, 99); // 0.1
*/
function normalize(n, min, max) {
if (min === max) {
return 0;
}
return (n - min) / (max - min);
}
/**
* Expand a normalized number `n` to the range `min, max`
* @param n A normalized number
* @param min The minimum value in the range
* @param max The maximum value in the range
* @returns A value within the number range
* @example
* const y = SMath.expand(0.25, 4, 6); // 4.5
*/
function expand(n, min, max) {
return (max - min) * n + min;
}
/**
* Translate a number `n` from the range `min1, max1` to the range `min2, max2`
* @param n The number to translate
* @param min1 The minimum value from the initial range
* @param max1 The maximum value from the initial range
* @param min2 The minimum value for the final range
* @param max2 The maximum value for the final range
* @returns A translated number in the final range
* @example
* const C = 20,
* F = SMath.translate(C, 0, 100, 32, 212); // 68
*/
function translate(n, min1, max1, min2, max2) {
return expand(normalize(n, min1, max1), min2, max2);
}
/**
* Generate an array of linearly spaced numbers.
* @param min The initial value of the linear space
* @param max The final value of the linear space
* @param count The number of values in the space
* @returns The linear space as an array of numbers
* @example
* const space = SMath.linspace(1, 5, 6);
* // [ 1, 1.8, 2.6, 3.4, 4.2, 5 ]
*/
function linspace(min, max, count) {
var space = [];
for (var i = 0; i < count; i++) {
space[i] = translate(i, 0, count - 1, min, max);
}
return space;
}
/**
* Generate an array of logarithmically spaced numbers.
* @param min The initial magnitude of the space
* @param max The final magnitude of the space
* @param count The number of values in the space
* @returns The logarithmic space as an array of numbers
* @example
* const space = SMath.logspace(0, 2, 5);
* // [ 1, 3.2, 10, 31.6, 100 ]
*/
function logspace(min, max, count) {
return linspace(min, max, count).map(function (n) { return Math.pow(10, n); });
}
/**
* Compute the factorial of `n`.
* @param n Any positive integer
* @returns `n!`
* @example
* const y = SMath.factorial(5); // 120
*/
function factorial(n) {
if (n < 0 || (n | 0) !== n) {
throw new Error('Input must be a positive integer.');
}
else if (n === 0) {
return 1;
}
else if (n <= 2) {
return n;
}
else {
return n * factorial(n - 1);
}
}
/**
* Factorize `n` into its prime factors.
* @param n Any positive integer
* @returns The array of prime factors
* @example
* const y = SMath.factors(12); // [ 2, 2, 3 ]
*/
function factors(n) {
if (n < 0 || (n | 0) !== n) {
throw new Error('Input must be a positive integer!');
}
if (n <= 3) {
return [n];
}
var f = [];
var i = 2;
while (n > 1 && i <= n) {
if ((n / i) === ((n / i) | 0)) {
n /= i;
f.push(i);
}
else {
i++;
}
}
return f;
}
/**
* Round a number to the nearest multiple of an arbitrary
* base. Does not round when the base is set to zero.
* @param n Any number to round
* @param base Any base to round to
* @returns `n` rounded to the nearest multiple of `base`
* @example
* const y = SMath.round2(Math.PI, 0.2); // 3.2
*/
function round2(n, base) {
var rounded = base ? base * Math.round(n / base) : n;
var precision = 10; // Removes precision errors
return parseFloat(rounded.toFixed(precision));
}
/**
* Calculate the relative normalized error or deviation from any
* value to an accepted value. An error of 0 indicates that the
* two values are identical. An error of -0.1 indicates that the
* experimental value is 10% smaller than (90% of) the accepted
* value. An error of 1.0 indicates that the experimental value
* is 100% greater (or twice the size) of the accepted value.
* @param experimental The value observed or produced by a test
* @param actual The accepted or theoretical value
* @returns The relative (normalized) error
* @example
* const e = SMath.error(22.5, 25); // -0.1
*/
function error(experimental, actual) {
return (experimental - actual) / actual;
}
/**
* Add up all the inputs.
* If none are present, returns 0.
* @param data An array of numeric inputs
* @returns The sum total
* @example
* const y = SMath.sum([1, 2, 3]); // 6
*/
function sum(data) {
return data.reduce(function (a, b) { return a + b; }, 0);
}
/**
* Multiply all the inputs.
* If none are present, returns 1.
* @param data An array of numeric inputs
* @returns The product
* @example
* const y = SMath.prod([2, 2, 3, 5]); // 60
*/
function prod(data) {
return data.reduce(function (a, b) { return a * b; }, 1);
}
/**
* Compute the average, or mean, of a set of numbers.
* @param data An array of numeric inputs
* @returns The average, or mean
* @example
* const y = SMath.avg([1, 2, 4, 4]); // 2.75
*/
function avg(data) {
return sum(data) / data.length;
}
/**
* Compute the median of a set of numbers.
* @param data An array of numeric inputs
* @returns The median of the dataset
* @example
* const y = SMath.median([2, 5, 3, 1]); // 2.5
*/
function median(data) {
data.sort(function (a, b) { return a - b; });
if (data.length % 2) {
return data[(data.length - 1) / 2];
}
return avg([data[data.length / 2 - 1], data[data.length / 2]]);
}
/**
* Compute the variance of a **complete population**.
* @param data An array of numeric inputs
* @returns The population variance
* @example
* const y = SMath.varp([1, 2, 4, 4]); // 1.6875
*/
function varp(data) {
var mean = avg(data), squares = data.map(function (x) { return Math.pow((x - mean), 2); });
return sum(squares) / data.length;
}
/**
* Compute the variance of a **sample**.
* @param data An array of numeric inputs
* @returns The sample variance
* @example
* const y = SMath.vars([1, 2, 4, 4]); // 2.25
*/
function vars(data) {
var mean = avg(data), squares = data.map(function (x) { return Math.pow((x - mean), 2); });
return sum(squares) / (data.length - 1);
}
/**
* Compute the standard deviation of a **complete population**.
* @param data An array of numeric inputs
* @returns The population standard deviation
* @example
* const y = SMath.stdevp([1, 2, 3, 4]); // 1.118...
*/
function stdevp(data) {
return Math.sqrt(varp(data));
}
/**
* Compute the standard deviation of a **sample**.
* @param data An array of numeric inputs
* @returns The sample standard deviation
* @example
* const y = SMath.stdevs([1, 2, 3, 4]); // 1.29...
*/
function stdevs(data) {
return Math.sqrt(vars(data));
}
/**
* Generate a uniformly-distributed floating-point number within the range.
* @param min The minimum bound
* @param max The maximum bound
* @returns A random float within the range
* @example
* const y = SMath.runif(-2, 2); // 0.376...
*/
function runif(min, max) {
return expand(Math.random(), min, max);
}
/**
* Generate a uniformly-distributed integer within the range.
* @param min The minimum bound (inclusive)
* @param max The maximum bound (inclusive)
* @returns A random integer within the range
* @example
* const y = SMath.rint(-4, 3); // -4
*/
function rint(min, max) {
min |= 0;
max |= 0;
if (min < 0) {
min--;
}
if (max > 0) {
max++;
}
return clamp(runif(min, max), min, max) | 0; // `| 0` pulls toward 0
}
/**
* Generate a normally-distributed floating-point number.
* @param mean The mean of the population distribution
* @param stdev The standard deviation of the population
* @returns A random float
* @example
* const y = SMath.rnorm(2, 3); // 1.627...
*/
function rnorm(mean, stdev) {
if (mean === void 0) { mean = 0; }
if (stdev === void 0) { stdev = 1; }
return mean + stdev * Math.sqrt(-2 * Math.log(Math.random())) * Math.cos(2 * Math.PI * Math.random());
}
/**
* Generate a population of normally-distributed floating-point numbers.
* @param count The number of values to generate
* @param mean The mean of the population distribution
* @param stdev The standard deviation of the population
* @returns A population of random floats
* @example
* const dataset = SMath.rdist(3); // [ 1.051..., -0.779..., -2.254... ]
*/
function rdist(count, mean, stdev) {
if (mean === void 0) { mean = 0; }
if (stdev === void 0) { stdev = 1; }
var distribution = [];
for (var i = 0; i < count; i++) {
distribution[i] = rnorm(mean, stdev);
}
return distribution;
}
/**
* Randomize an array of arbitrary elements.
* @param stack An array of arbitrary elements
* @returns The `stack` array in a random order
* @example
* const shuffled = SMath.shuffle(['a', 'b', 'c']); // [ 'c', 'a', 'b' ]
*/
function shuffle(stack) {
var rawData = [];
for (var _i = 0, stack_1 = stack; _i < stack_1.length; _i++) {
var item = stack_1[_i];
rawData.push({ index: Math.random(), value: item });
}
return rawData.sort(function (a, b) { return a.index - b.index; }).map(function (a) { return a.value; });
}
/**
* Take the limit of a function. A return value of `NaN` indicates
* that no limit exists either due to a discontinuity or imaginary value.
* @param f Function `f(x)`
* @param x The x-value where to take the limit
* @param h The approach distance
* @param discontinuity_cutoff The discontinuity cutoff
* @returns `lim(f(x->x))`
* @example
* const y = SMath.lim(Math.log, 0); // -Infinity
*/
function lim(f, x, h, discontinuity_cutoff) {
if (h === void 0) { h = 1e-3; }
if (discontinuity_cutoff === void 0) { discontinuity_cutoff = 1; }
var center = f(x), left1 = f(x - h), left2 = f(x - h / 2), right1 = f(x + h), right2 = f(x + h / 2);
var left, right;
if (Number.isFinite(center)) {
return center;
}
// Check the limit approaching from the left
if (Number.isFinite(left1) && Number.isFinite(left2)) {
if (left2 > left1 + 2 * h) {
left = Infinity;
}
else if (left2 < left1 - 2 * h) {
left = -Infinity;
}
else {
left = avg([left1, left2]);
}
}
else if (left1 === left2) { // Handles +/-Infinity case
left = left1;
}
else {
left = NaN;
}
// Check the limit approaching from the right
if (Number.isFinite(right1) && Number.isFinite(right2)) {
if (right2 > right1 + 2 * h) {
right = Infinity;
}
else if (right2 < right1 - 2 * h) {
right = -Infinity;
}
else {
right = avg([right1, right2]);
}
}
else if (right1 === right2) { // Handles +/-Infinity case
right = right1;
}
else {
right = NaN;
}
// Check if limits match or are close
if (left === right) { // Handles +/-Infinity case
return left;
}
else if (approx(left, right, discontinuity_cutoff)) {
return avg([left, right]);
}
else if (!Number.isNaN(left) && Number.isNaN(right)) {
return left;
}
else if (Number.isNaN(left) && !Number.isNaN(right)) {
return right;
}
else {
return NaN;
}
}
/**
* Take the derivative of a function.
* @param f Function `f(x)`
* @param x The x-value where to evaluate the derivative
* @param h Small step value
* @returns `f'(x)`
* @example
* const y = SMath.differentiate(x => 3 * x ** 2, 2); // 12
*/
function differentiate(f, x, h) {
if (h === void 0) { h = 1e-3; }
return (f(x + h) - f(x - h)) / (2 * h);
}
/**
* Compute the definite integral of a function.
* @param f Function `f(x)`
* @param a The miminum integral bound
* @param b The maximum integral bound
* @param Ndx The number of rectangles to compute
* @returns `F(b)-F(a)`
* @example
* const y = SMath.integrate(x => 3 * x ** 2, 1, 2); // 7
*/
function integrate(f, a, b, Ndx) {
if (Ndx === void 0) { Ndx = 1e3; }
return ((b - a) / Ndx) * sum(linspace(a, b, Ndx).map(function (x) { return f(x); }));
}
/**
* Convert an arbitrary decimal number into a simplified fraction (or ratio).
* See `mixed()` for instructions on how to break out the whole number part.
* @param n The decimal number to convert
* @param epsilon Maximum absolute error
* @returns An object containing the fraction's numerator and denominator
* @example
* const frac = SMath.rat(0.625); // { num: 5, den: 8 }
*/
function rat(n, epsilon) {
if (epsilon === void 0) { epsilon = 1e-6; }
var num = 0, den = 1;
var sign = n < 0 ? -1 : 1;
while (!approx(sign * n, num / den, epsilon)) {
if (sign * n > num / den) {
num++;
}
else {
den++;
}
}
return { num: sign * num, den: den };
}
/**
* Convert an arbitrary decimal number into a simplified fraction, after
* breaking out the whole number part first. See `rat()` for keeping the
* number as a ratio without separating the whole number part.
* @param n A decimal number to convert
* @param epsilon Maximum absolute error
* @returns An object containing the whole part and fraction numerator and denominator
* @example
* const frac = SMath.mixed(-8 / 6); // { whole: -1, num: 1, den: 3 }
*/
function mixed(n, epsilon) {
if (epsilon === void 0) { epsilon = 1e-6; }
return __assign({ whole: n | 0 }, rat(n < -1 ? (n | 0) - n : n - (n | 0), epsilon));
}
/**
* Convert any number to its hexadecimal equivalent.
* @param n A decimal number to convert
* @param length The minimum number of digits to show
* @returns The number `n` converted to hexadecimal
* @example
* const hex = SMath.toHex(10, 2); // '0A'
* @deprecated Use native `number.toString(16)`
*/
function toHex(n, length) {
if (length === void 0) { length = 0; }
return (n < 0 ? '-' : '') + (n < 0 ? -n : n).toString(16).padStart(length, '0').toUpperCase();
}