use super::funejson::Value; use super::math_round_div::round_div; use super::money_amount::{money_from_value, money_to_value, Money}; use super::money_sum::sum_money; fn tenths(amount: &Money, miles: i64) -> i64 { round_div(amount.minor * 10, miles, "half-up") } fn display(value: i64) -> String { let sign = if value < 0 { "-" } else { "" }; let magnitude = value.unsigned_abs(); format!("{}{}.{}", sign, magnitude / 10, magnitude % 10) } /// Whole-life cost per mile, in tenths of a penny, the unit fleet reports use. /// /// Each part is rounded on its own for the breakdown, and the total is rounded /// from the total cost: adding up the rounded parts drifts by a tenth or two, /// which is why the total is not their sum. /// /// # Panics /// Panics if miles is not above zero or the costs mix currencies. pub fn cost_per_mile(costs: &WholeLifeCosts, miles: i64) -> CostPerMile { if miles <= 0 { panic!("miles must be a whole number above zero, received {}", miles); } let total_cost = sum_money( &[ costs.depreciation.clone(), costs.fuel.clone(), costs.maintenance.clone(), costs.insurance.clone(), costs.tax.clone(), ], &costs.depreciation.currency, ); let total_tenths = tenths(&total_cost, miles); CostPerMile { depreciation_tenths: tenths(&costs.depreciation, miles), fuel_tenths: tenths(&costs.fuel, miles), maintenance_tenths: tenths(&costs.maintenance, miles), insurance_tenths: tenths(&costs.insurance, miles), tax_tenths: tenths(&costs.tax, miles), total_tenths, per_mile: display(total_tenths), total_cost, } } pub fn cost_per_mile_to_value(c: &CostPerMile) -> Value { Value::obj(vec![ ("totalCost", money_to_value(&c.total_cost)), ("depreciationTenths", Value::Int(c.depreciation_tenths)), ("fuelTenths", Value::Int(c.fuel_tenths)), ("maintenanceTenths", Value::Int(c.maintenance_tenths)), ("insuranceTenths", Value::Int(c.insurance_tenths)), ("taxTenths", Value::Int(c.tax_tenths)), ("totalTenths", Value::Int(c.total_tenths)), ("perMile", Value::str(&c.per_mile)), ]) } pub fn fune_vector(args: &[Value]) -> Value { if let Value::Float(f) = &args[1] { panic!("miles must be a whole number above zero, received {}", f); } let c = &args[0]; let costs = WholeLifeCosts { depreciation: money_from_value(c.get("depreciation")), fuel: money_from_value(c.get("fuel")), maintenance: money_from_value(c.get("maintenance")), insurance: money_from_value(c.get("insurance")), tax: money_from_value(c.get("tax")), }; cost_per_mile_to_value(&cost_per_mile(&costs, args[1].as_i64())) }