use log::{warn};
use libm::log10;
pub fn nper<C: Into<f64> + Copy, P: Into<f64> + Copy, F: Into<f64> + Copy>(periodic_rate: f64, payment: C, present_value: P, future_value: F) -> f64 {
nper_solution(periodic_rate, payment, present_value, future_value).periods
}
pub fn nper_solution<C: Into<f64> + Copy, P: Into<f64> + Copy, F: Into<f64> + Copy>(periodic_rate: f64, payment: C, present_value: P, future_value: F) -> NperSolution {
let pmt = payment.into();
let pv = present_value.into();
let fv = future_value.into();
assert!(pv >= 0.0);
assert!(fv >= 0.0);
assert!(pv + fv > 0.0, "Either present_value, and/or future_value, must be greater than 0.");
assert!(pmt < 0_f64, "The payment amount must be negative, same as Excel / Google Sheets."); assert!(periodic_rate.is_finite(), "Rate must be finite.");
assert!(pmt.is_finite(), "Payment amount must be finite.");
assert!(pv.is_finite(), "Present Value amount must be finite.");
assert!(fv.is_finite(), "Future Value amount must be finite.");
let numerator = libm::log10( (pmt - fv * periodic_rate) / (pmt + pv * periodic_rate) );
let num_periods = numerator / libm::log10(1. + periodic_rate);
NperSolution::new(periodic_rate, num_periods, pmt, pv, fv)
}
#[derive(Debug)]
pub struct NperSolution {
pub periodic_rate: f64,
pub periods: f64,
pub payment: f64,
pub present_value_total: f64,
pub future_value_total: f64,
pub formula: String,
}
impl NperSolution {
pub fn new(periodic_rate: f64, periods: f64, payment: f64, present_value_total: f64, future_value_total: f64) -> Self {
let formula = format!("LOG10(({} - {}*{})/({} + {}*{})) / LOG10(1 + {})", payment, future_value_total, periodic_rate, payment, present_value_total, periodic_rate, periodic_rate);
Self {
periodic_rate,
periods,
payment,
present_value_total,
future_value_total,
formula,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
#[test]
fn test_nper_1() {
assert_eq!(round_6(27.7879559), round_6(nper(0.034, -500, 1000, 20_000)));
assert_eq!(round_6(59.76100743), round_6(nper(0.034, -50, 1000, 2_000)));
assert_eq!(round_6(25.68169193), round_6(nper(0.034, -50, 0, 2_000)));
assert_eq!(round_6(80.18661533), round_6(nper(0.034, -5, 0, 2_000)));
assert_eq!(round_6(106.3368288), round_6(nper(0.034, -200, 0, 200_000)));
}
#[should_panic]
#[test]
fn test_nper_2() {
nper(1_f64/0_f64, -500, 1000, 20_000);
nper(0.034, -1_f64/0_f64, 1000, 20_000);
nper(0.034, -500, 1_f64/0_f64, 20_000);
nper(0.034, -500, 0, 1_f64/0_f64);
}
#[should_panic]
#[test]
fn test_nper_3() {
nper(1_f64/0_f64, 500, 1000, 20_000);
}
#[should_panic]
#[test]
fn test_nper_4() {
nper(1_f64/0_f64, 0, 1000, 20_000);
}
#[should_panic]
#[test]
fn test_nper_5() {
nper(1_f64/0_f64, -0, 1000, 20_000);
}
}