@flighthq/math 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/angle.d.ts +13 -0
- package/dist/angle.d.ts.map +1 -0
- package/dist/angle.js +24 -0
- package/dist/angle.js.map +1 -0
- package/dist/clamp.d.ts +15 -0
- package/dist/clamp.d.ts.map +1 -0
- package/dist/clamp.js +23 -0
- package/dist/clamp.js.map +1 -0
- package/dist/comparison.d.ts +17 -0
- package/dist/comparison.d.ts.map +1 -0
- package/dist/comparison.js +26 -0
- package/dist/comparison.js.map +1 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +11 -0
- package/dist/constants.js.map +1 -0
- package/dist/hash.d.ts +35 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +51 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolation.d.ts +27 -0
- package/dist/interpolation.d.ts.map +1 -0
- package/dist/interpolation.js +43 -0
- package/dist/interpolation.js.map +1 -0
- package/dist/interpolationAdvanced.d.ts +42 -0
- package/dist/interpolationAdvanced.d.ts.map +1 -0
- package/dist/interpolationAdvanced.js +71 -0
- package/dist/interpolationAdvanced.js.map +1 -0
- package/dist/nextPowerOfTwo.d.ts +22 -0
- package/dist/nextPowerOfTwo.d.ts.map +1 -0
- package/dist/nextPowerOfTwo.js +52 -0
- package/dist/nextPowerOfTwo.js.map +1 -0
- package/dist/numberTheory.d.ts +33 -0
- package/dist/numberTheory.d.ts.map +1 -0
- package/dist/numberTheory.js +62 -0
- package/dist/numberTheory.js.map +1 -0
- package/dist/random.d.ts +20 -0
- package/dist/random.d.ts.map +1 -0
- package/dist/random.js +27 -0
- package/dist/random.js.map +1 -0
- package/dist/randomDistributions.d.ts +105 -0
- package/dist/randomDistributions.d.ts.map +1 -0
- package/dist/randomDistributions.js +209 -0
- package/dist/randomDistributions.js.map +1 -0
- package/dist/randomRange.d.ts +18 -0
- package/dist/randomRange.d.ts.map +1 -0
- package/dist/randomRange.js +29 -0
- package/dist/randomRange.js.map +1 -0
- package/dist/rounding.d.ts +35 -0
- package/dist/rounding.d.ts.map +1 -0
- package/dist/rounding.js +53 -0
- package/dist/rounding.js.map +1 -0
- package/dist/scalar.d.ts +30 -0
- package/dist/scalar.d.ts.map +1 -0
- package/dist/scalar.js +42 -0
- package/dist/scalar.js.map +1 -0
- package/dist/statistics.d.ts +29 -0
- package/dist/statistics.d.ts.map +1 -0
- package/dist/statistics.js +67 -0
- package/dist/statistics.js.map +1 -0
- package/package.json +37 -0
- package/src/angle.test.ts +83 -0
- package/src/clamp.test.ts +61 -0
- package/src/comparison.test.ts +54 -0
- package/src/constants.test.ts +38 -0
- package/src/hash.test.ts +84 -0
- package/src/interpolation.test.ts +93 -0
- package/src/interpolationAdvanced.test.ts +122 -0
- package/src/nextPowerOfTwo.test.ts +84 -0
- package/src/numberTheory.test.ts +95 -0
- package/src/random.test.ts +32 -0
- package/src/randomDistributions.test.ts +348 -0
- package/src/randomRange.test.ts +99 -0
- package/src/rounding.test.ts +81 -0
- package/src/scalar.test.ts +58 -0
- package/src/statistics.test.ts +74 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createRandomSource } from './random';
|
|
2
|
+
import { randomBool, randomInt, randomRange, randomSign } from './randomRange';
|
|
3
|
+
|
|
4
|
+
const rng = () => createRandomSource(0xabcdef);
|
|
5
|
+
|
|
6
|
+
describe('randomBool', () => {
|
|
7
|
+
it('returns only booleans', () => {
|
|
8
|
+
const random = rng();
|
|
9
|
+
for (let i = 0; i < 100; i++) {
|
|
10
|
+
const v = randomBool(random);
|
|
11
|
+
expect(typeof v).toBe('boolean');
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
it('returns true with probability 1', () => {
|
|
15
|
+
const random = rng();
|
|
16
|
+
for (let i = 0; i < 10; i++) expect(randomBool(random, 1)).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
it('returns false with probability 0', () => {
|
|
19
|
+
const random = rng();
|
|
20
|
+
for (let i = 0; i < 10; i++) expect(randomBool(random, 0)).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
it('is deterministic for the same seed', () => {
|
|
23
|
+
const a = rng();
|
|
24
|
+
const b = rng();
|
|
25
|
+
for (let i = 0; i < 20; i++) expect(randomBool(a)).toBe(randomBool(b));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('randomInt', () => {
|
|
30
|
+
it('returns integers within [min, max]', () => {
|
|
31
|
+
const random = rng();
|
|
32
|
+
for (let i = 0; i < 200; i++) {
|
|
33
|
+
const v = randomInt(random, 0, 9);
|
|
34
|
+
expect(Number.isInteger(v)).toBe(true);
|
|
35
|
+
expect(v).toBeGreaterThanOrEqual(0);
|
|
36
|
+
expect(v).toBeLessThanOrEqual(9);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
it('returns min when min equals max', () => {
|
|
40
|
+
const random = rng();
|
|
41
|
+
expect(randomInt(random, 5, 5)).toBe(5);
|
|
42
|
+
});
|
|
43
|
+
it('throws when min > max', () => {
|
|
44
|
+
const random = rng();
|
|
45
|
+
expect(() => randomInt(random, 10, 5)).toThrow(RangeError);
|
|
46
|
+
});
|
|
47
|
+
it('is deterministic for the same seed', () => {
|
|
48
|
+
const a = rng();
|
|
49
|
+
const b = rng();
|
|
50
|
+
for (let i = 0; i < 20; i++) expect(randomInt(a, 0, 100)).toBe(randomInt(b, 0, 100));
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('randomRange', () => {
|
|
55
|
+
it('returns values within [min, max)', () => {
|
|
56
|
+
const random = rng();
|
|
57
|
+
for (let i = 0; i < 200; i++) {
|
|
58
|
+
const v = randomRange(random, 5, 10);
|
|
59
|
+
expect(v).toBeGreaterThanOrEqual(5);
|
|
60
|
+
expect(v).toBeLessThan(10);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
it('returns min when min equals max', () => {
|
|
64
|
+
const random = rng();
|
|
65
|
+
expect(randomRange(random, 3, 3)).toBe(3);
|
|
66
|
+
});
|
|
67
|
+
it('is deterministic for the same seed', () => {
|
|
68
|
+
const a = rng();
|
|
69
|
+
const b = rng();
|
|
70
|
+
for (let i = 0; i < 20; i++) expect(randomRange(a, 0, 100)).toBe(randomRange(b, 0, 100));
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('randomSign', () => {
|
|
75
|
+
it('returns only 1 or -1', () => {
|
|
76
|
+
const random = rng();
|
|
77
|
+
for (let i = 0; i < 100; i++) {
|
|
78
|
+
const v = randomSign(random);
|
|
79
|
+
expect(v === 1 || v === -1).toBe(true);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
it('produces both signs', () => {
|
|
83
|
+
const random = rng();
|
|
84
|
+
let sawPositive = false;
|
|
85
|
+
let sawNegative = false;
|
|
86
|
+
for (let i = 0; i < 100; i++) {
|
|
87
|
+
const v = randomSign(random);
|
|
88
|
+
if (v === 1) sawPositive = true;
|
|
89
|
+
if (v === -1) sawNegative = true;
|
|
90
|
+
}
|
|
91
|
+
expect(sawPositive).toBe(true);
|
|
92
|
+
expect(sawNegative).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
it('is deterministic for the same seed', () => {
|
|
95
|
+
const a = rng();
|
|
96
|
+
const b = rng();
|
|
97
|
+
for (let i = 0; i < 20; i++) expect(randomSign(a)).toBe(randomSign(b));
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ceilTo, euclideanMod, floorTo, fract, roundTo } from './rounding';
|
|
2
|
+
|
|
3
|
+
describe('ceilTo', () => {
|
|
4
|
+
it('rounds up to the nearest step', () => {
|
|
5
|
+
expect(ceilTo(7, 5)).toBe(10);
|
|
6
|
+
});
|
|
7
|
+
it('leaves an exact multiple unchanged', () => {
|
|
8
|
+
expect(ceilTo(10, 5)).toBe(10);
|
|
9
|
+
});
|
|
10
|
+
it('returns value when step is 0', () => {
|
|
11
|
+
expect(ceilTo(7, 0)).toBe(7);
|
|
12
|
+
});
|
|
13
|
+
it('works with negative values', () => {
|
|
14
|
+
expect(ceilTo(-7, 5)).toBe(-5);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('euclideanMod', () => {
|
|
19
|
+
it('returns the remainder for positive inputs', () => {
|
|
20
|
+
expect(euclideanMod(7, 4)).toBe(3);
|
|
21
|
+
});
|
|
22
|
+
it('returns a non-negative result for negative inputs', () => {
|
|
23
|
+
expect(euclideanMod(-1, 4)).toBe(3);
|
|
24
|
+
expect(euclideanMod(-5, 4)).toBe(3);
|
|
25
|
+
});
|
|
26
|
+
it('returns 0 when value is a multiple of divisor', () => {
|
|
27
|
+
expect(euclideanMod(8, 4)).toBe(0);
|
|
28
|
+
});
|
|
29
|
+
it('throws for a zero divisor', () => {
|
|
30
|
+
expect(() => euclideanMod(7, 0)).toThrow(RangeError);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('floorTo', () => {
|
|
35
|
+
it('rounds down to the nearest step', () => {
|
|
36
|
+
expect(floorTo(7, 5)).toBe(5);
|
|
37
|
+
});
|
|
38
|
+
it('leaves an exact multiple unchanged', () => {
|
|
39
|
+
expect(floorTo(10, 5)).toBe(10);
|
|
40
|
+
});
|
|
41
|
+
it('returns value when step is 0', () => {
|
|
42
|
+
expect(floorTo(7, 0)).toBe(7);
|
|
43
|
+
});
|
|
44
|
+
it('works with negative values', () => {
|
|
45
|
+
expect(floorTo(-7, 5)).toBe(-10);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('fract', () => {
|
|
50
|
+
it('returns the fractional part of a positive number', () => {
|
|
51
|
+
expect(fract(3.75)).toBeCloseTo(0.75, 10);
|
|
52
|
+
});
|
|
53
|
+
it('returns 0 for an integer', () => {
|
|
54
|
+
expect(fract(3)).toBe(0);
|
|
55
|
+
});
|
|
56
|
+
it('preserves sign for negative numbers', () => {
|
|
57
|
+
expect(fract(-1.3)).toBeCloseTo(-0.3, 10);
|
|
58
|
+
});
|
|
59
|
+
it('returns 0 for 0', () => {
|
|
60
|
+
expect(fract(0)).toBe(0);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('roundTo', () => {
|
|
65
|
+
it('rounds to the nearest step', () => {
|
|
66
|
+
expect(roundTo(7, 5)).toBe(5);
|
|
67
|
+
expect(roundTo(8, 5)).toBe(10);
|
|
68
|
+
});
|
|
69
|
+
it('leaves an exact multiple unchanged', () => {
|
|
70
|
+
expect(roundTo(10, 5)).toBe(10);
|
|
71
|
+
});
|
|
72
|
+
it('returns value when step is 0', () => {
|
|
73
|
+
expect(roundTo(7, 0)).toBe(7);
|
|
74
|
+
});
|
|
75
|
+
it('works with floating-point steps', () => {
|
|
76
|
+
expect(roundTo(0.3, 0.1)).toBeCloseTo(0.3, 10);
|
|
77
|
+
});
|
|
78
|
+
it('works with negative values', () => {
|
|
79
|
+
expect(roundTo(-7, 5)).toBe(-5);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ceilPowerOfTwo, floorPowerOfTwo, quantize, sign } from './scalar';
|
|
2
|
+
|
|
3
|
+
describe('ceilPowerOfTwo', () => {
|
|
4
|
+
it('rounds up to the next power of two', () => {
|
|
5
|
+
expect(ceilPowerOfTwo(5)).toBe(8);
|
|
6
|
+
expect(ceilPowerOfTwo(9)).toBe(16);
|
|
7
|
+
});
|
|
8
|
+
it('leaves an exact power of two unchanged', () => {
|
|
9
|
+
expect(ceilPowerOfTwo(8)).toBe(8);
|
|
10
|
+
expect(ceilPowerOfTwo(256)).toBe(256);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('floorPowerOfTwo', () => {
|
|
15
|
+
it('rounds down to the previous power of two', () => {
|
|
16
|
+
expect(floorPowerOfTwo(6)).toBe(4);
|
|
17
|
+
expect(floorPowerOfTwo(100)).toBe(64);
|
|
18
|
+
});
|
|
19
|
+
it('leaves an exact power of two unchanged', () => {
|
|
20
|
+
expect(floorPowerOfTwo(8)).toBe(8);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('quantize', () => {
|
|
25
|
+
it('quantizes to the nearest step', () => {
|
|
26
|
+
expect(quantize(0.7, 4, 0, 1)).toBeCloseTo(0.75, 10);
|
|
27
|
+
});
|
|
28
|
+
it('returns min at the lower bound', () => {
|
|
29
|
+
expect(quantize(0, 4, 0, 1)).toBeCloseTo(0, 10);
|
|
30
|
+
});
|
|
31
|
+
it('returns max at the upper bound', () => {
|
|
32
|
+
expect(quantize(1, 4, 0, 1)).toBeCloseTo(1, 10);
|
|
33
|
+
});
|
|
34
|
+
it('returns min when steps is 0', () => {
|
|
35
|
+
expect(quantize(0.5, 0, 0, 1)).toBe(0);
|
|
36
|
+
});
|
|
37
|
+
it('returns min when min equals max', () => {
|
|
38
|
+
expect(quantize(5, 4, 5, 5)).toBe(5);
|
|
39
|
+
});
|
|
40
|
+
it('clamps values outside the range', () => {
|
|
41
|
+
expect(quantize(-1, 4, 0, 1)).toBeCloseTo(0, 10);
|
|
42
|
+
expect(quantize(2, 4, 0, 1)).toBeCloseTo(1, 10);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('sign', () => {
|
|
47
|
+
it('returns 1 for positive values', () => {
|
|
48
|
+
expect(sign(5)).toBe(1);
|
|
49
|
+
expect(sign(0.001)).toBe(1);
|
|
50
|
+
});
|
|
51
|
+
it('returns -1 for negative values', () => {
|
|
52
|
+
expect(sign(-5)).toBe(-1);
|
|
53
|
+
expect(sign(-0.001)).toBe(-1);
|
|
54
|
+
});
|
|
55
|
+
it('returns 0 for zero', () => {
|
|
56
|
+
expect(sign(0)).toBe(0);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { mean, median, standardDeviation, variance, weightedAverage } from './statistics';
|
|
2
|
+
|
|
3
|
+
describe('mean', () => {
|
|
4
|
+
it('returns the arithmetic mean', () => {
|
|
5
|
+
expect(mean([1, 2, 3, 4, 5])).toBe(3);
|
|
6
|
+
});
|
|
7
|
+
it('returns the single element for a one-element array', () => {
|
|
8
|
+
expect(mean([42])).toBe(42);
|
|
9
|
+
});
|
|
10
|
+
it('returns NaN for an empty array', () => {
|
|
11
|
+
expect(mean([])).toBeNaN();
|
|
12
|
+
});
|
|
13
|
+
it('handles negative values', () => {
|
|
14
|
+
expect(mean([-2, 0, 2])).toBe(0);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('median', () => {
|
|
19
|
+
it('returns the middle value for an odd-length array', () => {
|
|
20
|
+
expect(median([3, 1, 2])).toBe(2);
|
|
21
|
+
});
|
|
22
|
+
it('returns the average of the two middle values for an even-length array', () => {
|
|
23
|
+
expect(median([1, 2, 3, 4])).toBe(2.5);
|
|
24
|
+
});
|
|
25
|
+
it('returns NaN for an empty array', () => {
|
|
26
|
+
expect(median([])).toBeNaN();
|
|
27
|
+
});
|
|
28
|
+
it('does not mutate the input', () => {
|
|
29
|
+
const values = [5, 3, 1, 4, 2];
|
|
30
|
+
const original = values.slice();
|
|
31
|
+
median(values);
|
|
32
|
+
expect(values).toEqual(original);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('standardDeviation', () => {
|
|
37
|
+
it('returns 0 for a single-element array', () => {
|
|
38
|
+
expect(standardDeviation([5])).toBe(0);
|
|
39
|
+
});
|
|
40
|
+
it('returns NaN for an empty array', () => {
|
|
41
|
+
expect(standardDeviation([])).toBeNaN();
|
|
42
|
+
});
|
|
43
|
+
it('computes the population standard deviation', () => {
|
|
44
|
+
expect(standardDeviation([2, 4, 4, 4, 5, 5, 7, 9])).toBeCloseTo(2, 5);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('variance', () => {
|
|
49
|
+
it('returns 0 for a single-element array', () => {
|
|
50
|
+
expect(variance([5])).toBe(0);
|
|
51
|
+
});
|
|
52
|
+
it('returns NaN for an empty array', () => {
|
|
53
|
+
expect(variance([])).toBeNaN();
|
|
54
|
+
});
|
|
55
|
+
it('computes the population variance', () => {
|
|
56
|
+
expect(variance([2, 4, 4, 4, 5, 5, 7, 9])).toBeCloseTo(4, 5);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('weightedAverage', () => {
|
|
61
|
+
it('returns the weighted average', () => {
|
|
62
|
+
expect(weightedAverage([1, 2, 3], [1, 1, 1])).toBeCloseTo(2, 10);
|
|
63
|
+
expect(weightedAverage([0, 10], [1, 3])).toBeCloseTo(7.5, 10);
|
|
64
|
+
});
|
|
65
|
+
it('returns NaN for an empty array', () => {
|
|
66
|
+
expect(weightedAverage([], [])).toBeNaN();
|
|
67
|
+
});
|
|
68
|
+
it('returns NaN when all weights are 0', () => {
|
|
69
|
+
expect(weightedAverage([1, 2, 3], [0, 0, 0])).toBeNaN();
|
|
70
|
+
});
|
|
71
|
+
it('throws when arrays have different lengths', () => {
|
|
72
|
+
expect(() => weightedAverage([1, 2], [1])).toThrow(RangeError);
|
|
73
|
+
});
|
|
74
|
+
});
|