use super::funejson::Value; use super::math_big_integer::BigInt; use super::math_round_div::round_div; use super::money_amount::{money, money_from_value, money_to_value, Money}; /// 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(), )) }