use crate::eval::coercion::to_number;
use crate::eval::functions::check_arity;
use crate::types::{ErrorKind, Value};
pub fn irr_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 2, 255) {
return err;
}
let mut cfs = Vec::with_capacity(args.len());
for arg in args {
match to_number(arg.clone()) {
Ok(n) => cfs.push(n),
Err(e) => return e,
}
}
let has_positive = cfs.iter().any(|&n| n > 0.0);
let has_negative = cfs.iter().any(|&n| n < 0.0);
if !has_positive || !has_negative {
return Value::Error(ErrorKind::Num);
}
let mut rate = 0.1_f64;
for _ in 0..100 {
let (npv, dnpv) = npv_and_derivative(&cfs, rate);
if !npv.is_finite() || !dnpv.is_finite() || dnpv == 0.0 {
return Value::Error(ErrorKind::Num);
}
let new_rate = rate - npv / dnpv;
if (new_rate - rate).abs() < 1e-7 {
if !new_rate.is_finite() {
return Value::Error(ErrorKind::Num);
}
return Value::Number(new_rate);
}
rate = new_rate;
}
Value::Error(ErrorKind::Num)
}
fn npv_and_derivative(cfs: &[f64], rate: f64) -> (f64, f64) {
let mut npv = 0.0;
let mut dnpv = 0.0;
for (i, &cf) in cfs.iter().enumerate() {
let t = i as f64;
let denom = (1.0 + rate).powf(t);
npv += cf / denom;
dnpv -= t * cf / ((1.0 + rate).powf(t + 1.0));
}
(npv, dnpv)
}
#[cfg(test)]
mod tests;