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, 1, 256) {
return err;
}
let (cfs, guess) = match collect_cashflows_with_guess(args) {
Ok(pair) => pair,
Err(e) => return e,
};
if cfs.len() < 2 {
return Value::Error(ErrorKind::NA);
}
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 = guess;
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 collect_cashflows_with_guess(args: &[Value]) -> Result<(Vec<f64>, f64), Value> {
if let Value::Array(items) = &args[0] {
let cfs = flatten_values(items.clone())?;
let guess = if args.len() > 1 {
to_number(args[1].clone())?
} else {
0.1
};
return Ok((cfs, guess));
}
let mut cfs = Vec::with_capacity(args.len());
for arg in args {
cfs.push(to_number(arg.clone())?);
}
Ok((cfs, 0.1))
}
fn flatten_values(items: Vec<Value>) -> Result<Vec<f64>, Value> {
let mut out = Vec::new();
for v in items {
match v {
Value::Array(inner) => {
let sub = flatten_values(inner)?;
out.extend(sub);
}
other => out.push(to_number(other)?),
}
}
Ok(out)
}
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;