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::round_div; ← from math.round-div ^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
/// A hundred years of monthly payments, sixty of weekly ones.
pub const MAX_PAYMENTS: i64 = 3000;
/// The number of payments a term holds, after checking the arguments every
/// lending capability shares.
///
/// # Panics
/// Panics on a rate, frequency or term out of range.
pub fn payment_count(annual_rate_basis_points: i64, term_months: i64, payments_per_year: i64) -> i64 {
if annual_rate_basis_points < 0 || annual_rate_basis_points > 100000 {
panic!(
"annualRateBasisPoints must be between 0 and 100000, received {}",
annual_rate_basis_points
);
}
if payments_per_year < 1 || payments_per_year > 52 {
panic!("paymentsPerYear must be between 1 and 52, received {}", payments_per_year);
}
if term_months < 1 {
panic!("termMonths must be at least 1, received {}", term_months);
}
if (term_months as i128 * payments_per_year as i128) % 12 != 0 {
panic!(
"a term of {} months is not a whole number of payments at {} a year",
term_months, payments_per_year
);
}
let n = (term_months as i128 * payments_per_year as i128 / 12) as i64;
if n > MAX_PAYMENTS {
panic!("{} payments is more than the {} payment limit", n, MAX_PAYMENTS);
}
n
}
/// Round the exact quotient numerator / denominator (both positive) with a
/// math.round-div mode, however large they are. Only the integer part and
/// where the remainder sits against one half matter, so the decision is
/// handed to round_div as a small fraction with the same integer part and the
/// same side of the half: q, q + 1/4, q + 1/2 or q + 3/4.
pub fn round_wide(numerator: &BigInt, denominator: &BigInt, mode: &str) -> i64 {
let (q, remainder) = numerator.div_rem(denominator);
let twice = remainder.mul(&BigInt::from_i64(2));
let q = q.to_i64();
if twice.is_zero() {
return round_div(q, 1, mode);
}
if twice == *denominator {
return round_div(2 * q + 1, 2, mode);
}
round_div(4 * q + if twice < *denominator { 1 } else { 3 }, 4, mode)
}
/// The level payment that repays `principal` over the term at a nominal
/// annual rate divided equally between the periods:
///
/// payment = P · r / (1 − (1 + r)^−n), r = rate / payments_per_year
///
/// With r = b / D (b basis points, D = 10000 × payments_per_year) this is the
/// exact fraction P · b · (D + b)^n / (D · ((D + b)^n − D^n)), evaluated in
/// integers of whatever size it takes and rounded once. A zero rate is P / n.
///
/// # Panics
/// Panics on a principal of zero or less, or a rate, frequency or term out of
/// range.
pub fn loan_payment(
principal: &Money,
annual_rate_basis_points: i64,
term_months: i64,
payments_per_year: i64,
mode: &str,
) -> Money {
if principal.minor <= 0 {
panic!("principal must be greater than zero, received {}", principal.minor);
}
let n = payment_count(annual_rate_basis_points, term_months, payments_per_year);
if annual_rate_basis_points == 0 {
return money(round_div(principal.minor, n, mode), &principal.currency);
}
let b = BigInt::from_i64(annual_rate_basis_points);
let d = BigInt::from_i64(10000 * payments_per_year);
let grown = d.add(&b).pow(n as u64);
let numerator = BigInt::from_i64(principal.minor).mul(&b).mul(&grown);
let denominator = d.mul(&grown.sub(&d.pow(n as u64)));
money(round_wide(&numerator, &denominator, mode), &principal.currency)
}
pub fn fune_vector(args: &[Value]) -> Value {
money_to_value(&loan_payment(
&money_from_value(&args[0]),
args[1].as_i64(),
args[2].as_i64(),
args[3].as_i64(),
args[4].as_str(),
))
}