Functional Weave
Code in TypeScript

stats.weighted-average@1.0.0

impl/typescript.ts

2,090 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 { type RoundingMode, roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune

const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);

/**
 * Weighted average of integers, rounded once at the end.
 *
 * The numerator and denominator are exact integers, so the rounding mode the
 * caller names is the only rounding that ever happens.
 */
export function weightedAverage(
  values: readonly number[],
  weights: readonly number[],
  decimals: number,
  mode: RoundingMode,
): number {
  if (!Array.isArray(values) || !Array.isArray(weights)) {
    throw new TypeError("values and weights must be lists of integers");
  }
  if (values.length !== weights.length) {
    throw new RangeError(`values and weights must be the same length, received ${values.length} and ${weights.length}`);
  }
  if (values.length === 0) throw new RangeError("values must not be empty");
  if (!Number.isInteger(decimals) || decimals < 0 || decimals > 9) {
    throw new RangeError(`decimals must be a whole number from 0 to 9, received ${decimals}`);
  }

  // BigInt so an overflow is detected, not silently rounded away.
  let numerator = BigInt(0);
  let denominator = BigInt(0);
  for (let i = 0; i < values.length; i++) {
    const v = values[i];
    const w = weights[i];
    if (typeof v !== "number" || !Number.isSafeInteger(v)) throw new TypeError(`values must be integers, received ${v}`);
    if (typeof w !== "number" || !Number.isSafeInteger(w)) throw new TypeError(`weights must be integers, received ${w}`);
    if (w < 0) throw new RangeError(`weights must not be negative, received ${w}`);
    numerator += BigInt(v) * BigInt(w);
    denominator += BigInt(w);
  }
  if (denominator === BigInt(0)) throw new RangeError("weights must not all be zero");

  const scale = BigInt(10) ** BigInt(decimals);
  const scaled = numerator * scale;
  if (scaled > MAX_SAFE || scaled < -MAX_SAFE || denominator > MAX_SAFE) {
    throw new RangeError("weighted sum is too large to average exactly; lower decimals or rescale the values");
  }
  return roundDiv(Number(scaled), Number(denominator), mode) / 10 ** decimals + 0;
}