51 lines
1.5 KiB
JavaScript
51 lines
1.5 KiB
JavaScript
|
|
'use strict';
|
||
|
|
|
||
|
|
const { test } = require('node:test');
|
||
|
|
const assert = require('node:assert/strict');
|
||
|
|
|
||
|
|
const { mean, stdDev, median, mad, lerp } = require('../../src/stats');
|
||
|
|
|
||
|
|
const EPS = 1e-9;
|
||
|
|
|
||
|
|
function near(a, b, eps = EPS) {
|
||
|
|
assert.ok(Math.abs(a - b) <= eps, `expected ${a} ≈ ${b} (eps ${eps})`);
|
||
|
|
}
|
||
|
|
|
||
|
|
test('mean: basic and empty', () => {
|
||
|
|
assert.equal(mean([1, 2, 3, 4]), 2.5);
|
||
|
|
assert.equal(mean([]), 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('stdDev: zero-variance, classic sample, single-element, empty', () => {
|
||
|
|
assert.equal(stdDev([1, 1, 1, 1]), 0);
|
||
|
|
near(stdDev([1, 2, 3, 4, 5]), 1.5811388300841898);
|
||
|
|
assert.equal(stdDev([5]), 0);
|
||
|
|
assert.equal(stdDev([]), 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('median: odd, even, empty', () => {
|
||
|
|
assert.equal(median([1, 2, 3, 4, 5]), 3);
|
||
|
|
assert.equal(median([1, 2, 3, 4]), 2.5);
|
||
|
|
assert.equal(median([]), 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('mad: hand-checked sample and constant array', () => {
|
||
|
|
// [1,1,2,2,4,6,9] -> median 2 -> |dev| [1,1,0,0,2,4,7] -> sorted
|
||
|
|
// [0,0,1,1,2,4,7] -> mad = 1.
|
||
|
|
assert.equal(mad([1, 1, 2, 2, 4, 6, 9]), 1);
|
||
|
|
assert.equal(mad([5, 5, 5]), 0);
|
||
|
|
assert.equal(mad([]), 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('lerp: in-range mapping and degenerate pass-through', () => {
|
||
|
|
assert.equal(lerp(2, 0, 4, 0, 100), 50);
|
||
|
|
assert.equal(lerp(2, 0, 0, 0, 100), 2);
|
||
|
|
// iMin > iMax also degenerate (defensive against swapped bounds).
|
||
|
|
assert.equal(lerp(2, 4, 0, 0, 100), 2);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('lerp: float arithmetic stays within epsilon', () => {
|
||
|
|
near(lerp(0.1, 0, 1, 0, 10), 1);
|
||
|
|
near(lerp(1 / 3, 0, 1, 0, 30), 10);
|
||
|
|
});
|