Functional Weave
Code in Python

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);
}