Functional Weave
Code in Rust

energy.gas-kwh@1.0.0

impl/rust.rs

2,445 bytes · the Rust 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.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_div::round_div;  ← from math.round-div ^1.0.0 · built alongside by fune

// 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: i64 = 4261;
const DIVISOR: i64 = 1_500_000;
const MAX_VOLUME: i64 = 1_000_000_000_000;
const MAX_SAFE: i128 = 9_007_199_254_740_991;

/// 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.
///
/// # Panics
/// Panics on a volume or calorific value out of range, an unknown unit, or a
/// result too large to be exact in every language.
pub fn gas_kwh(volume: i64, volume_unit: &str, calorific_value: i64) -> GasEnergy {
    if volume < 0 || volume > MAX_VOLUME {
        panic!("volume must be a whole number of units from 0 to {}, received {}", MAX_VOLUME, volume);
    }
    if !(1..=100000).contains(&calorific_value) {
        panic!(
            "calorific value must be a whole number of thousandths of a MJ/m³ from 1 to 100000, received {}",
            calorific_value
        );
    }
    let hundredths = match volume_unit {
        "cubic-metres" => volume * 100,
        "hundreds-cubic-feet" => volume * 283,
        other => panic!("unknown volume unit \"{}\": expected cubic-metres or hundreds-cubic-feet", other),
    };
    let scaled = FACTOR * calorific_value;
    let whole = hundredths / DIVISOR;
    let rest = hundredths % DIVISOR;
    let watt_hours = whole as i128 * scaled as i128 + round_div(rest * scaled, DIVISOR, "half-up") as i128;
    if watt_hours > MAX_SAFE {
        panic!("gas energy too large to calculate exactly");
    }
    GasEnergy {
        cubic_metres_hundredths: hundredths,
        watt_hours: watt_hours as i64,
    }
}

pub fn gas_energy_to_value(e: &GasEnergy) -> Value {
    Value::obj(vec![
        ("cubicMetresHundredths", Value::Int(e.cubic_metres_hundredths)),
        ("wattHours", Value::Int(e.watt_hours)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    if let Value::Float(f) = &args[0] {
        panic!("volume must be a whole number of units, received {}", f);
    }
    if let Value::Float(f) = &args[2] {
        panic!("calorific value must be a whole number of thousandths of a MJ/m³, received {}", f);
    }
    gas_energy_to_value(&gas_kwh(args[0].as_i64(), args[1].as_str(), args[2].as_i64()))
}