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