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_big_integer::BigInt; ← from math.big-integer ^1.0.0 · built alongside by fune
use super::math_round_div_big::round_div_big; ← from math.round-div-big ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
fn whole(name: &str, value: i64, max: i64) {
if value < 0 || value > max {
panic!("{} must be a whole number from 0 to {}, received {}", name, max, value);
}
}
/// A vehicle's value from its age and mileage, on the caller's retention curves.
///
/// Each whole year multiplies by that year's retention and a part year by a
/// straight-line share of it; each whole 10,000 miles multiplies by the
/// mileage retention and the remaining miles by a straight-line share. The
/// product is kept as one exact fraction and rounded once: rounding the value
/// year by year drifts by a penny or more over a long life.
///
/// # Panics
/// Panics on a negative cost, an out-of-range age, mileage or rate, an empty
/// curve or an unknown mode.
pub fn vehicle_depreciation(
cost: &Money,
age_months: i64,
mileage: i64,
retained_per_year_basis_points: &[i64],
retained_per10k_miles_basis_points: i64,
mode: &str,
) -> VehicleValue {
if cost.minor < 0 {
panic!("cost must not be negative, received {}", cost.minor);
}
whole("ageMonths", age_months, 600);
whole("mileage", mileage, 2000000);
if retained_per_year_basis_points.is_empty() {
panic!("retainedPerYearBasisPoints must have at least one year");
}
for bp in retained_per_year_basis_points {
whole("a retention rate", *bp, 10000);
}
whole("a retention rate", retained_per10k_miles_basis_points, 10000);
let last = retained_per_year_basis_points.len() - 1;
let rate_for = |year: usize| retained_per_year_basis_points[year.min(last)];
let ten_thousand = BigInt::from_i64(10000);
let mut num = BigInt::from_i64(1);
let mut den = BigInt::from_i64(1);
let years = (age_months / 12) as usize;
for year in 0..years {
num = num.mul(&BigInt::from_i64(rate_for(year)));
den = den.mul(&ten_thousand);
}
let months = age_months % 12;
if months > 0 {
num = num.mul(&BigInt::from_i64(120000 - (10000 - rate_for(years)) * months));
den = den.mul(&BigInt::from_i64(120000));
}
let mile_rate = retained_per10k_miles_basis_points;
for _ in 0..(mileage / 10000) {
num = num.mul(&BigInt::from_i64(mile_rate));
den = den.mul(&ten_thousand);
}
let miles = mileage % 10000;
if miles > 0 {
num = num.mul(&BigInt::from_i64(100000000 - (10000 - mile_rate) * miles));
den = den.mul(&BigInt::from_i64(100000000));
}
let value: i64 = round_div_big(&BigInt::from_i64(cost.minor).mul(&num).to_string(), &den.to_string(), mode)
.parse()
.unwrap();
let retained: i64 = round_div_big(&num.mul(&ten_thousand).to_string(), &den.to_string(), "half-up")
.parse()
.unwrap();
VehicleValue {
value: money(value, &cost.currency),
depreciation: money(cost.minor - value, &cost.currency),
retained_basis_points: retained,
}
}
pub fn vehicle_value_to_value(v: &VehicleValue) -> Value {
Value::obj(vec![
("value", money_to_value(&v.value)),
("depreciation", money_to_value(&v.depreciation)),
("retainedBasisPoints", Value::Int(v.retained_basis_points)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
for (i, name) in [(1usize, "ageMonths"), (2, "mileage"), (4, "a retention rate")] {
if let Value::Float(f) = &args[i] {
panic!("{} must be a whole number, received {}", name, f);
}
}
let curve: Vec<i64> = args[3]
.as_arr()
.iter()
.map(|v| {
if let Value::Float(f) = v {
panic!("a retention rate must be a whole number, received {}", f);
}
v.as_i64()
})
.collect();
vehicle_value_to_value(&vehicle_depreciation(
&money_from_value(&args[0]),
args[1].as_i64(),
args[2].as_i64(),
&curve,
args[4].as_i64(),
args[5].as_str(),
))
}