use super::funejson::Value; use super::energy_carbon_emissions_data::CONVERSION_FACTORS; use super::math_round_div::round_div; // quantity (thousandths of a unit) x factor (hundred-thousandths of a kg) // / 100000 is grams. const DIVISOR: i64 = 100_000; const MAX_SAFE: i128 = 9_007_199_254_740_991; /// kgCO2e, as whole grams, for an amount of a fuel, from the UK government /// conversion factor set for `year`. /// /// # Panics /// Panics on a negative quantity, an unknown fuel, a year with no factor set, /// or a result too large to be exact in every language. pub fn carbon_emissions(fuel: &str, quantity: i64, year: i64) -> CarbonEmissions { if quantity < 0 { panic!( "quantity must be a whole number of thousandths of a unit, 0 or more, received {}", quantity ); } if !CONVERSION_FACTORS.iter().any(|f| f.fuel == fuel) { panic!("unknown fuel \"{}\"", fuel); } let row = match CONVERSION_FACTORS.iter().find(|f| f.fuel == fuel && f.year == year) { Some(row) => row, None => panic!("no conversion factor for {} in {}", fuel, year), }; let whole = quantity / DIVISOR; let rest = quantity % DIVISOR; let grams = whole as i128 * row.factor as i128 + round_div(rest * row.factor, DIVISOR, "half-up") as i128; if grams > MAX_SAFE { panic!("emissions too large to calculate exactly"); } CarbonEmissions { fuel: fuel.to_string(), unit: row.unit.to_string(), year, scope: row.scope, factor: row.factor, grams: grams as i64, } } pub fn carbon_emissions_to_value(e: &CarbonEmissions) -> Value { Value::obj(vec![ ("fuel", Value::str(&e.fuel)), ("unit", Value::str(&e.unit)), ("year", Value::Int(e.year)), ("scope", Value::Int(e.scope)), ("factor", Value::Int(e.factor)), ("grams", Value::Int(e.grams)), ]) } pub fn fune_vector(args: &[Value]) -> Value { if let Value::Float(f) = &args[1] { panic!("quantity must be a whole number of thousandths of a unit, 0 or more, received {}", f); } carbon_emissions_to_value(&carbon_emissions(args[0].as_str(), args[1].as_i64(), args[2].as_i64())) }