use super::funejson::Value; use super::math_round_div::round_div; // 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())) }