From 14d1a317e365b4bbbc8a559527d9c1e6d3b44ade Mon Sep 17 00:00:00 2001 From: smikhalevski Date: Wed, 30 Aug 2023 21:03:54 +0300 Subject: [PATCH] One function per file --- src/main/all.ts | 10 - src/main/and.ts | 12 + src/main/binarySearch.ts | 41 +--- src/main/binarySearchComp.ts | 39 +++ src/main/bitwise.ts | 58 ----- src/main/cdf.ts | 71 ------ src/main/cdfGauss.ts | 65 +++++ src/main/{cdfInv.ts => cdfGaussInv.ts} | 9 +- src/main/clamp.ts | 21 ++ src/main/closest.ts | 39 +++ src/main/compose.ts | 16 ++ src/main/copyOver.ts | 5 +- src/main/cspline.ts | 49 ++-- src/main/csplineMonot.ts | 43 ++-- src/main/cycle.ts | 20 ++ src/main/decay.ts | 5 - src/main/deg.ts | 9 + src/main/easeExp.ts | 12 + src/main/easeInCubic.ts | 6 + src/main/easeInOutCubic.ts | 6 + src/main/easeInOutQuad.ts | 6 + src/main/easeInOutQuart.ts | 6 + src/main/easeInOutQuint.ts | 6 + src/main/easeInQuad.ts | 6 + src/main/easeInQuart.ts | 6 + src/main/easeInQuint.ts | 6 + src/main/easeLog.ts | 12 + src/main/easeOutCubic.ts | 6 + src/main/easeOutQuad.ts | 6 + src/main/easeOutQuart.ts | 6 + src/main/easeOutQuint.ts | 6 + src/main/easing.ts | 84 ------- src/main/gauss.ts | 18 +- src/main/gaussDist.ts | 24 -- src/main/hypot.ts | 8 + src/main/index.ts | 55 ++++- src/main/left.ts | 16 ++ src/main/lerp.ts | 52 ++-- src/main/logx.ts | 26 ++ src/main/mapRange.ts | 5 - src/main/math.ts | 197 --------------- src/main/nan.ts | 8 + src/main/or.ts | 12 + src/main/qsort.ts | 21 +- src/main/rad.ts | 9 + src/main/range.ts | 31 --- src/main/right.ts | 17 ++ src/main/scale.ts | 23 ++ src/main/seq.ts | 29 +++ src/main/sign.ts | 11 + src/main/snap.ts | 23 ++ src/main/sq.ts | 8 + src/main/trunc.ts | 9 + src/main/types.ts | 24 +- src/main/utils.ts | 4 +- src/main/xor.ts | 10 + src/test/and.test.ts | 10 + src/test/binarySearch.test.ts | 10 +- src/test/bitwise.test.ts | 61 ----- src/test/cdf.test.ts | 13 - src/test/cdfGauss.test.ts | 11 + src/test/clamp.test.ts | 13 + src/test/closest.test.ts | 9 + src/test/copyOver.test.ts | 22 +- src/test/cspline.test.ts | 16 +- src/test/csplineMonot.test.ts | 18 +- src/test/cycle.test.ts | 16 ++ src/test/deg.test.ts | 6 + src/test/left.test.ts | 10 + src/test/lerp.test.ts | 16 +- src/test/logx.test.ts | 26 ++ src/test/math.test.ts | 182 -------------- src/test/or.test.ts | 10 + src/test/qsort.test.ts | 324 ++++++++++++------------- src/test/rad.test.ts | 6 + src/test/range.test.ts | 17 -- src/test/right.test.ts | 10 + src/test/scale.test.ts | 13 + src/test/seq.test.ts | 8 + src/test/snap.test.ts | 8 + src/test/xor.test.ts | 10 + 81 files changed, 1058 insertions(+), 1119 deletions(-) delete mode 100644 src/main/all.ts create mode 100644 src/main/and.ts create mode 100644 src/main/binarySearchComp.ts delete mode 100644 src/main/bitwise.ts delete mode 100644 src/main/cdf.ts create mode 100644 src/main/cdfGauss.ts rename src/main/{cdfInv.ts => cdfGaussInv.ts} (83%) create mode 100644 src/main/clamp.ts create mode 100644 src/main/closest.ts create mode 100644 src/main/compose.ts create mode 100644 src/main/cycle.ts delete mode 100644 src/main/decay.ts create mode 100644 src/main/deg.ts create mode 100644 src/main/easeExp.ts create mode 100644 src/main/easeInCubic.ts create mode 100644 src/main/easeInOutCubic.ts create mode 100644 src/main/easeInOutQuad.ts create mode 100644 src/main/easeInOutQuart.ts create mode 100644 src/main/easeInOutQuint.ts create mode 100644 src/main/easeInQuad.ts create mode 100644 src/main/easeInQuart.ts create mode 100644 src/main/easeInQuint.ts create mode 100644 src/main/easeLog.ts create mode 100644 src/main/easeOutCubic.ts create mode 100644 src/main/easeOutQuad.ts create mode 100644 src/main/easeOutQuart.ts create mode 100644 src/main/easeOutQuint.ts delete mode 100644 src/main/easing.ts delete mode 100644 src/main/gaussDist.ts create mode 100644 src/main/hypot.ts create mode 100644 src/main/left.ts create mode 100644 src/main/logx.ts delete mode 100644 src/main/mapRange.ts delete mode 100644 src/main/math.ts create mode 100644 src/main/nan.ts create mode 100644 src/main/or.ts create mode 100644 src/main/rad.ts delete mode 100644 src/main/range.ts create mode 100644 src/main/right.ts create mode 100644 src/main/scale.ts create mode 100644 src/main/seq.ts create mode 100644 src/main/sign.ts create mode 100644 src/main/snap.ts create mode 100644 src/main/sq.ts create mode 100644 src/main/trunc.ts create mode 100644 src/main/xor.ts create mode 100644 src/test/and.test.ts delete mode 100644 src/test/bitwise.test.ts delete mode 100644 src/test/cdf.test.ts create mode 100644 src/test/cdfGauss.test.ts create mode 100644 src/test/clamp.test.ts create mode 100644 src/test/closest.test.ts create mode 100644 src/test/cycle.test.ts create mode 100644 src/test/deg.test.ts create mode 100644 src/test/left.test.ts create mode 100644 src/test/logx.test.ts delete mode 100644 src/test/math.test.ts create mode 100644 src/test/or.test.ts create mode 100644 src/test/rad.test.ts delete mode 100644 src/test/range.test.ts create mode 100644 src/test/right.test.ts create mode 100644 src/test/scale.test.ts create mode 100644 src/test/seq.test.ts create mode 100644 src/test/snap.test.ts create mode 100644 src/test/xor.test.ts diff --git a/src/main/all.ts b/src/main/all.ts deleted file mode 100644 index 4eef260..0000000 --- a/src/main/all.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Mapper } from './types'; - -export function all(...fns: Mapper[]): Mapper { - return x => { - for (const fn of fns) { - x = fn(x); - } - return x; - }; -} diff --git a/src/main/and.ts b/src/main/and.ts new file mode 100644 index 0000000..8688e4c --- /dev/null +++ b/src/main/and.ts @@ -0,0 +1,12 @@ +import { MASK_LOWER, MASK_UPPER } from './utils'; + +/** + * Bitwise AND operator for large unsigned integers. + * + * @param x The left integer. + * @param y The right integer. + * @group Bitwise Operations + */ +export function and(x: number, y: number): number { + return ((x / MASK_UPPER) & (y / MASK_UPPER)) * MASK_UPPER + (x & MASK_LOWER & (y & MASK_LOWER)); +} diff --git a/src/main/binarySearch.ts b/src/main/binarySearch.ts index f41d733..0420e53 100644 --- a/src/main/binarySearch.ts +++ b/src/main/binarySearch.ts @@ -1,5 +1,3 @@ -import { Comparator } from './types'; - /** * Searches the specified array `xs` for the specified value `x` using the binary search algorithm. The array must be * sorted into ascending order according to the natural ordering of its elements prior to making this call. If it is @@ -8,8 +6,6 @@ import { Comparator } from './types'; * @param xs The array to be searched. * @param x The value to be searched for. * @param n The maximum index in `xs` that is searched (exclusive). - * @param comparator The callback that defines the sort order. If omitted, the array elements are compared using - * [comparison operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#comparison_operators). * @returns The index of the searched value, if it is contained in the array; otherwise, -(insertion point) - 1. The * insertion point is defined as the point at which the searched value would be inserted into the array: the index of * the first element greater than the searched value, or array length if all elements in the array are less than the @@ -17,37 +13,22 @@ import { Comparator } from './types'; * found. * @group Search */ -export function binarySearch(xs: ArrayLike, x: T, n = xs.length, comparator?: Comparator): number { - let i, a; +export function binarySearch(xs: ArrayLike, x: T, n = xs.length): number { + let i, xi; let m = 0; --n; - if (comparator !== undefined) { - while (m <= n) { - i = (n + m) >> 1; - a = comparator(xs[i], x); - - if (a < 0) { - m = i + 1; - } else if (a > 0) { - n = i - 1; - } else { - return i; - } - } - } else { - while (m <= n) { - i = (n + m) >> 1; - a = xs[i]; + while (m <= n) { + i = (n + m) >> 1; + xi = xs[i]; - if (a < x) { - m = i + 1; - } else if (a > x) { - n = i - 1; - } else { - return i; - } + if (xi < x) { + m = i + 1; + } else if (xi > x) { + n = i - 1; + } else { + return i; } } return -m - 1; diff --git a/src/main/binarySearchComp.ts b/src/main/binarySearchComp.ts new file mode 100644 index 0000000..f62f2e6 --- /dev/null +++ b/src/main/binarySearchComp.ts @@ -0,0 +1,39 @@ +import { Comparator } from './types'; + +/** + * Searches the specified array `xs` for the specified value `x` using the binary search algorithm. The array must be + * sorted into ascending order according to the `comparator` prior to making this call. If it is not sorted, the results + * are undefined. + * + * @param xs The array to be searched. + * @param x The value to be searched for. + * @param comparator The callback that defines the sort order. If omitted, the array elements are compared using + * [comparison operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#comparison_operators). + * @param n The maximum index in `xs` that is searched (exclusive). + * @returns The index of the searched value, if it is contained in the array; otherwise, -(insertion point) - 1. The + * insertion point is defined as the point at which the searched value would be inserted into the array: the index of + * the first element greater than the searched value, or array length if all elements in the array are less than the + * specified key. Note that this guarantees that the return value will be ≥ 0 if and only if the searched value is + * found. + * @group Search + */ +export function binarySearchComp(xs: ArrayLike, x: T, comparator: Comparator, n = xs.length): number { + let i, result; + let m = 0; + + --n; + + while (m <= n) { + i = (n + m) >> 1; + result = comparator(xs[i], x); + + if (result < 0) { + m = i + 1; + } else if (result > 0) { + n = i - 1; + } else { + return i; + } + } + return -m - 1; +} diff --git a/src/main/bitwise.ts b/src/main/bitwise.ts deleted file mode 100644 index 4770bbd..0000000 --- a/src/main/bitwise.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { HI, LO, pow } from './utils'; - -/** - * Bitwise left shift operator for large unsigned integers. - * - * ```ts - * left(0xab, 8); // → 0xab_00 - * // or - * 0xab << 8; - * ``` - * - * @group Bitwise Operations - */ -export function left(x: number, shift: number): number { - return x * pow(2, shift); -} - -/** - * Bitwise right shift operator for large unsigned integers. - * - * ```ts - * right(0xab_cd, 8); // → 0xab - * // or - * 0xab_cd >> 8; - * ``` - * - * @group Bitwise Operations - */ -export function right(x: number, shift: number): number { - return x / pow(2, shift); -} - -/** - * Bitwise XOR operator for large unsigned integers. - * - * @group Bitwise Operations - */ -export function xor(a: number, b: number): number { - return ((a / HI) ^ (b / HI)) * HI + ((a & LO) ^ (b & LO)); -} - -/** - * Bitwise OR operator for large unsigned integers. - * - * @group Bitwise Operations - */ -export function or(a: number, b: number): number { - return ((a / HI) | (b / HI)) * HI + ((a & LO) | (b & LO)); -} - -/** - * Bitwise AND operator for large unsigned integers. - * - * @group Bitwise Operations - */ -export function and(a: number, b: number): number { - return ((a / HI) & (b / HI)) * HI + (a & LO & (b & LO)); -} diff --git a/src/main/cdf.ts b/src/main/cdf.ts deleted file mode 100644 index c9d039a..0000000 --- a/src/main/cdf.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Mapper } from './types'; -import { abs } from './utils'; - -/** - * Cumulative distribution function for Gaussian distribution. - * - * @param m Mean of the input value. - * @param d Standard deviation of the input value. - */ -export function cdf(m = 0, d = 1): Mapper { - return x => { - let p; - let z = (x - m) / d; - - if (z !== z) { - return NaN; - } - if (z === 0) { - return 0.5; - } - if (z <= -3.5) { - return 0; - } - if (z >= 3.5) { - return 1; - } - - p = abs(z); - p = zTable[(p * 10) | 0][((p * 100) | 0) - ((p * 10) | 0) * 10]; - - return z < 0 ? 1 - p : p; - }; -} - -const zTable = [ - [0.5, 0.504, 0.508, 0.512, 0.516, 0.5199, 0.5239, 0.5279, 0.5319, 0.5359], - [0.5398, 0.5438, 0.5478, 0.5517, 0.5557, 0.5596, 0.5636, 0.5675, 0.5714, 0.5753], - [0.5793, 0.5832, 0.5871, 0.591, 0.5948, 0.5987, 0.6026, 0.6064, 0.6103, 0.6141], - [0.6179, 0.6217, 0.6255, 0.6293, 0.6331, 0.6368, 0.6406, 0.6443, 0.648, 0.6517], - [0.6554, 0.6591, 0.6628, 0.6664, 0.67, 0.6736, 0.6772, 0.6808, 0.6844, 0.6879], - [0.6915, 0.695, 0.6985, 0.7019, 0.7054, 0.7088, 0.7123, 0.7157, 0.719, 0.7224], - [0.7257, 0.7291, 0.7324, 0.7357, 0.7389, 0.7422, 0.7454, 0.7486, 0.7517, 0.7549], - [0.758, 0.7611, 0.7642, 0.7673, 0.7704, 0.7734, 0.7764, 0.7794, 0.7823, 0.7852], - [0.7881, 0.791, 0.7939, 0.7967, 0.7995, 0.8023, 0.8051, 0.8078, 0.8106, 0.8133], - [0.8159, 0.8186, 0.8212, 0.8238, 0.8264, 0.8289, 0.8315, 0.834, 0.8365, 0.8389], - [0.8413, 0.8438, 0.8461, 0.8485, 0.8508, 0.8531, 0.8554, 0.8577, 0.8599, 0.8621], - [0.8643, 0.8665, 0.8686, 0.8708, 0.8729, 0.8749, 0.877, 0.879, 0.881, 0.883], - [0.8849, 0.8869, 0.8888, 0.8907, 0.8925, 0.8944, 0.8962, 0.898, 0.8997, 0.9015], - [0.9032, 0.9049, 0.9066, 0.9082, 0.9099, 0.9115, 0.9131, 0.9147, 0.9162, 0.9177], - [0.9192, 0.9207, 0.9222, 0.9236, 0.9251, 0.9265, 0.9279, 0.9292, 0.9306, 0.9319], - [0.9332, 0.9345, 0.9357, 0.937, 0.9382, 0.9394, 0.9406, 0.9418, 0.9429, 0.9441], - [0.9452, 0.9463, 0.9474, 0.9484, 0.9495, 0.9505, 0.9515, 0.9525, 0.9535, 0.9545], - [0.9554, 0.9564, 0.9573, 0.9582, 0.9591, 0.9599, 0.9608, 0.9616, 0.9625, 0.9633], - [0.9641, 0.9649, 0.9656, 0.9664, 0.9671, 0.9678, 0.9686, 0.9693, 0.9699, 0.9706], - [0.9713, 0.9719, 0.9726, 0.9732, 0.9738, 0.9744, 0.975, 0.9756, 0.9761, 0.9767], - [0.9772, 0.9778, 0.9783, 0.9788, 0.9793, 0.9798, 0.9803, 0.9808, 0.9812, 0.9817], - [0.9821, 0.9826, 0.983, 0.9834, 0.9838, 0.9842, 0.9846, 0.985, 0.9854, 0.9857], - [0.9861, 0.9864, 0.9868, 0.9871, 0.9875, 0.9878, 0.9881, 0.9884, 0.9887, 0.989], - [0.9893, 0.9896, 0.9898, 0.9901, 0.9904, 0.9906, 0.9909, 0.9911, 0.9913, 0.9916], - [0.9918, 0.992, 0.9922, 0.9925, 0.9927, 0.9929, 0.9931, 0.9932, 0.9934, 0.9936], - [0.9938, 0.994, 0.9941, 0.9943, 0.9945, 0.9946, 0.9948, 0.9949, 0.9951, 0.9952], - [0.9953, 0.9955, 0.9956, 0.9957, 0.9959, 0.996, 0.9961, 0.9962, 0.9963, 0.9964], - [0.9965, 0.9966, 0.9967, 0.9968, 0.9969, 0.997, 0.9971, 0.9972, 0.9973, 0.9974], - [0.9974, 0.9975, 0.9976, 0.9977, 0.9977, 0.9978, 0.9979, 0.9979, 0.998, 0.9981], - [0.9981, 0.9982, 0.9982, 0.9983, 0.9984, 0.9984, 0.9985, 0.9985, 0.9986, 0.9986], - [0.9987, 0.9987, 0.9987, 0.9988, 0.9988, 0.9989, 0.9989, 0.9989, 0.999, 0.999], - [0.999, 0.9991, 0.9991, 0.9991, 0.9992, 0.9992, 0.9992, 0.9992, 0.9993, 0.9993], - [0.9993, 0.9993, 0.9994, 0.9994, 0.9994, 0.9994, 0.9994, 0.9995, 0.9995, 0.9995], - [0.9995, 0.9995, 0.9995, 0.9996, 0.9996, 0.9996, 0.9996, 0.9996, 0.9996, 0.9997], - [0.9997, 0.9997, 0.9997, 0.9997, 0.9997, 0.9997, 0.9997, 0.9997, 0.9997, 0.9998], -]; diff --git a/src/main/cdfGauss.ts b/src/main/cdfGauss.ts new file mode 100644 index 0000000..434ca32 --- /dev/null +++ b/src/main/cdfGauss.ts @@ -0,0 +1,65 @@ +import { Mapper } from './types'; +import { abs } from './utils'; + +/** + * Cumulative distribution function for Gaussian (normal) distribution. + * + * ```ts + * seq(3).map(cdfGauss(0.5, 0.3)) + * // ⮕ [ 0.04, 0.5, 0.95 ] + * ``` + * + * @param mean Mean of the input value. + * @param deviation The standard deviation of the input value. + * @group Distributions + */ +export function cdfGauss(mean = 0, deviation = 1): Mapper { + return x => { + let p; + let z = (x - mean) / deviation; + + if (z !== z) { + return NaN; + } + if (z === 0) { + return 0.5; + } + if (z <= -3.5) { + return 0; + } + if (z >= 3.5) { + return 1; + } + + p = zTable[(abs(z) * 100) | 0]; + + return z < 0 ? 1 - p : p; + }; +} + +const zTable = [ + 0.5, 0.504, 0.508, 0.512, 0.516, 0.5199, 0.5239, 0.5279, 0.5319, 0.5359, 0.5398, 0.5438, 0.5478, 0.5517, 0.5557, + 0.5596, 0.5636, 0.5675, 0.5714, 0.5753, 0.5793, 0.5832, 0.5871, 0.591, 0.5948, 0.5987, 0.6026, 0.6064, 0.6103, 0.6141, + 0.6179, 0.6217, 0.6255, 0.6293, 0.6331, 0.6368, 0.6406, 0.6443, 0.648, 0.6517, 0.6554, 0.6591, 0.6628, 0.6664, 0.67, + 0.6736, 0.6772, 0.6808, 0.6844, 0.6879, 0.6915, 0.695, 0.6985, 0.7019, 0.7054, 0.7088, 0.7123, 0.7157, 0.719, 0.7224, + 0.7257, 0.7291, 0.7324, 0.7357, 0.7389, 0.7422, 0.7454, 0.7486, 0.7517, 0.7549, 0.758, 0.7611, 0.7642, 0.7673, 0.7704, + 0.7734, 0.7764, 0.7794, 0.7823, 0.7852, 0.7881, 0.791, 0.7939, 0.7967, 0.7995, 0.8023, 0.8051, 0.8078, 0.8106, 0.8133, + 0.8159, 0.8186, 0.8212, 0.8238, 0.8264, 0.8289, 0.8315, 0.834, 0.8365, 0.8389, 0.8413, 0.8438, 0.8461, 0.8485, 0.8508, + 0.8531, 0.8554, 0.8577, 0.8599, 0.8621, 0.8643, 0.8665, 0.8686, 0.8708, 0.8729, 0.8749, 0.877, 0.879, 0.881, 0.883, + 0.8849, 0.8869, 0.8888, 0.8907, 0.8925, 0.8944, 0.8962, 0.898, 0.8997, 0.9015, 0.9032, 0.9049, 0.9066, 0.9082, 0.9099, + 0.9115, 0.9131, 0.9147, 0.9162, 0.9177, 0.9192, 0.9207, 0.9222, 0.9236, 0.9251, 0.9265, 0.9279, 0.9292, 0.9306, + 0.9319, 0.9332, 0.9345, 0.9357, 0.937, 0.9382, 0.9394, 0.9406, 0.9418, 0.9429, 0.9441, 0.9452, 0.9463, 0.9474, 0.9484, + 0.9495, 0.9505, 0.9515, 0.9525, 0.9535, 0.9545, 0.9554, 0.9564, 0.9573, 0.9582, 0.9591, 0.9599, 0.9608, 0.9616, + 0.9625, 0.9633, 0.9641, 0.9649, 0.9656, 0.9664, 0.9671, 0.9678, 0.9686, 0.9693, 0.9699, 0.9706, 0.9713, 0.9719, + 0.9726, 0.9732, 0.9738, 0.9744, 0.975, 0.9756, 0.9761, 0.9767, 0.9772, 0.9778, 0.9783, 0.9788, 0.9793, 0.9798, 0.9803, + 0.9808, 0.9812, 0.9817, 0.9821, 0.9826, 0.983, 0.9834, 0.9838, 0.9842, 0.9846, 0.985, 0.9854, 0.9857, 0.9861, 0.9864, + 0.9868, 0.9871, 0.9875, 0.9878, 0.9881, 0.9884, 0.9887, 0.989, 0.9893, 0.9896, 0.9898, 0.9901, 0.9904, 0.9906, 0.9909, + 0.9911, 0.9913, 0.9916, 0.9918, 0.992, 0.9922, 0.9925, 0.9927, 0.9929, 0.9931, 0.9932, 0.9934, 0.9936, 0.9938, 0.994, + 0.9941, 0.9943, 0.9945, 0.9946, 0.9948, 0.9949, 0.9951, 0.9952, 0.9953, 0.9955, 0.9956, 0.9957, 0.9959, 0.996, 0.9961, + 0.9962, 0.9963, 0.9964, 0.9965, 0.9966, 0.9967, 0.9968, 0.9969, 0.997, 0.9971, 0.9972, 0.9973, 0.9974, 0.9974, 0.9975, + 0.9976, 0.9977, 0.9977, 0.9978, 0.9979, 0.9979, 0.998, 0.9981, 0.9981, 0.9982, 0.9982, 0.9983, 0.9984, 0.9984, 0.9985, + 0.9985, 0.9986, 0.9986, 0.9987, 0.9987, 0.9987, 0.9988, 0.9988, 0.9989, 0.9989, 0.9989, 0.999, 0.999, 0.999, 0.9991, + 0.9991, 0.9991, 0.9992, 0.9992, 0.9992, 0.9992, 0.9993, 0.9993, 0.9993, 0.9993, 0.9994, 0.9994, 0.9994, 0.9994, + 0.9994, 0.9995, 0.9995, 0.9995, 0.9995, 0.9995, 0.9995, 0.9996, 0.9996, 0.9996, 0.9996, 0.9996, 0.9996, 0.9997, + 0.9997, 0.9997, 0.9997, 0.9997, 0.9997, 0.9997, 0.9997, 0.9997, 0.9997, 0.9998, +]; diff --git a/src/main/cdfInv.ts b/src/main/cdfGaussInv.ts similarity index 83% rename from src/main/cdfInv.ts rename to src/main/cdfGaussInv.ts index e66ccb4..e5d2761 100644 --- a/src/main/cdfInv.ts +++ b/src/main/cdfGaussInv.ts @@ -1,11 +1,12 @@ import { log, sqrt } from './utils'; +import { Mapper } from './types'; /** - * Inverse cumulative distribution function for Gaussian distribution. + * Inverse cumulative distribution function for Gaussian (normal) distribution. * - * @param x The value in range (0, 1). + * @group Distributions */ -export function cdfInv(x: number): number { +export const cdfGaussInv: Mapper = x => { let q, r; if (x <= 0) { @@ -43,4 +44,4 @@ export function cdfInv(x: number): number { r + 1) ); -} +}; diff --git a/src/main/clamp.ts b/src/main/clamp.ts new file mode 100644 index 0000000..2646060 --- /dev/null +++ b/src/main/clamp.ts @@ -0,0 +1,21 @@ +import { Mapper } from './types'; + +/** + * Clamps `x` to range [`a`, `b`]. + * + * ```ts + * clamp()(0.2); + * // ⮕ 0.2 + * + * clamp()(-2); + * // ⮕ 0.5 + * + * clamp(5, 10)(33); + * // ⮕ 10 + * ``` + * + * @group Math + */ +export function clamp(a = 0, b = 1): Mapper { + return x => (x < a ? a : x > b ? b : x); +} diff --git a/src/main/closest.ts b/src/main/closest.ts new file mode 100644 index 0000000..f92a977 --- /dev/null +++ b/src/main/closest.ts @@ -0,0 +1,39 @@ +import { Mapper } from './types'; +import { abs } from './utils'; +import { nan } from './nan'; + +/** + * Returns the closest value to `x` from `xs`. + * + * If `arr` is empty then `x` is returned as is. + * + * ```ts + * closest([0, 3, 6])(1.8); + * // ⮕ 3 + * ``` + * + * @group Math + */ +export function closest(xs: ArrayLike): Mapper { + const n = xs.length; + + if (n === 0) { + return nan; + } + + return x => { + let targetX = x; + let dx = 1 / 0; + + for (let i = 0; i < n; ++i) { + const xi = xs[i]; + const dxi = abs(xi - x); + + if (dxi < dx) { + targetX = xi; + dx = dxi; + } + } + return targetX; + }; +} diff --git a/src/main/compose.ts b/src/main/compose.ts new file mode 100644 index 0000000..25eaa76 --- /dev/null +++ b/src/main/compose.ts @@ -0,0 +1,16 @@ +import { Mapper } from './types'; + +/** + * Composes mapper functions into a single function. + * + * @param fns The composed mapper functions. + * @group Utils + */ +export function compose(...fns: Mapper[]): Mapper { + return x => { + for (const fn of fns) { + x = fn(x); + } + return x; + }; +} diff --git a/src/main/copyOver.ts b/src/main/copyOver.ts index c8d2b30..31b3858 100644 --- a/src/main/copyOver.ts +++ b/src/main/copyOver.ts @@ -10,10 +10,11 @@ import { ArrayValue, MutableArrayLike } from './types'; * @param destIndex The start position in the destination array. * @param n The number of elements to copy. * @returns The destination array. - * @template T The type of the destination array. + * @template T The destination array. + * @group Arrays */ export function copyOver>( - src: MutableArrayLike>, + src: ArrayLike>, dest: T, srcIndex = 0, destIndex = 0, diff --git a/src/main/cspline.ts b/src/main/cspline.ts index 3470dfa..ed908bb 100644 --- a/src/main/cspline.ts +++ b/src/main/cspline.ts @@ -1,6 +1,7 @@ -import { Interpolator, MutableArrayLike } from './types'; +import { Mapper, MutableArrayLike } from './types'; import { binarySearch } from './binarySearch'; import { min } from './utils'; +import { nan } from './nan'; /** * Returns a natural cubic spline interpolation function for given pivot points. @@ -9,8 +10,8 @@ import { min } from './utils'; * interpolation. * * ```ts - * const f = cspline(xs, ys); - * const y = f(x); + * const fn = cspline(xs, ys); + * const y = fn(x); * ``` * * @param xs The array of X coordinates of pivot points in ascending order. @@ -18,30 +19,22 @@ import { min } from './utils'; * @returns The function that takes X coordinate and returns an interpolated Y coordinate. * @group Interpolation */ -export function cspline(xs: ArrayLike, ys: ArrayLike): Interpolator { - let n = 0; +export function cspline(xs: ArrayLike, ys: ArrayLike): Mapper { + const n = min(xs.length, ys.length); - const splines: number[] = []; - - const fn: Interpolator = x => (n === 0 ? NaN : n === 1 ? ys[0] : interpolateCSpline(xs, ys, x, n, splines)); - - fn.update = (nextXs, nextYs) => { - const nextN = min(nextXs.length, nextYs.length); - xs = nextXs; - ys = nextYs; + if (n === 0) { + return nan; + } + if (n === 1) { + const y0 = ys[0]; + return () => y0; + } - if (nextN > 1) { - for (let i = n * 3; i < nextN * 3; ++i) { - splines.push(0); - } - populateCSplines(xs, ys, nextN, splines); - } - n = nextN; - }; + const splines = new Float32Array(n * 3); - fn.update(xs, ys); + populateCSplines(xs, ys, n, splines); - return fn; + return x => interpolateCSpline(xs, ys, x, n, splines); } /** @@ -64,6 +57,7 @@ export function cspline(xs: ArrayLike, ys: ArrayLike): Interpola * @see {@link createCSplines} * @see {@link https://en.wikipedia.org/wiki/Spline_(mathematics)#Algorithm_for_computing_natural_cubic_splines Algorithm for computing natural cubic splines} * @group Interpolation + * @internal */ export function interpolateCSpline( xs: ArrayLike, @@ -72,6 +66,8 @@ export function interpolateCSpline( n: number, splines: MutableArrayLike ): number { + let i, k, dx; + if (x <= xs[0]) { return ys[0]; } @@ -79,14 +75,14 @@ export function interpolateCSpline( return ys[n - 1]; } - let i = binarySearch(xs, x, n); + i = binarySearch(xs, x, n); if (i >= 0) { return ys[i]; } i = ~i; - const k = i * 3; - const dx = x - xs[i]; + k = i * 3; + dx = x - xs[i]; return ys[i] + (splines[k + 2] + (splines[k] / 2 + (splines[k + 1] * dx) / 6) * dx) * dx; } @@ -104,6 +100,7 @@ export function interpolateCSpline( * @param n The number of pivot points, usually equals `xs.length`. * @param splines Mutable array that would be populated with spline components, length must be at least `n * 3`. * @group Interpolation + * @internal */ export function populateCSplines( xs: ArrayLike, diff --git a/src/main/csplineMonot.ts b/src/main/csplineMonot.ts index acb969b..b4f33d9 100644 --- a/src/main/csplineMonot.ts +++ b/src/main/csplineMonot.ts @@ -1,6 +1,7 @@ import { binarySearch } from './binarySearch'; -import { Interpolator, MutableArrayLike } from './types'; +import { Mapper, MutableArrayLike } from './types'; import { min } from './utils'; +import { nan } from './nan'; /** * Returns a monotonous cubic spline interpolation function for given pivot points, that prevent overshoot of @@ -10,8 +11,8 @@ import { min } from './utils'; * during interpolation. * * ```ts - * const f = csplineMonot(xs, ys); - * const y = f(x); + * const fn = csplineMonot(xs, ys); + * const y = fn(x); * ``` * * @param xs The array of X coordinates of pivot points in ascending order. @@ -21,30 +22,22 @@ import { min } from './utils'; * @see {@link https://en.wikipedia.org/wiki/Monotone_cubic_interpolation Monotone cubic interpolation} * @group Interpolation */ -export function csplineMonot(xs: ArrayLike, ys: ArrayLike): Interpolator { - let n = 0; +export function csplineMonot(xs: ArrayLike, ys: ArrayLike): Mapper { + const n = min(xs.length, ys.length); - const splines: number[] = []; - - const fn: Interpolator = x => (n === 0 ? NaN : n === 1 ? ys[0] : interpolateCSplineMonot(xs, ys, x, n, splines)); - - fn.update = (nextXs, nextYs) => { - const nextN = min(nextXs.length, nextYs.length); - xs = nextXs; - ys = nextYs; + if (n === 0) { + return nan; + } + if (n === 1) { + const y0 = ys[0]; + return () => y0; + } - if (nextN > 1) { - for (let i = n * 3 - 2; i < nextN * 3 - 2; ++i) { - splines.push(0); - } - populateCSplinesMonot(xs, ys, nextN, splines); - } - n = nextN; - }; + const splines = new Float32Array(n * 3 - 2); - fn.update(xs, ys); + populateCSplinesMonot(xs, ys, n, splines); - return fn; + return x => interpolateCSplineMonot(xs, ys, x, n, splines); } /** @@ -67,13 +60,14 @@ export function csplineMonot(xs: ArrayLike, ys: ArrayLike): Inte * * @see {@link https://en.wikipedia.org/wiki/Monotone_cubic_interpolation Monotone cubic interpolation} * @group Interpolation + * @internal */ export function interpolateCSplineMonot( xs: ArrayLike, ys: ArrayLike, x: number, n: number, - splines: number[] + splines: MutableArrayLike ): number { if (x <= xs[0]) { return ys[0]; @@ -110,6 +104,7 @@ export function interpolateCSplineMonot( * * @see {@link https://en.wikipedia.org/wiki/Monotone_cubic_interpolation Monotone cubic interpolation} * @group Interpolation + * @internal */ export function populateCSplinesMonot( xs: ArrayLike, diff --git a/src/main/cycle.ts b/src/main/cycle.ts new file mode 100644 index 0000000..3212f2d --- /dev/null +++ b/src/main/cycle.ts @@ -0,0 +1,20 @@ +import { Mapper } from './types'; +import { ceil } from './utils'; + +/** + * Brings `x` to the range [`a`, `b`] by adding or subtracting the range size |`a` - `b`|. + * + * ```ts + * cycle(0, 10)(12); + * // ⮕ 2 + * + * cycle(100, 33)(-333); + * // ⮕ 69 + * ``` + * + * @group Math + */ +export function cycle(a = 0, b = 1): Mapper { + const d = b - a; + return x => (x > a ? x - ceil((x - b) / d) * d : x + ceil((a - x) / d) * d); +} diff --git a/src/main/decay.ts b/src/main/decay.ts deleted file mode 100644 index 74431ec..0000000 --- a/src/main/decay.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Mapper } from './types'; - -export function decay(fn: Mapper): Mapper { - return x => 1 - fn(x); -} diff --git a/src/main/deg.ts b/src/main/deg.ts new file mode 100644 index 0000000..7435ddd --- /dev/null +++ b/src/main/deg.ts @@ -0,0 +1,9 @@ +import { Mapper } from './types'; +import { PI } from './utils'; + +/** + * Converts degrees to radians. + * + * @group Math + */ +export const deg: Mapper = x => (x / PI) * 180; diff --git a/src/main/easeExp.ts b/src/main/easeExp.ts new file mode 100644 index 0000000..39f36d2 --- /dev/null +++ b/src/main/easeExp.ts @@ -0,0 +1,12 @@ +import { Mapper } from './types'; +import { exp } from './utils'; + +/** + * Maps `x` ∈ [0, 1] exponentially to [0, 1]. + * + * @param stiffness Greater values produce more bent curve, `easeExp(0)(x) = x`. + * @group Easing + */ +export function easeExp(stiffness = 1): Mapper { + return x => (stiffness === 0 ? x : (exp(stiffness * x) - 1) / (exp(stiffness) - 1)); +} diff --git a/src/main/easeInCubic.ts b/src/main/easeInCubic.ts new file mode 100644 index 0000000..c0dffd0 --- /dev/null +++ b/src/main/easeInCubic.ts @@ -0,0 +1,6 @@ +import { Mapper } from './types'; + +/** + * @group Easing + */ +export const easeInCubic: Mapper = x => x * x * x; diff --git a/src/main/easeInOutCubic.ts b/src/main/easeInOutCubic.ts new file mode 100644 index 0000000..c80395e --- /dev/null +++ b/src/main/easeInOutCubic.ts @@ -0,0 +1,6 @@ +import { Mapper } from './types'; + +/** + * @group Easing + */ +export const easeInOutCubic: Mapper = x => (x < 0.5 ? 4 * x * x * x : (x - 1) * (2 * x - 2) * (2 * x - 2) + 1); diff --git a/src/main/easeInOutQuad.ts b/src/main/easeInOutQuad.ts new file mode 100644 index 0000000..2cdcd64 --- /dev/null +++ b/src/main/easeInOutQuad.ts @@ -0,0 +1,6 @@ +import { Mapper } from './types'; + +/** + * @group Easing + */ +export const easeInOutQuad: Mapper = x => (x < 0.5 ? 2 * x * x : -1 + (4 - 2 * x) * x); diff --git a/src/main/easeInOutQuart.ts b/src/main/easeInOutQuart.ts new file mode 100644 index 0000000..a747b1f --- /dev/null +++ b/src/main/easeInOutQuart.ts @@ -0,0 +1,6 @@ +import { Mapper } from './types'; + +/** + * @group Easing + */ +export const easeInOutQuart: Mapper = x => (x < 0.5 ? 8 * x * x * x * x : 1 - 8 * --x * x * x * x); diff --git a/src/main/easeInOutQuint.ts b/src/main/easeInOutQuint.ts new file mode 100644 index 0000000..21f6b64 --- /dev/null +++ b/src/main/easeInOutQuint.ts @@ -0,0 +1,6 @@ +import { Mapper } from './types'; + +/** + * @group Easing + */ +export const easeInOutQuint: Mapper = x => (x < 0.5 ? 16 * x * x * x * x * x : 1 + 16 * --x * x * x * x * x); diff --git a/src/main/easeInQuad.ts b/src/main/easeInQuad.ts new file mode 100644 index 0000000..af6bad3 --- /dev/null +++ b/src/main/easeInQuad.ts @@ -0,0 +1,6 @@ +import { Mapper } from './types'; + +/** + * @group Easing + */ +export const easeInQuad: Mapper = x => x * x; diff --git a/src/main/easeInQuart.ts b/src/main/easeInQuart.ts new file mode 100644 index 0000000..bbff8ad --- /dev/null +++ b/src/main/easeInQuart.ts @@ -0,0 +1,6 @@ +import { Mapper } from './types'; + +/** + * @group Easing + */ +export const easeInQuart: Mapper = x => x * x * x * x; diff --git a/src/main/easeInQuint.ts b/src/main/easeInQuint.ts new file mode 100644 index 0000000..b63053b --- /dev/null +++ b/src/main/easeInQuint.ts @@ -0,0 +1,6 @@ +import { Mapper } from './types'; + +/** + * @group Easing + */ +export const easeInQuint: Mapper = x => x * x * x * x * x; diff --git a/src/main/easeLog.ts b/src/main/easeLog.ts new file mode 100644 index 0000000..5f369a8 --- /dev/null +++ b/src/main/easeLog.ts @@ -0,0 +1,12 @@ +import { Mapper } from './types'; +import { exp, log } from './utils'; + +/** + * Maps `x` ∈ [0, 1] logarithmically to [0, 1]. + * + * @param stiffness Greater values produce more bent curve, `easeLog(0)(x) = x`. + * @group Easing + */ +export function easeLog(stiffness = 1): Mapper { + return x => (stiffness === 0 ? x : log(x * (exp(stiffness) - 1) + 1) / stiffness); +} diff --git a/src/main/easeOutCubic.ts b/src/main/easeOutCubic.ts new file mode 100644 index 0000000..e9f9817 --- /dev/null +++ b/src/main/easeOutCubic.ts @@ -0,0 +1,6 @@ +import { Mapper } from './types'; + +/** + * @group Easing + */ +export const easeOutCubic: Mapper = x => --x * x * x + 1; diff --git a/src/main/easeOutQuad.ts b/src/main/easeOutQuad.ts new file mode 100644 index 0000000..f3c779f --- /dev/null +++ b/src/main/easeOutQuad.ts @@ -0,0 +1,6 @@ +import { Mapper } from './types'; + +/** + * @group Easing + */ +export const easeOutQuad: Mapper = x => x * (2 - x); diff --git a/src/main/easeOutQuart.ts b/src/main/easeOutQuart.ts new file mode 100644 index 0000000..1875f31 --- /dev/null +++ b/src/main/easeOutQuart.ts @@ -0,0 +1,6 @@ +import { Mapper } from './types'; + +/** + * @group Easing + */ +export const easeOutQuart: Mapper = x => 1 - --x * x * x * x; diff --git a/src/main/easeOutQuint.ts b/src/main/easeOutQuint.ts new file mode 100644 index 0000000..0a20e1a --- /dev/null +++ b/src/main/easeOutQuint.ts @@ -0,0 +1,6 @@ +import { Mapper } from './types'; + +/** + * @group Easing + */ +export const easeOutQuint: Mapper = x => 1 + --x * x * x * x * x; diff --git a/src/main/easing.ts b/src/main/easing.ts deleted file mode 100644 index bfb7557..0000000 --- a/src/main/easing.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Mapper } from './types'; -import { exp, log } from './utils'; - -/** - * Maps `x` ∈ [0, 1] exponentially to [0, 1]. - * - * @param q Greater values produce more bent curve, `f(x, 0) = x`. - * @group Easing - */ -export function easeExp(q = 1): Mapper { - return x => (q === 0 ? x : (exp(q * x) - 1) / (exp(q) - 1)); -} - -/** - * Maps `x` ∈ [0, 1] logarithmically to [0, 1]. - * - * @param q Greater values produce more bent curve, `f(x, 0) = x`. - * @group Easing - */ -export function easeLog(q = 1): Mapper { - return x => (q === 0 ? x : log(x * (exp(q) - 1) + 1) / q); -} - -export const easeLinear: Mapper = x => x; - -/** - * @group Easing - */ -export const easeInQuad: Mapper = x => x * x; - -/** - * @group Easing - */ -export const easeOutQuad: Mapper = x => x * (2 - x); - -/** - * @group Easing - */ -export const easeInOutQuad: Mapper = x => (x < 0.5 ? 2 * x * x : -1 + (4 - 2 * x) * x); - -/** - * @group Easing - */ -export const easeInCubic: Mapper = x => x * x * x; - -/** - * @group Easing - */ -export const easeOutCubic: Mapper = x => --x * x * x + 1; - -/** - * @group Easing - */ -export const easeInOutCubic: Mapper = x => (x < 0.5 ? 4 * x * x * x : (x - 1) * (2 * x - 2) * (2 * x - 2) + 1); - -/** - * @group Easing - */ -export const easeInQuart: Mapper = x => x * x * x * x; - -/** - * @group Easing - */ -export const easeOutQuart: Mapper = x => 1 - --x * x * x * x; - -/** - * @group Easing - */ -export const easeInOutQuart: Mapper = x => (x < 0.5 ? 8 * x * x * x * x : 1 - 8 * --x * x * x * x); - -/** - * @group Easing - */ -export const easeInQuint: Mapper = x => x * x * x * x * x; - -/** - * @group Easing - */ -export const easeOutQuint: Mapper = x => 1 + --x * x * x * x * x; - -/** - * @group Easing - */ -export const easeInOutQuint: Mapper = x => (x < 0.5 ? 16 * x * x * x * x * x : 1 + 16 * --x * x * x * x * x); diff --git a/src/main/gauss.ts b/src/main/gauss.ts index 5db0051..4e81fec 100644 --- a/src/main/gauss.ts +++ b/src/main/gauss.ts @@ -2,16 +2,22 @@ import { Mapper } from './types'; import { exp, PI, sqrt } from './utils'; /** - * Gaussian distribution. + * Gaussian (normal) distribution. * - * @param m Mean. - * @param d Standard deviation. + * ```ts + * seq(3).map(gauss(0.5, 0.3)) + * // ⮕ [0.33, 1.32, 0.33] + * ``` + * + * @param mean The mean value. + * @param deviation Standard deviation. + * @group Distributions */ -export function gauss(m = 0, d = 1): Mapper { - const q = d * sqrt(2 * PI); +export function gauss(mean = 0, deviation = 1): Mapper { + const q = deviation * sqrt(2 * PI); return x => { - x = (x - m) / d; + x = (x - mean) / deviation; return exp(-0.5 * x * x) / q; }; } diff --git a/src/main/gaussDist.ts b/src/main/gaussDist.ts deleted file mode 100644 index 46c3e79..0000000 --- a/src/main/gaussDist.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { mapRange } from './mapRange'; -import { cdfInv } from './cdfInv'; - -/** - * Distributes `n` numbers normally from `min` to `max`. - * - * @param n The number of numbers to distribute. - * @param a The minimum value. - * @param b The maximum value. - */ -export function gaussDist(n: number, a: number, b: number): number[] { - const arr = []; - const q = n + 1; - - const gMin = cdfInv(1 / q); - const gMax = cdfInv(n / q); - - const v = mapRange(gMin, gMax, a, b); - - for (let i = 1; i < q; i++) { - arr.push(v(cdfInv(i / q))); - } - return arr; -} diff --git a/src/main/hypot.ts b/src/main/hypot.ts new file mode 100644 index 0000000..86160b6 --- /dev/null +++ b/src/main/hypot.ts @@ -0,0 +1,8 @@ +import { sqrt } from './utils'; + +/** + * Returns the square root of the sum of squares of its arguments. + * + * @group Math + */ +export const hypot: (x: number, y: number) => number = Math.hypot || ((x, y) => sqrt(x * x + y + y)); diff --git a/src/main/index.ts b/src/main/index.ts index 5e9da79..baf9791 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,11 +1,46 @@ -export * from './binarySearch'; -export * from './bitwise'; -export * from './copyOver'; -export * from './cspline'; -export * from './csplineMonot'; -export * from './easing'; -export * from './lerp'; -export * from './math'; -export * from './qsort'; -export * from './range'; export * from './types'; + +export { and } from './and'; +export { binarySearch } from './binarySearch'; +export { binarySearchComp } from './binarySearchComp'; +export { cdfGauss } from './cdfGauss'; +export { cdfGaussInv } from './cdfGaussInv'; +export { clamp } from './clamp'; +export { closest } from './closest'; +export { compose } from './compose'; +export { copyOver } from './copyOver'; +export { cspline, interpolateCSpline, populateCSplines } from './cspline'; +export { csplineMonot, interpolateCSplineMonot, populateCSplinesMonot } from './csplineMonot'; +export { cycle } from './cycle'; +export { deg } from './deg'; +export { easeExp } from './easeExp'; +export { easeInCubic } from './easeInCubic'; +export { easeInOutCubic } from './easeInOutCubic'; +export { easeInOutQuad } from './easeInOutQuad'; +export { easeInOutQuart } from './easeInOutQuart'; +export { easeInOutQuint } from './easeInOutQuint'; +export { easeInQuad } from './easeInQuad'; +export { easeInQuart } from './easeInQuart'; +export { easeInQuint } from './easeInQuint'; +export { easeLog } from './easeLog'; +export { easeOutCubic } from './easeOutCubic'; +export { easeOutQuad } from './easeOutQuad'; +export { easeOutQuart } from './easeOutQuart'; +export { easeOutQuint } from './easeOutQuint'; +export { gauss } from './gauss'; +export { hypot } from './hypot'; +export { left } from './left'; +export { lerp } from './lerp'; +export { logx } from './logx'; +export { nan } from './nan'; +export { or } from './or'; +export { qsort } from './qsort'; +export { rad } from './rad'; +export { right } from './right'; +export { scale } from './scale'; +export { seq } from './seq'; +export { sign } from './sign'; +export { snap } from './snap'; +export { sq } from './sq'; +export { trunc } from './trunc'; +export { xor } from './xor'; diff --git a/src/main/left.ts b/src/main/left.ts new file mode 100644 index 0000000..07e3f3c --- /dev/null +++ b/src/main/left.ts @@ -0,0 +1,16 @@ +import { pow } from './utils'; + +/** + * Bitwise left shift operator for large unsigned integers. + * + * ```ts + * left(0xab, 8); // → 0xab_00 + * // or + * 0xab << 8; + * ``` + * + * @group Bitwise Operations + */ +export function left(x: number, shift: number): number { + return x * pow(2, shift | 0) || 0; +} diff --git a/src/main/lerp.ts b/src/main/lerp.ts index c83a373..b20065d 100644 --- a/src/main/lerp.ts +++ b/src/main/lerp.ts @@ -1,6 +1,7 @@ -import { Interpolator } from './types'; +import { Mapper } from './types'; import { binarySearch } from './binarySearch'; import { min } from './utils'; +import { nan } from './nan'; /** * Returns a linear interpolation function for given pivot points. @@ -9,8 +10,8 @@ import { min } from './utils'; * interpolation. * * ```ts - * const f = lerp(xs, ys); - * const y = f(x); + * const fn = lerp(xs, ys); + * const y = fn(x); * ``` * * @param xs The array of X coordinates of pivot points in ascending order. @@ -18,39 +19,36 @@ import { min } from './utils'; * @returns The function that takes X coordinate and returns an interpolated Y coordinate. * @group Interpolation */ -export function lerp(xs: ArrayLike, ys: ArrayLike): Interpolator { - let n = 0; - - const fn: Interpolator = x => { - if (n === 0) { - return NaN; - } - if (x <= xs[0]) { - return ys[0]; +export function lerp(xs: ArrayLike, ys: ArrayLike): Mapper { + const n = min(xs.length, ys.length); + const x0 = xs[0]; + const y0 = ys[0]; + const xn = xs[n - 1]; + const yn = ys[n - 1]; + + if (n === 0) { + return nan; + } + + return x => { + let i, xj, yj; + + if (x <= x0) { + return y0; } - if (x >= xs[n - 1]) { - return ys[n - 1]; + if (x >= xn) { + return yn; } - let i = binarySearch(xs, x, n); + i = binarySearch(xs, x, n); if (i >= 0) { return ys[i]; } i = ~i; - const xj = xs[i - 1]; - const yj = ys[i - 1]; + xj = xs[i - 1]; + yj = ys[i - 1]; return yj + ((x - xj) / (xs[i] - xj)) * (ys[i] - yj); }; - - fn.update = (nextXs, nextYs) => { - n = min(nextXs.length, nextYs.length); - xs = nextXs; - ys = nextYs; - }; - - fn.update(xs, ys); - - return fn; } diff --git a/src/main/logx.ts b/src/main/logx.ts new file mode 100644 index 0000000..9eef546 --- /dev/null +++ b/src/main/logx.ts @@ -0,0 +1,26 @@ +import { log10 } from './utils'; + +/** + * Returns the logarithm of `x` with base `base`. + * + * ```ts + * logx(64, 2); + * // ⮕ 6 + * + * logx(1000, 10); + * // ⮕ 3 + * + * logx(99, 10); + * // ⮕ 1.99563519459755 + * + * logx(0.01, 10); + * // ⮕ -2 + * ``` + * + * @param x The number. + * @param base The base. + * @group Math + */ +export function logx(x: number, base: number): number { + return log10(x) / log10(base); +} diff --git a/src/main/mapRange.ts b/src/main/mapRange.ts deleted file mode 100644 index 6c52d64..0000000 --- a/src/main/mapRange.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Mapper } from './types'; - -export function mapRange(a1: number, b1: number, a2 = 0, b2 = 1): Mapper { - return x => a2 + ((b2 - a2) * (x - a1)) / (b1 - a1); -} diff --git a/src/main/math.ts b/src/main/math.ts deleted file mode 100644 index a363383..0000000 --- a/src/main/math.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { abs, ceil, floor, log10, PI, sqrt } from './utils'; - -/** - * Returns the integer part of a number by removing any fractional digits. - */ -export const trunc = Math.trunc || (x => (x < 0 ? ceil(x) : floor(x))); - -/** - * Returns either a positive or negative +/- 1, indicating the sign of a number passed into the argument. If the number - * passed into `sign()` is 0, it will return a +/- 0. Note that if the number is positive, an explicit (+) will not be - * returned. - */ -export const sign = Math.sign || (x => abs(x) / x); - -/** - * Returns the square root of the sum of squares of its arguments. - */ -export const hypot: (x: number, y: number) => number = Math.hypot || ((x, y) => sqrt(x * x + y + y)); - -/** - * Returns the logarithm of `x` with base `n`. - * - * ```ts - * logx(64, 2) // → 6 - * logx(1000, 10) // → 3 - * logx(99, 10) // → 1.99563519459755 - * logx(0.01, 10) // → -2 - * ``` - */ -export function logx(x: number, n: number): number { - return log10(abs(x)) / log10(n); -} - -/** - * Clamps `x` to range [`a`, `b`]. - * - * Range can be inverse. - * - * @see {@link clamp01} - */ -export function clamp(x: number, a: number, b: number): number { - if (a > b) { - const c = b; - b = a; - a = c; - } - x = +x; - return x < a ? a : x > b ? b : x; -} - -/** - * Clamps `x` to range [0, 1]. - */ -export function clamp1(x: number): number { - x = +x; - return x < 0 ? 0 : x > 1 ? 1 : x; -} - -/** - * Flips `x` ∈ [`a1`, `b1`] to [`a2`, `b2`]. - * - * Ranges can be inverse. - */ -export function flip(x: number, a1: number, b1: number, a2: number, b2: number): number { - return +a2 + ((b2 - a2) * (x - a1)) / (b1 - a1); -} - -/** - * Brings `x` to the range [`a`, `b`] by adding or subtracting the range size |`a` - `b`|. - * - * Range can be inverse. - * - * ```ts - * cycle(12, 0, 10) // → 2 - * cycle(-333, 100, 33) // → 69 - * ``` - * - * @see {@link snap} - */ -export function cycle(x: number, a: number, b: number): number { - if (a > b) { - const c = b; - b = a; - a = c; - } - x = +x; - const d = b - a; - return x > a ? x - ceil((x - b) / d) * d : x + ceil((a - x) / d) * d; -} - -/** - * Converts radians to degrees. - * - * @see {@link deg} - */ -export function rad(x: number): number { - return (x * PI) / 180; -} - -/** - * Converts degrees to radians. - * - * @see {@rad} - */ -export function deg(x: number): number { - return (x / PI) * 180; -} - -/** - * Semantic shortcut for `n` \*\* 2. - */ -export function sq(n: number): number { - return n * n; -} - -/** - * Rounds `x` to the closest value that is divided by `n` without any remainder. - * - * ```ts - * snap(17, 10) // → 20 - * snap(-10, 3) // → -9 - * ``` - * - * @see {@link cycle} - */ -export function snap(x: number, n: number): number { - const r = x % n; - return abs(r) < n / 2 ? x - r : x - r + n * sign(x); -} - -/** - * Returns the closest value to `x` from `arr`. - * - * If `arr` is empty then `x` is returned as is. - * - * ```ts - * closest(1.8, [0, 3, 6]) // → 3 - * ``` - * - * @see {@link isEpsClose} - */ -export function closest(x: number, xs: ArrayLike): number { - x = +x; - - let t = x; - let dx = 1 / 0; - - const n = xs.length; - - for (let i = 0; i < n; ++i) { - const xi = xs[i]; - const dxi = abs(xi - x); - - if (dxi < dx) { - t = xi; - dx = dxi; - } - } - return t; -} - -/** - * Returns `true` if `x` is in the `eps` neighbourhood of `a`. - * - * @see {@link closest} - */ -export function isEpsClose(x: number, a: number, eps = 0.01): boolean { - return abs(a - x) < eps; -} - -/** - * Returns `true` if `x` is in range [`a`, `b`]. - * - * Range can be inverse. - */ -export function isBetween(x: number, a: number, b: number): boolean { - if (a > b) { - const c = b; - b = a; - a = c; - } - return a <= x && x <= b; -} - -/** - * Returns `true` if `x` is not `null` and can be cast to a non-NaN number. - * - * ```ts - * isNumeric(1); // → true - * isNumeric('1'); // → true - * isNumeric('1a'); // → false - * isNumeric(null); // → false - * ``` - */ -export function isNumeric(x: T): x is NonNullable { - return x != null && !isNaN(+x); -} diff --git a/src/main/nan.ts b/src/main/nan.ts new file mode 100644 index 0000000..7b4a1cd --- /dev/null +++ b/src/main/nan.ts @@ -0,0 +1,8 @@ +/** + * Always returns `NaN`. + * + * @group Utils + */ +export function nan() { + return NaN; +} diff --git a/src/main/or.ts b/src/main/or.ts new file mode 100644 index 0000000..171b5e3 --- /dev/null +++ b/src/main/or.ts @@ -0,0 +1,12 @@ +import { MASK_UPPER, MASK_LOWER } from './utils'; + +/** + * Bitwise OR operator for large unsigned integers. + * + * @param x The left integer. + * @param y The right integer. + * @group Bitwise Operations + */ +export function or(x: number, y: number): number { + return ((x / MASK_UPPER) | (y / MASK_UPPER)) * MASK_UPPER + ((x & MASK_LOWER) | (y & MASK_LOWER)); +} diff --git a/src/main/qsort.ts b/src/main/qsort.ts index 75bc449..8a93e5c 100644 --- a/src/main/qsort.ts +++ b/src/main/qsort.ts @@ -12,7 +12,8 @@ let globalStack: number[] | null = []; * @param swap The callback that is invoked with indices that were swapped. * @param comparator The callback that defines the sort order. If omitted, the array elements are compared using * comparison operators. - * @returns The `arr` array. + * @returns The input array. + * @template T The input array. * @group Sort */ export function qsort>( @@ -24,8 +25,8 @@ export function qsort>( const n = arr.length; - const isComparing = typeof comparator === 'function'; - const isSwapping = typeof swap === 'function'; + const compares = comparator != null; + const swaps = swap != null; if (n < 2) { return arr; @@ -35,11 +36,11 @@ export function qsort>( ax = arr[0]; ay = arr[1]; - if (isComparing ? comparator(ax, ay) > 0 : ax > ay) { + if (compares ? comparator(ax, ay) > 0 : ax > ay) { arr[0] = ay; arr[1] = ax; - if (isSwapping) { + if (swaps) { swap(0, 1); } } @@ -77,7 +78,7 @@ export function qsort>( pristine = true; while (true) { - if (isComparing) { + if (compares) { while (x <= y && !(comparator(ax, pivotValue) >= 0)) { ax = arr[++x]; } @@ -102,7 +103,7 @@ export function qsort>( arr[l] = ar; arr[r] = pivotValue; - if (isSwapping) { + if (swaps) { swap(l, r); } } @@ -111,7 +112,7 @@ export function qsort>( ax = arr[x] = ay; ay = arr[y] = t; - if (isSwapping) { + if (swaps) { swap(x, y); } } @@ -124,7 +125,7 @@ export function qsort>( arr[l] = ar; arr[r] = pivotValue; - if (isSwapping) { + if (swaps) { swap(l, r); } } @@ -133,7 +134,7 @@ export function qsort>( arr[r] = ax; arr[x] = pivotValue; - if (isSwapping) { + if (swaps) { swap(x, r); } } diff --git a/src/main/rad.ts b/src/main/rad.ts new file mode 100644 index 0000000..7a5c5c3 --- /dev/null +++ b/src/main/rad.ts @@ -0,0 +1,9 @@ +import { Mapper } from './types'; +import { PI } from './utils'; + +/** + * Converts radians to degrees. + * + * @group Math + */ +export const rad: Mapper = x => (x * PI) / 180; diff --git a/src/main/range.ts b/src/main/range.ts deleted file mode 100644 index 70fac8b..0000000 --- a/src/main/range.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Mapper } from './types'; - -/** - * Creates an array of length `n` and fills it with numbers in range [`a`, `b`]. - * - * @param n The array length. - * @param a The minimum value. - * @param b The maximum value. - * @param easing The easing function that receives `x` ∈ [0, 1] and return a mapped value. - * @returns The array of numbers. - */ -export function range(n: number, a = 0, b = 1, easing?: Mapper): number[] { - const arr: number[] = []; - - if (n === 1) { - arr[0] = a; - return arr; - } - - if (typeof easing === 'function') { - for (let j = 0; j < n; ++j) { - arr[j] = a + easing(j / (n - 1)) * (b - a); - } - } else { - for (let j = 0; j < n; ++j) { - arr[j] = a + (j / (n - 1)) * (b - a); - } - } - - return arr; -} diff --git a/src/main/right.ts b/src/main/right.ts new file mode 100644 index 0000000..f2e1369 --- /dev/null +++ b/src/main/right.ts @@ -0,0 +1,17 @@ +import { pow } from './utils'; +import { trunc } from './trunc'; + +/** + * Bitwise right shift operator for large unsigned integers. + * + * ```ts + * right(0xab_cd, 8); // → 0xab + * // or + * 0xab_cd >> 8; + * ``` + * + * @group Bitwise Operations + */ +export function right(x: number, shift: number): number { + return trunc(x / pow(2, shift | 0)) || 0; +} diff --git a/src/main/scale.ts b/src/main/scale.ts new file mode 100644 index 0000000..db67b45 --- /dev/null +++ b/src/main/scale.ts @@ -0,0 +1,23 @@ +import { Mapper } from './types'; + +/** + * Scales value from [`a`, `b`] range to [`nextA`, `nextB`] range. + * + * ```ts + * scale(0, 1, 50, 100)(75); + * // ⮕ 0.5 + * + * scale(50, 100)(0.5); + * // ⮕ 75 + * ``` + * + * @param a The minimum value of output range. + * @param b The maximum value of output range. + * @param inputA The minimum value of input range. + * @param inputB The maximum value of input range. + * @returns Mapper that handles the input value scaling. + * @group Distributions + */ +export function scale(a: number, b: number, inputA = 0, inputB = 1): Mapper { + return x => a + ((b - a) * (x - inputA)) / (inputB - inputA); +} diff --git a/src/main/seq.ts b/src/main/seq.ts new file mode 100644 index 0000000..ff9bad1 --- /dev/null +++ b/src/main/seq.ts @@ -0,0 +1,29 @@ +/** + * Creates an array of length `n` and fills it with evenly distributed numbers in range [`a`, `b`]. + * + * ```ts + * seq(3); + * // ⮕ [0, 0.5, 1] + * + * seq(4, -10, 2); + * // ⮕ [-10, -6, -2, 2] + * ``` + * + * @param n The array length. + * @param a The minimum sequence value, + * @param b The maximum sequence value, + * @returns The array of numbers. + * @group Distributions + */ +export function seq(n: number, a = 0, b = 1): number[] { + if (n === 1) { + return [a]; + } + + const arr = []; + + for (let i = 0; i < n; ++i) { + arr.push(a + (b - a) * (i / (n - 1))); + } + return arr; +} diff --git a/src/main/sign.ts b/src/main/sign.ts new file mode 100644 index 0000000..c208f11 --- /dev/null +++ b/src/main/sign.ts @@ -0,0 +1,11 @@ +import { Mapper } from './types'; +import { abs } from './utils'; + +/** + * Returns either a positive or negative +/- 1, indicating the sign of a number passed into the argument. If the number + * passed into `sign()` is 0, it will return a +/- 0. Note that if the number is positive, an explicit (+) will not be + * returned. + * + * @group Math + */ +export const sign: Mapper = Math.sign || (x => abs(x) / x); diff --git a/src/main/snap.ts b/src/main/snap.ts new file mode 100644 index 0000000..155bffa --- /dev/null +++ b/src/main/snap.ts @@ -0,0 +1,23 @@ +import { Mapper } from './types'; +import { abs } from './utils'; +import { sign } from './sign'; + +/** + * Rounds `x` to the closest value that is divided by `n` without any remainder. + * + * ```ts + * snap(10)(17); + * // ⮕ 20 + * + * snap(3)(-10); + * // ⮕ -9 + * ``` + * + * @group Math + */ +export function snap(n: number): Mapper { + return x => { + const d = x % n; + return abs(d) < n / 2 ? x - d : x - d + n * sign(x); + }; +} diff --git a/src/main/sq.ts b/src/main/sq.ts new file mode 100644 index 0000000..022cf15 --- /dev/null +++ b/src/main/sq.ts @@ -0,0 +1,8 @@ +import { Mapper } from './types'; + +/** + * Semantic shortcut for `n` \*\* 2. + * + * @group Math + */ +export const sq: Mapper = x => x * x; diff --git a/src/main/trunc.ts b/src/main/trunc.ts new file mode 100644 index 0000000..85f5587 --- /dev/null +++ b/src/main/trunc.ts @@ -0,0 +1,9 @@ +import { Mapper } from './types'; +import { ceil, floor } from './utils'; + +/** + * Returns the integer part of a number by removing any fractional digits. + * + * @group Math + */ +export const trunc: Mapper = Math.trunc || (x => (x < 0 ? ceil(x) : floor(x))); diff --git a/src/main/types.ts b/src/main/types.ts index efbe9a5..88539f8 100644 --- a/src/main/types.ts +++ b/src/main/types.ts @@ -1,25 +1,31 @@ /** * A function that maps a number to another number. + * + * @param x The input value. + * @returns The output value. + * @template I The input value. + * @template O The output value. + * @group Utils */ -export type Mapper = (x: number) => number; +export type Mapper = (x: I) => O; /** * Compares `a` and `b` and returns: * - A negative number if `a` < `b`; * - A positive number if `a` > `b`; * - 0 if `a` is equal to `b`. + * + * @template T Compared values. + * @group Utils */ export type Comparator = (a: T, b: T) => number; /** - * An interpolator function. + * An array like object with mutable elements. * - * @group Interpolation + * @template T The array element. + * @group Utils */ -export interface Interpolator extends Mapper { - update(xs: ArrayLike, ys: ArrayLike): void; -} - export interface MutableArrayLike { [index: number]: T; @@ -27,6 +33,6 @@ export interface MutableArrayLike { } /** - * Infers an array value type. + * Infers the value stored in an array. */ -export type ArrayValue> = T extends ArrayLike ? E : never; +export type ArrayValue> = T extends ArrayLike ? V : never; diff --git a/src/main/utils.ts b/src/main/utils.ts index 4085a62..0916f74 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -1,4 +1,4 @@ export const { ceil, floor, abs, sqrt, log10, PI, exp, log, min, pow } = Math; -export const HI = 0x80_00_00_00; -export const LO = 0x7f_ff_ff_ff; +export const MASK_UPPER = 0x80_00_00_00; +export const MASK_LOWER = 0x7f_ff_ff_ff; diff --git a/src/main/xor.ts b/src/main/xor.ts new file mode 100644 index 0000000..e715d28 --- /dev/null +++ b/src/main/xor.ts @@ -0,0 +1,10 @@ +import { MASK_UPPER, MASK_LOWER } from './utils'; + +/** + * Bitwise XOR operator for large unsigned integers. + * + * @group Bitwise Operations + */ +export function xor(a: number, b: number): number { + return ((a / MASK_UPPER) ^ (b / MASK_UPPER)) * MASK_UPPER + ((a & MASK_LOWER) ^ (b & MASK_LOWER)); +} diff --git a/src/test/and.test.ts b/src/test/and.test.ts new file mode 100644 index 0000000..dca1c3f --- /dev/null +++ b/src/test/and.test.ts @@ -0,0 +1,10 @@ +import { and } from '../main'; + +test('applies AND operator', () => { + expect(and(0xab_cd_ef_ab_cd, 0x11_22_33_44_55)).toBe(0x1_00_23_00_45); +}); + +test('handles NaN as native operator', () => { + expect(and(NaN, 0xab_cd)).toBe(0); + expect(and(0xab_cd, NaN)).toBe(0); +}); diff --git a/src/test/binarySearch.test.ts b/src/test/binarySearch.test.ts index 6d84279..c9173aa 100644 --- a/src/test/binarySearch.test.ts +++ b/src/test/binarySearch.test.ts @@ -1,9 +1,7 @@ import { binarySearch } from '../main'; -describe('binarySearch', () => { - test('searches for a value in an array', () => { - expect(binarySearch([], 20)).toBe(-1); - expect(binarySearch([10, 20, 30, 40], 20)).toBe(1); - expect(binarySearch([10, 20, 30, 40], 25)).toBe(-3); - }); +test('searches for a value in an array', () => { + expect(binarySearch([], 20)).toBe(-1); + expect(binarySearch([10, 20, 30, 40], 20)).toBe(1); + expect(binarySearch([10, 20, 30, 40], 25)).toBe(-3); }); diff --git a/src/test/bitwise.test.ts b/src/test/bitwise.test.ts deleted file mode 100644 index cb3cead..0000000 --- a/src/test/bitwise.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import {and, left, or, right, xor} from '../main'; - -describe('left', () => { - - test('shifts left', () => { - expect(left(0xab_cd_ef_ab_cd, 24)).toBe(0xab_cd_ef_ab_cd_00_00_00); - }); - - test('handles NaN as native operator', () => { - expect(left(NaN, 8)).toBe(0); - expect(left(0xab_cd, NaN)).toBe(0xab_cd); - }); -}); - -describe('right', () => { - - test('shifts right', () => { - expect(right(0xab_cd_ef_ab_cd_ef_ab, 24)).toBe(0xab_cd_ef_ab); - }); - - test('handles NaN as native operator', () => { - expect(right(NaN, 8)).toBe(0); - expect(right(0xab_cd, NaN)).toBe(0xab_cd); - }); -}); - -describe('xor', () => { - - test('applies XOR operator', () => { - expect(xor(0x10_00_00_00_00, 0x10_10_10_10_10)).toBe(0x10_10_10_10); - }); - - test('handles NaN as native operator', () => { - expect(xor(NaN, 0xab_cd)).toBe(0xab_cd); - expect(xor(0xab_cd, NaN)).toBe(0xab_cd); - }); -}); - -describe('or', () => { - - test('applies OR operator', () => { - expect(or(0xab_cd_ef_ab_cd, 0x11_22_33_44_55)).toBe(0xbb_ef_ff_ef_dd); - }); - - test('handles NaN as native operator', () => { - expect(or(NaN, 0xab_cd)).toBe(0xab_cd); - expect(or(0xab_cd, NaN)).toBe(0xab_cd); - }); -}); - -describe('and', () => { - - test('applies AND operator', () => { - expect(and(0xab_cd_ef_ab_cd, 0x11_22_33_44_55)).toBe(0x1_00_23_00_45); - }); - - test('handles NaN as native operator', () => { - expect(and(NaN, 0xab_cd)).toBe(0); - expect(and(0xab_cd, NaN)).toBe(0); - }); -}); diff --git a/src/test/cdf.test.ts b/src/test/cdf.test.ts deleted file mode 100644 index f04ccc0..0000000 --- a/src/test/cdf.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { cdf } from '../main/cdf'; - -describe('cdf', () => { - test('', () => { - expect(cdf(0, 1)(2)).toBe(0.9772); - expect(cdf(0, 1)(0)).toBe(0.5); - expect(cdf(4, 2)(-1)).toBe(0.006199999999999983); - - expect(cdf(NaN, 0)(0)).toBe(NaN); - expect(cdf(0, NaN)(0)).toBe(NaN); - expect(cdf(0, 1)(NaN)).toBe(NaN); - }); -}); diff --git a/src/test/cdfGauss.test.ts b/src/test/cdfGauss.test.ts new file mode 100644 index 0000000..c5d72dd --- /dev/null +++ b/src/test/cdfGauss.test.ts @@ -0,0 +1,11 @@ +import { cdfGauss } from '../main'; + +test('returns CDF for normal distribution', () => { + expect(cdfGauss(0, 1)(2)).toBe(0.9772); + expect(cdfGauss(0, 1)(0)).toBe(0.5); + expect(cdfGauss(4, 2)(-1)).toBe(0.006199999999999983); + + expect(cdfGauss(NaN, 0)(0)).toBe(NaN); + expect(cdfGauss(0, NaN)(0)).toBe(NaN); + expect(cdfGauss(0, 1)(NaN)).toBe(NaN); +}); diff --git a/src/test/clamp.test.ts b/src/test/clamp.test.ts new file mode 100644 index 0000000..dcdbcd6 --- /dev/null +++ b/src/test/clamp.test.ts @@ -0,0 +1,13 @@ +import { clamp } from '../main'; + +test('returns minimum', () => { + expect(clamp(2, 4)(-1)).toBe(2); +}); + +test('returns maximum', () => { + expect(clamp(2, 4)(100)).toBe(4); +}); + +test('returns value', () => { + expect(clamp(2, 4)(3)).toBe(3); +}); diff --git a/src/test/closest.test.ts b/src/test/closest.test.ts new file mode 100644 index 0000000..4698096 --- /dev/null +++ b/src/test/closest.test.ts @@ -0,0 +1,9 @@ +import { closest } from '../main'; + +test('finds closest inside the range', () => { + expect(closest([3, 0])(1.8)).toBe(3); +}); + +test('finds closest outside of the range', () => { + expect(closest([1, 3, 0])(-100)).toBe(0); +}); diff --git a/src/test/copyOver.test.ts b/src/test/copyOver.test.ts index ecdbd5c..6f794e3 100644 --- a/src/test/copyOver.test.ts +++ b/src/test/copyOver.test.ts @@ -1,19 +1,13 @@ import { copyOver } from '../main'; -describe('copyOver', () => { - it('copies values', () => { - expect(copyOver([1, 2, 3], ['a', 'b', 'c', 'd'])).toEqual([1, 2, 3, 'd']); - }); - - it('copies values to target index', () => { - expect(copyOver([1, 2, 3], ['a', 'b', 'c', 'd'], 0, 2)).toEqual(['a', 'b', 1, 2, 3]); - }); +test('copies values', () => { + expect(copyOver([1, 2, 3], ['a', 'b', 'c', 'd'])).toEqual([1, 2, 3, 'd']); +}); - it('copies values to target index with length', () => { - expect(copyOver([1, 2, 3], ['a', 'b', 'c', 'd'], 0, 2, 2)).toEqual(['a', 'b', 1, 2]); - }); +test('copies values to target index', () => { + expect(copyOver([1, 2, 3], ['a', 'b', 'c', 'd'], 0, 2)).toEqual(['a', 'b', 1, 2, 3]); +}); - it('copies values to target index with length and source index', () => { - expect(copyOver([1, 2, 3], ['a', 'b', 'c', 'd'], 2, 2, 2)).toEqual(['a', 'b', 3, 'd']); - }); +test('copies values to target index with length', () => { + expect(copyOver([1, 2, 3], ['a', 'b', 'c', 'd'], 0, 2, 2)).toEqual(['a', 'b', 1, 2]); }); diff --git a/src/test/cspline.test.ts b/src/test/cspline.test.ts index aa4af15..2eee60b 100644 --- a/src/test/cspline.test.ts +++ b/src/test/cspline.test.ts @@ -1,13 +1,11 @@ import { cspline } from '../main'; -describe('cspline', () => { - it('creates a cubic spline interpolator', () => { - const f = cspline([0, 1, 2], [0, 1, 0]); +test('creates a cubic spline interpolator', () => { + const fn = cspline([0, 1, 2], [0, 1, 0]); - expect(f(0)).toBeCloseTo(0); - expect(f(0.5)).toBeCloseTo(0.6875); - expect(f(1)).toBeCloseTo(1); - expect(f(1.5)).toBeCloseTo(0.6875); - expect(f(2)).toBeCloseTo(0); - }); + expect(fn(0)).toBeCloseTo(0); + expect(fn(0.5)).toBeCloseTo(0.6875); + expect(fn(1)).toBeCloseTo(1); + expect(fn(1.5)).toBeCloseTo(0.6875); + expect(fn(2)).toBeCloseTo(0); }); diff --git a/src/test/csplineMonot.test.ts b/src/test/csplineMonot.test.ts index 67a56c0..2c0746e 100644 --- a/src/test/csplineMonot.test.ts +++ b/src/test/csplineMonot.test.ts @@ -1,14 +1,12 @@ import { csplineMonot } from '../main'; -describe('csplineMonot', () => { - it('creates a monotonous cubic spline interpolator', () => { - const f = csplineMonot([0, 1, 2], [0, 1, 0]); +test('creates a monotonous cubic spline interpolator', () => { + const fn = csplineMonot([0, 1, 2], [0, 1, 0]); - expect(f(-1)).toBeCloseTo(0); - expect(f(0)).toBeCloseTo(0); - expect(f(0.5)).toBeCloseTo(0.625); - expect(f(1)).toBeCloseTo(1); - expect(f(1.5)).toBeCloseTo(0.625); - expect(f(2)).toBeCloseTo(0); - }); + expect(fn(-1)).toBeCloseTo(0); + expect(fn(0)).toBeCloseTo(0); + expect(fn(0.5)).toBeCloseTo(0.625); + expect(fn(1)).toBeCloseTo(1); + expect(fn(1.5)).toBeCloseTo(0.625); + expect(fn(2)).toBeCloseTo(0); }); diff --git a/src/test/cycle.test.ts b/src/test/cycle.test.ts new file mode 100644 index 0000000..ec1047d --- /dev/null +++ b/src/test/cycle.test.ts @@ -0,0 +1,16 @@ +import { cycle } from '../main'; + +test('returns boundary values', () => { + expect(cycle(0, 10)(0)).toBe(0); + expect(cycle(0, 10)(10)).toBe(10); +}); + +test('brings number to range', () => { + expect(cycle(0, 10)(11)).toBe(1); + expect(cycle(0, 100)(150)).toBe(50); + expect(cycle()(123.5)).toBe(0.5); + expect(cycle(0, 100)(-50)).toBe(50); + expect(cycle(0, -1)(0.222)).toBe(-0.778); + expect(cycle(33, 100)(333)).toBe(65); + expect(cycle(33, 100)(-333)).toBe(69); +}); diff --git a/src/test/deg.test.ts b/src/test/deg.test.ts new file mode 100644 index 0000000..1491487 --- /dev/null +++ b/src/test/deg.test.ts @@ -0,0 +1,6 @@ +import { deg } from '../main'; + +test('converts radians to degrees', () => { + expect(deg(Math.PI)).toBe(180); + expect(deg(Math.PI / 2)).toBe(90); +}); diff --git a/src/test/left.test.ts b/src/test/left.test.ts new file mode 100644 index 0000000..848ae89 --- /dev/null +++ b/src/test/left.test.ts @@ -0,0 +1,10 @@ +import { left } from '../main'; + +test('shifts left', () => { + expect(left(0xab_cd_ef_ab_cd, 24)).toBe(0xab_cd_ef_ab_cd_00_00_00); +}); + +test('handles NaN as native operator', () => { + expect(left(NaN, 8)).toBe(0); + expect(left(0xab_cd, NaN)).toBe(0xab_cd); +}); diff --git a/src/test/lerp.test.ts b/src/test/lerp.test.ts index 22e4400..a970b7e 100644 --- a/src/test/lerp.test.ts +++ b/src/test/lerp.test.ts @@ -1,13 +1,11 @@ import { lerp } from '../main'; -describe('lerp', () => { - it('creates a linear interpolator', () => { - const f = lerp([0, 1, 2], [0, 1, 0]); +test('creates a linear interpolator', () => { + const fn = lerp([0, 1, 2], [0, 1, 0]); - expect(f(0)).toBe(0); - expect(f(0.5)).toBe(0.5); - expect(f(1)).toBe(1); - expect(f(1.5)).toBe(0.5); - expect(f(2)).toBe(0); - }); + expect(fn(0)).toBe(0); + expect(fn(0.5)).toBe(0.5); + expect(fn(1)).toBe(1); + expect(fn(1.5)).toBe(0.5); + expect(fn(2)).toBe(0); }); diff --git a/src/test/logx.test.ts b/src/test/logx.test.ts new file mode 100644 index 0000000..e2a1373 --- /dev/null +++ b/src/test/logx.test.ts @@ -0,0 +1,26 @@ +import { logx } from '../main'; + +test('returns Infinity', () => { + expect(logx(0, 10)).toBe(-Infinity); +}); + +test('returns log of fractional value', () => { + expect(logx(0.1, 10)).toBe(-1); + expect(logx(0.01, 10)).toBe(-2); + expect(logx(10 ** -6, 10)).toBe(-6); +}); + +test('returns log of positive integer value', () => { + expect(logx(1, 10)).toBe(0); + expect(logx(11, 10)).toBeCloseTo(1.04); + expect(logx(333, 10)).toBeCloseTo(2.52); + expect(logx(10 ** 6, 10)).toBe(6); +}); + +test('returns NaN for negative values', () => { + expect(logx(-1, 10)).toBe(NaN); +}); + +test('returns log of a very large number', () => { + expect(logx(562_949_953_421_311, 10)).toBeCloseTo(14.75); +}); diff --git a/src/test/math.test.ts b/src/test/math.test.ts deleted file mode 100644 index f0304b9..0000000 --- a/src/test/math.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -import {byte, clamp, clamp1, closest, cycle, deg, flip, isNumeric, logx, rad, snap, uint} from '../main'; - -describe('uint', () => { - - test('returns unsigned int', () => { - expect(uint(-123123123.123)).toBe(123123123); - expect(uint(123123123.123)).toBe(123123123); - expect(uint(NaN)).toBe(0); - }); -}); - -describe('byte', () => { - - test('returns unsigned byte', () => { - expect(byte(-105.666)).toBe(105); - expect(byte(105.666)).toBe(105); - expect(byte(-123123123.123)).toBe(0xff); - expect(byte(123123123.123)).toBe(0xff); - expect(byte(NaN)).toBe(0); - }); -}); - -describe('logx', () => { - - test('returns 0', () => { - expect(logx(0, 10)).toBe(0); - }); - - test('returns log of fractional value', () => { - expect(logx(.1, 10)).toBe(-1); - expect(logx(.01, 10)).toBe(-2); - expect(logx(10 ** -6, 10)).toBe(-6); - }); - - test('returns log of positive integer value', () => { - expect(logx(1, 10)).toBe(0); - expect(logx(11, 10)).toBeCloseTo(1.04); - expect(logx(333, 10)).toBeCloseTo(2.52); - expect(logx(10 ** 6, 10)).toBe(6); - }); - - test('returns log of negative integer value', () => { - expect(logx(-1, 10)).toBe(0); - expect(logx(-11, 10)).toBeCloseTo(1.04); - expect(logx(-333, 10)).toBeCloseTo(2.52); - expect(logx(-(10 ** 6), 10)).toBe(6); - }); - - test('returns log of a very large number', () => { - expect(logx(562_949_953_421_311, 10)).toBeCloseTo(14.75); - }); -}); - -describe('rad', () => { - - test('converts degrees to radians', () => { - expect(rad(1)).toBeCloseTo(0.017); - expect(rad(90)).toBe(Math.PI / 2); - }); -}); - -describe('deg', () => { - - test('converts radians to degrees', () => { - expect(deg(Math.PI)).toBe(180); - expect(deg(Math.PI / 2)).toBe(90); - }); -}); - -describe('clamp', () => { - - test('returns minimum', () => { - expect(clamp(-1, 2, 4)).toBe(2); - }); - - test('returns maximum', () => { - expect(clamp(100, 2, 4)).toBe(4); - }); - - test('returns value', () => { - expect(clamp(3, 2, 4)).toBe(3); - }); - - test('handles non-numeric values', () => { - expect(clamp('a' as any, 2, 4)).toBeNaN(); - expect(clamp('5' as any, 2, 4)).toBe(4); - }); -}); - -describe('clamp1', () => { - - test('returns minimum', () => { - expect(clamp1(-2)).toBe(0); - }); - - test('returns maximum', () => { - expect(clamp1(2)).toBe(1); - }); - - test('returns value', () => { - expect(clamp1(.2)).toBe(.2); - }); - - test('handles non-numeric values', () => { - expect(clamp1('a' as any)).toBeNaN(); - expect(clamp1('5' as any)).toBe(1); - }); -}); - -describe('cycle', () => { - - test('returns boundary values', () => { - expect(cycle(0, 0, 10)).toBe(0); - expect(cycle(0, 10, 0)).toBe(0); - expect(cycle(10, 0, 10)).toBe(10); - }); - - test('brings number to range', () => { - expect(cycle(11, 0, 10)).toBe(1); - expect(cycle(150, 0, 100)).toBe(50); - expect(cycle(123.5, 0, 1)).toBe(.5); - expect(cycle(-50, 0, 100)).toBe(50); - expect(cycle(.222, 0, -1)).toBe(-0.778); - expect(cycle(333, 33, 100)).toBe(65); - expect(cycle(-333, 33, 100)).toBe(69); - }); -}); - -describe('flip', () => { - - test('flips value from one range to another', () => { - expect(flip(2, 1, 3, 0, 100)).toBe(50); - }); - - test('flips value between inverse ranges', () => { - expect(flip(-75, -100, 0, 0, 100)).toBe(25); - }); - - test('flips value outside of initial range', () => { - expect(flip(4, 1, 2, -100, 100)).toBe(500); - }); - - test('handles non-numeric values', () => { - expect(flip('a' as any, 1, 3, 0, 100)).toBeNaN(); - expect(flip(1, 'a' as any, 3, 0, 100)).toBeNaN(); - expect(flip(2, 1, 'a' as any, 0, 100)).toBeNaN(); - expect(flip(2, 1, 3, 'a' as any, 100)).toBeNaN(); - expect(flip(2, 1, 3, 0, 'a' as any)).toBeNaN(); - expect(flip('2' as any, 1, 3, 0, 100)).toBe(50); - }); -}); - -describe('closest', () => { - - test('finds closest inside the range', () => { - expect(closest(1.8, [3, 0])).toBe(3); - }); - - test('finds closest outside of the range', () => { - expect(closest(-100, [1, 3, 0])).toBe(0); - }); -}); - -describe('snap', () => { - - test('rounds to a step', () => { - expect(snap(-10, 3)).toBe(-9); - expect(snap(-11, 3)).toBe(-12); - expect(snap(10, 3)).toBe(9); - expect(snap(11, 3)).toBe(12); - }); -}); - -describe('isNumeric', () => { - - test('returns true for number-like values', () => { - expect(isNumeric(1)).toBe(true); - expect(isNumeric('1')).toBe(true); - expect(isNumeric('1a')).toBe(false); - expect(isNumeric(null)).toBe(false); - }); -}); diff --git a/src/test/or.test.ts b/src/test/or.test.ts new file mode 100644 index 0000000..b0d6574 --- /dev/null +++ b/src/test/or.test.ts @@ -0,0 +1,10 @@ +import { or } from '../main'; + +test('applies OR operator', () => { + expect(or(0xab_cd_ef_ab_cd, 0x11_22_33_44_55)).toBe(0xbb_ef_ff_ef_dd); +}); + +test('handles NaN as native operator', () => { + expect(or(NaN, 0xab_cd)).toBe(0xab_cd); + expect(or(0xab_cd, NaN)).toBe(0xab_cd); +}); diff --git a/src/test/qsort.test.ts b/src/test/qsort.test.ts index 6d5b627..eeab7c6 100644 --- a/src/test/qsort.test.ts +++ b/src/test/qsort.test.ts @@ -1,208 +1,206 @@ import { qsort } from '../main'; -describe('qsort', () => { - test('returns the input array of 0', () => { - const arr: unknown[] = []; +test('returns the input array of 0', () => { + const arr: unknown[] = []; - expect(qsort(arr)).toBe(arr); - expect(arr).toEqual([]); - }); - - test('returns the input array of 1', () => { - const arr = [0]; - - expect(qsort(arr)).toBe(arr); - expect(arr).toEqual([0]); - }); + expect(qsort(arr)).toBe(arr); + expect(arr).toEqual([]); +}); - test('returns the input array of 2', () => { - const arr = [0, 0]; +test('returns the input array of 1', () => { + const arr = [0]; - expect(qsort(arr)).toBe(arr); - expect(arr).toEqual([0, 0]); - }); + expect(qsort(arr)).toBe(arr); + expect(arr).toEqual([0]); +}); - test('sorts an array of 2', () => { - const arr = [1, 0]; +test('returns the input array of 2', () => { + const arr = [0, 0]; - expect(qsort(arr)).toEqual([0, 1]); - }); + expect(qsort(arr)).toBe(arr); + expect(arr).toEqual([0, 0]); +}); - test('sorts an array of 3', () => { - const arr = [2, 0, 1]; +test('sorts an array of 2', () => { + const arr = [1, 0]; - expect(qsort(arr)).toEqual([0, 1, 2]); - }); + expect(qsort(arr)).toEqual([0, 1]); +}); - test('sorts an array of 4', () => { - const arr = [3, 4, 2, 1]; +test('sorts an array of 3', () => { + const arr = [2, 0, 1]; - expect(qsort(arr)).toEqual([1, 2, 3, 4]); - }); + expect(qsort(arr)).toEqual([0, 1, 2]); +}); - test('sorts the sorted array of 4', () => { - const arr = [0, 1, 2, 3]; +test('sorts an array of 4', () => { + const arr = [3, 4, 2, 1]; - expect(qsort(arr)).toEqual([0, 1, 2, 3]); - }); + expect(qsort(arr)).toEqual([1, 2, 3, 4]); +}); - test('sorts the sorted array of 4 with swap callback', () => { - const arr = [0, 1, 2, 3]; - const swapMock = jest.fn(); +test('sorts the sorted array of 4', () => { + const arr = [0, 1, 2, 3]; - expect(qsort(arr, swapMock)).toEqual([0, 1, 2, 3]); - expect(swapMock.mock.calls).toEqual([]); - }); + expect(qsort(arr)).toEqual([0, 1, 2, 3]); +}); - test('sorts an array of 2 with swap callback', () => { - const arr = [1, 0]; - const swapMock = jest.fn(); +test('sorts the sorted array of 4 with swap callback', () => { + const arr = [0, 1, 2, 3]; + const swapMock = jest.fn(); - expect(qsort(arr, swapMock)).toEqual([0, 1]); - expect(swapMock.mock.calls).toEqual([[0, 1]]); - }); + expect(qsort(arr, swapMock)).toEqual([0, 1, 2, 3]); + expect(swapMock.mock.calls).toEqual([]); +}); - test('sorts an array of 3 with swap callback', () => { - const arr = [2, 0, 1]; - const swapMock = jest.fn(); +test('sorts an array of 2 with swap callback', () => { + const arr = [1, 0]; + const swapMock = jest.fn(); - expect(qsort(arr, swapMock)).toEqual([0, 1, 2]); - expect(swapMock.mock.calls).toEqual([ - [0, 2], - [0, 1], - ]); - }); + expect(qsort(arr, swapMock)).toEqual([0, 1]); + expect(swapMock.mock.calls).toEqual([[0, 1]]); +}); - test('sorts an array of 4 with swap callback', () => { - const arr = [3, 4, 2, 1]; - const swapMock = jest.fn(); +test('sorts an array of 3 with swap callback', () => { + const arr = [2, 0, 1]; + const swapMock = jest.fn(); - expect(qsort(arr, swapMock)).toEqual([1, 2, 3, 4]); - expect(swapMock.mock.calls).toEqual([ - [0, 3], // → [1, 4, 2, 3] - [1, 2], // → [1, 2, 4, 3] - [2, 3], // → [1, 2, 3, 4] - ]); - }); + expect(qsort(arr, swapMock)).toEqual([0, 1, 2]); + expect(swapMock.mock.calls).toEqual([ + [0, 2], + [0, 1], + ]); +}); - test('sorts an array of 5 with a single swap', () => { - const arr = [2, 1, 1, 1, 0]; - const swapMock = jest.fn(); +test('sorts an array of 4 with swap callback', () => { + const arr = [3, 4, 2, 1]; + const swapMock = jest.fn(); - expect(qsort(arr, swapMock)).toEqual([0, 1, 1, 1, 2]); - expect(swapMock.mock.calls).toEqual([[0, 4]]); - }); + expect(qsort(arr, swapMock)).toEqual([1, 2, 3, 4]); + expect(swapMock.mock.calls).toEqual([ + [0, 3], // → [1, 4, 2, 3] + [1, 2], // → [1, 2, 4, 3] + [2, 3], // → [1, 2, 3, 4] + ]); +}); - test('sorts before the swap call', () => { - const arr = [3, 4, 2, 1]; - const swapMock = jest.fn(); +test('sorts an array of 5 with a single swap', () => { + const arr = [2, 1, 1, 1, 0]; + const swapMock = jest.fn(); - swapMock.mockImplementationOnce(() => undefined); - swapMock.mockImplementationOnce(() => { - throw new Error(); - }); + expect(qsort(arr, swapMock)).toEqual([0, 1, 1, 1, 2]); + expect(swapMock.mock.calls).toEqual([[0, 4]]); +}); - expect(() => qsort(arr, swapMock)).toThrow(); +test('sorts before the swap call', () => { + const arr = [3, 4, 2, 1]; + const swapMock = jest.fn(); - expect(arr).toEqual([1, 2, 4, 3]); - expect(swapMock.mock.calls).toEqual([ - [0, 3], - [1, 2], - ]); + swapMock.mockImplementationOnce(() => undefined); + swapMock.mockImplementationOnce(() => { + throw new Error(); }); - test('uses comparator to sort', () => { - const arr = [3, 4, 2, 1]; + expect(() => qsort(arr, swapMock)).toThrow(); - expect(qsort(arr, undefined, (a, b) => b - a)).toEqual([4, 3, 2, 1]); - }); + expect(arr).toEqual([1, 2, 4, 3]); + expect(swapMock.mock.calls).toEqual([ + [0, 3], + [1, 2], + ]); +}); - test('uses both swap callback and comparator to sort', () => { - const arr = [3, 4, 2, 1]; - - const swapMock = jest.fn(); - const comparatorMock = jest.fn((a, b) => a - b); - - expect(qsort(arr, swapMock, comparatorMock)).toEqual([1, 2, 3, 4]); - expect(swapMock.mock.calls).toEqual([ - [0, 3], // → [1, 4, 2, 3] - [1, 2], // → [1, 2, 4, 3] - [2, 3], // → [1, 2, 3, 4] - ]); - expect(comparatorMock.mock.calls).toEqual([ - [1, 3], - [4, 3], - [2, 3], - [2, 3], - [4, 3], - [2, 1], - ]); - }); +test('uses comparator to sort', () => { + const arr = [3, 4, 2, 1]; - test('does not swap before the initial comparator call', () => { - const arr = [3, 4, 2, 1]; - const comparatorMock = jest.fn(() => { - throw new Error(); - }); + expect(qsort(arr, undefined, (a, b) => b - a)).toEqual([4, 3, 2, 1]); +}); - expect(() => qsort(arr, undefined, comparatorMock)).toThrow(); +test('uses both swap callback and comparator to sort', () => { + const arr = [3, 4, 2, 1]; + + const swapMock = jest.fn(); + const comparatorMock = jest.fn((a, b) => a - b); + + expect(qsort(arr, swapMock, comparatorMock)).toEqual([1, 2, 3, 4]); + expect(swapMock.mock.calls).toEqual([ + [0, 3], // → [1, 4, 2, 3] + [1, 2], // → [1, 2, 4, 3] + [2, 3], // → [1, 2, 3, 4] + ]); + expect(comparatorMock.mock.calls).toEqual([ + [1, 3], + [4, 3], + [2, 3], + [2, 3], + [4, 3], + [2, 1], + ]); +}); - expect(arr).toEqual([3, 4, 2, 1]); // No swaps +test('does not swap before the initial comparator call', () => { + const arr = [3, 4, 2, 1]; + const comparatorMock = jest.fn(() => { + throw new Error(); }); - test('throw in comparator does not break sorting', () => { - const arr = [3, 4, 2, 1]; - const swapMock = jest.fn(); - const comparatorMock = jest.fn(); - - comparatorMock.mockImplementationOnce((a, b) => a - b); - comparatorMock.mockImplementationOnce((a, b) => a - b); - comparatorMock.mockImplementationOnce((a, b) => a - b); - comparatorMock.mockImplementationOnce(() => { - throw new Error(); - }); - - expect(() => qsort(arr, swapMock, comparatorMock)).toThrow(); - - expect(arr).toEqual([1, 2, 4, 3]); - expect(swapMock.mock.calls).toEqual([ - [0, 3], - [1, 2], - ]); - expect(comparatorMock.mock.calls).toEqual([ - [1, 3], - [4, 3], - [2, 3], - [2, 3], - ]); - }); + expect(() => qsort(arr, undefined, comparatorMock)).toThrow(); + + expect(arr).toEqual([3, 4, 2, 1]); // No swaps +}); - test('sorts typed arrays', () => { - const arr = Int32Array.of(3, 4, 2, 1); +test('throw in comparator does not break sorting', () => { + const arr = [3, 4, 2, 1]; + const swapMock = jest.fn(); + const comparatorMock = jest.fn(); + + comparatorMock.mockImplementationOnce((a, b) => a - b); + comparatorMock.mockImplementationOnce((a, b) => a - b); + comparatorMock.mockImplementationOnce((a, b) => a - b); + comparatorMock.mockImplementationOnce(() => { + throw new Error(); + }); + + expect(() => qsort(arr, swapMock, comparatorMock)).toThrow(); + + expect(arr).toEqual([1, 2, 4, 3]); + expect(swapMock.mock.calls).toEqual([ + [0, 3], + [1, 2], + ]); + expect(comparatorMock.mock.calls).toEqual([ + [1, 3], + [4, 3], + [2, 3], + [2, 3], + ]); +}); - // Ensure valid type is inferred - const arr2: Int32Array = qsort(arr, undefined, (a, b) => b - a); +test('sorts typed arrays', () => { + const arr = Int32Array.of(3, 4, 2, 1); - expect(arr2).toEqual(Int32Array.of(4, 3, 2, 1)); - }); + // Ensure valid type is inferred + const arr2: Int32Array = qsort(arr, undefined, (a, b) => b - a); - test('sorts arrays with undefined elements without comparator', () => { - expect(qsort([undefined, 2, 1])).toEqual([1, 2, undefined]); - }); + expect(arr2).toEqual(Int32Array.of(4, 3, 2, 1)); +}); - test('does not sort an array where all elements are equal according to a comparator', () => { - const arr = [0, 1, 2, 3]; - const swapMock = jest.fn(); +test('sorts arrays with undefined elements without comparator', () => { + expect(qsort([undefined, 2, 1])).toEqual([1, 2, undefined]); +}); - expect(qsort(arr, swapMock, () => 0)).toEqual([0, 1, 2, 3]); - expect(swapMock.mock.calls).toEqual([]); - }); +test('does not sort an array where all elements are equal according to a comparator', () => { + const arr = [0, 1, 2, 3]; + const swapMock = jest.fn(); - test('sort an array where any element is less than another element', () => { - expect(qsort([0, 1, 2, 3], undefined, () => -1)).toEqual([1, 2, 3, 0]); - }); + expect(qsort(arr, swapMock, () => 0)).toEqual([0, 1, 2, 3]); + expect(swapMock.mock.calls).toEqual([]); +}); - test('sort an array where any element is greater than another element', () => { - expect(qsort([0, 1, 2, 3], undefined, () => 1)).toEqual([0, 1, 2, 3]); - }); +test('sort an array where any element is less than another element', () => { + expect(qsort([0, 1, 2, 3], undefined, () => -1)).toEqual([1, 2, 3, 0]); +}); + +test('sort an array where any element is greater than another element', () => { + expect(qsort([0, 1, 2, 3], undefined, () => 1)).toEqual([0, 1, 2, 3]); }); diff --git a/src/test/rad.test.ts b/src/test/rad.test.ts new file mode 100644 index 0000000..37cc547 --- /dev/null +++ b/src/test/rad.test.ts @@ -0,0 +1,6 @@ +import { rad } from '../main'; + +test('converts degrees to radians', () => { + expect(rad(1)).toBeCloseTo(0.017); + expect(rad(90)).toBe(Math.PI / 2); +}); diff --git a/src/test/range.test.ts b/src/test/range.test.ts deleted file mode 100644 index 651769f..0000000 --- a/src/test/range.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { range } from '../main'; - -describe('range', () => { - test('creates a new array', () => { - expect(range(0)).toEqual([]); - expect(range(1)).toEqual([0]); - expect(range(5)).toEqual([0, 0.25, 0.5, 0.75, 1]); - expect(range(5, -10, 10)).toEqual([-10, -5, 0, 5, 10]); - }); - - test('fills an existing array', () => { - const a: number[] = []; - - expect(range(a, 5, -10, 10)).toBe(a); - expect(a).toEqual([-10, -5, 0, 5, 10]); - }); -}); diff --git a/src/test/right.test.ts b/src/test/right.test.ts new file mode 100644 index 0000000..d97ff0e --- /dev/null +++ b/src/test/right.test.ts @@ -0,0 +1,10 @@ +import { right } from '../main'; + +test('shifts right', () => { + expect(right(0xab_cd_ef_ab_cd_ef_ab, 24)).toBe(0xab_cd_ef_ab); +}); + +test('handles NaN as native operator', () => { + expect(right(NaN, 8)).toBe(0); + expect(right(0xab_cd, NaN)).toBe(0xab_cd); +}); diff --git a/src/test/scale.test.ts b/src/test/scale.test.ts new file mode 100644 index 0000000..7d9dd23 --- /dev/null +++ b/src/test/scale.test.ts @@ -0,0 +1,13 @@ +import { scale } from '../main'; + +test('scales value from one range to another', () => { + expect(scale(0, 100, 1, 3)(2)).toBe(50); +}); + +test('scales value between inverse ranges', () => { + expect(scale(0, 100, -100, 0)(-75)).toBe(25); +}); + +test('scales value outside of initial range', () => { + expect(scale(-100, 100, 1, 2)(4)).toBe(500); +}); diff --git a/src/test/seq.test.ts b/src/test/seq.test.ts new file mode 100644 index 0000000..5ab8d40 --- /dev/null +++ b/src/test/seq.test.ts @@ -0,0 +1,8 @@ +import { seq } from '../main'; + +test('creates a new array', () => { + expect(seq(0)).toEqual([]); + expect(seq(1)).toEqual([0]); + expect(seq(5)).toEqual([0, 0.25, 0.5, 0.75, 1]); + expect(seq(5, -10, 10)).toEqual([-10, -5, 0, 5, 10]); +}); diff --git a/src/test/snap.test.ts b/src/test/snap.test.ts new file mode 100644 index 0000000..4da9d3d --- /dev/null +++ b/src/test/snap.test.ts @@ -0,0 +1,8 @@ +import { snap } from '../main'; + +test('rounds to a step', () => { + expect(snap(3)(-10)).toBe(-9); + expect(snap(3)(-11)).toBe(-12); + expect(snap(3)(10)).toBe(9); + expect(snap(3)(11)).toBe(12); +}); diff --git a/src/test/xor.test.ts b/src/test/xor.test.ts new file mode 100644 index 0000000..89faa6c --- /dev/null +++ b/src/test/xor.test.ts @@ -0,0 +1,10 @@ +import { xor } from '../main'; + +test('applies XOR operator', () => { + expect(xor(0x10_00_00_00_00, 0x10_10_10_10_10)).toBe(0x10_10_10_10); +}); + +test('handles NaN as native operator', () => { + expect(xor(NaN, 0xab_cd)).toBe(0xab_cd); + expect(xor(0xab_cd, NaN)).toBe(0xab_cd); +});