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