Functional Weave
Code in Rust

stats.percentile@2.0.0

impl/typescript.ts

2,061 bytes · the TypeScript implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import { roundFloat } from "./math_round_float.ts";  ← from math.round-float ^1.0.0 · built alongside by fune
import { type PercentileMethod } from "./stats_percentile_types.ts";

/**
 * The p-th percentile of `values` by a named method.
 *
 * There is no single "95th percentile": the caller names the definition, so
 * a dashboard and a billing job cannot quietly disagree.
 */
export function percentile(values: readonly number[], p: number, method: PercentileMethod, decimals: number): number {
  if (!Array.isArray(values)) throw new TypeError("values must be a list of numbers");
  if (values.length === 0) throw new RangeError("values must not be empty");
  for (const v of values) {
    if (typeof v !== "number" || !Number.isFinite(v)) {
      throw new TypeError(`values must be finite numbers, received ${v}`);
    }
  }
  if (typeof p !== "number" || !Number.isFinite(p) || p < 0 || p > 100) {
    throw new RangeError(`p must be between 0 and 100, received ${p}`);
  }
  // Checked up front as well as in roundFloat (same wording), so a bad
  // decimals is reported before the method is looked at, as in 1.x.
  if (!Number.isInteger(decimals) || decimals < 0 || decimals > 12) {
    throw new RangeError(`decimals must be a whole number from 0 to 12, received ${decimals}`);
  }

  const sorted = [...values].sort((a, b) => a - b);
  const n = sorted.length;

  let result: number;
  if (method === "nearest-rank") {
    // The smallest value with at least p% of the sample at or below it.
    let rank = Math.ceil((p * n) / 100);
    if (rank < 1) rank = 1;
    result = sorted[rank - 1];
  } else if (method === "linear") {
    // Hyndman & Fan type 7: zero-based position (n - 1) * p / 100.
    const h = ((n - 1) * p) / 100;
    const lo = Math.floor(h);
    if (lo >= n - 1) {
      result = sorted[n - 1];
    } else {
      result = sorted[lo] + (h - lo) * (sorted[lo + 1] - sorted[lo]);
    }
  } else {
    throw new RangeError(`unknown percentile method "${method}"`);
  }
  // math.round-float rounds on the exact value of the double: 2.675 gives 2.67.
  return roundFloat(result, decimals);
}