stats.standard-deviation@1.0.0
impl/typescript.ts
1,792 bytes · the TypeScript implementation · view raw
import { type DeviationKind } from "./stats_standard_deviation_types.ts";
const POW10 = [1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12];
// Half away from zero on the binary64 value. A deviation is never negative,
// so there is no sign to restore.
function roundTo(x: number, decimals: number): number {
const scale = POW10[decimals];
const y = x * scale;
let r = Math.floor(y);
if (y - r >= 0.5) r += 1;
return r / scale + 0;
}
/**
* Population or sample standard deviation, two-pass.
*
* The one-pass "mean of squares minus square of mean" shortcut cancels
* catastrophically on large, close values; two passes do not.
*/
export function standardDeviation(values: readonly number[], kind: DeviationKind, decimals: number): number {
if (!Array.isArray(values)) throw new TypeError("values must be a list of numbers");
for (const v of values) {
if (typeof v !== "number" || !Number.isFinite(v)) {
throw new TypeError(`values must be finite numbers, received ${v}`);
}
}
if (kind !== "population" && kind !== "sample") {
throw new RangeError(`unknown standard deviation kind "${kind}"`);
}
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 12) {
throw new RangeError(`decimals must be a whole number from 0 to 12, received ${decimals}`);
}
const n = values.length;
if (n === 0) throw new RangeError("values must not be empty");
if (kind === "sample" && n < 2) {
throw new RangeError("sample standard deviation needs at least 2 values");
}
let sum = 0;
for (const v of values) sum += v;
const mean = sum / n;
let squares = 0;
for (const v of values) {
const d = v - mean;
squares += d * d;
}
return roundTo(Math.sqrt(squares / (kind === "sample" ? n - 1 : n)), decimals);
}