UNPKG

smath

Version:

Small math function library

547 lines (546 loc) 16.6 kB
"use strict"; 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 * * ![NPM Downloads](https://img.shields.io/npm/d18m/smath) * ![NPM Last Update](https://img.shields.io/npm/last-update/smath) */ /** * 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(); }