Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { roundDiv } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { type GasEnergy, type GasVolumeUnit } from "./energy_gas_kwh_types.ts";
// 1.02264 / 3.6 in lowest terms, with the scales of the arguments folded in:
// watt-hours = hundredths of m³ x 4261 x CV (thousandths of MJ/m³) / 1,500,000.
const FACTOR = 4261;
const DIVISOR = 1_500_000;
const MAX_VOLUME = 1_000_000_000_000;
/**
* Gas meter units to kWh, the way a GB bill does it: volume (x 2.83 for an
* imperial meter) x 1.02264 x calorific value / 3.6, rounded once, half-up,
* to the watt-hour.
*/
export function gasKwh(volume: number, volumeUnit: GasVolumeUnit, calorificValue: number): GasEnergy {
if (!Number.isInteger(volume) || volume < 0 || volume > MAX_VOLUME) {
throw new RangeError(`volume must be a whole number of units from 0 to ${MAX_VOLUME}, received ${volume}`);
}
if (!Number.isInteger(calorificValue) || calorificValue < 1 || calorificValue > 100000) {
throw new RangeError(
`calorific value must be a whole number of thousandths of a MJ/m³ from 1 to 100000, received ${calorificValue}`
);
}
let hundredths: number;
if (volumeUnit === "cubic-metres") hundredths = volume * 100;
else if (volumeUnit === "hundreds-cubic-feet") hundredths = volume * 283;
else throw new RangeError(`unknown volume unit "${volumeUnit}": expected cubic-metres or hundreds-cubic-feet`);
// Split the volume by the divisor so no intermediate leaves the exact range.
const scaled = FACTOR * calorificValue;
const whole = Math.floor(hundredths / DIVISOR);
const rest = hundredths - whole * DIVISOR;
const wattHours = whole * scaled + roundDiv(rest * scaled, DIVISOR, "half-up");
if (wattHours > Number.MAX_SAFE_INTEGER) {
throw new RangeError("gas energy too large to calculate exactly");
}
return { cubicMetresHundredths: hundredths, wattHours };
}