Skip to content

Commit

Permalink
faster pow
Browse files Browse the repository at this point in the history
  • Loading branch information
Anexen committed Jan 26, 2024
1 parent 090c169 commit 7e9ca5d
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 11 deletions.
11 changes: 7 additions & 4 deletions src/core/periodic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,11 @@ pub fn npv(rate: f64, values: &[f64], start_from_zero: Option<bool>) -> f64 {
}

fn npv_deriv(rate: f64, values: &[f64]) -> f64 {
values.iter().enumerate().map(|(i, v)| -(i as f64) * v / (rate + 1.0).powf(i as f64 + 1.)).sum()
values
.iter()
.enumerate()
.map(|(i, v)| -(i as f64) * v * utils::fast_pow(rate + 1.0, -(i as f64 + 1.0)))
.sum()
}

pub fn irr(values: &[f64], guess: Option<f64>) -> Result<f64, InvalidPaymentsError> {
Expand All @@ -442,7 +446,6 @@ pub fn irr(values: &[f64], guess: Option<f64>) -> Result<f64, InvalidPaymentsErr
self::npv(rate, values, Some(true))
};
let df = |rate| self::npv_deriv(rate, values);
let is_good_rate = |rate: f64| rate.is_finite() && f(rate).abs() < 1e-3;

let guess = match guess {
Some(g) => g,
Expand All @@ -454,13 +457,13 @@ pub fn irr(values: &[f64], guess: Option<f64>) -> Result<f64, InvalidPaymentsErr

let rate = newton_raphson(guess, &f, &df);

if is_good_rate(rate) {
if utils::is_a_good_rate(rate, f) {
return Ok(rate);
}

let rate = brentq(&f, -0.999999999999999, 100., 100);

if is_good_rate(rate) {
if utils::is_a_good_rate(rate, f) {
return Ok(rate);
}

Expand Down
12 changes: 6 additions & 6 deletions src/core/scheduled/xirr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::{year_fraction, DayCount};
use crate::core::{
models::{validate, DateLike, InvalidPaymentsError},
optimize::{brentq, newton_raphson},
utils::{self, fast_pow},
};

pub fn xirr(
Expand All @@ -22,25 +23,24 @@ pub fn xirr(
xnpv_result(amounts, deltas, rate)
};
let df = |rate| xnpv_result_deriv(amounts, deltas, rate);
let is_good_rate = |rate: f64| rate.is_finite() && f(rate).abs() < 1e-3;

let rate = newton_raphson(guess.unwrap_or(0.1), &f, &df);

if is_good_rate(rate) {
if utils::is_a_good_rate(rate, f) {
return Ok(rate);
}

let rate = brentq(&f, -0.999999999999999, 100., 100);

if is_good_rate(rate) {
if utils::is_a_good_rate(rate, f) {
return Ok(rate);
}

let mut step = 0.01;
let mut guess = -0.99999999999999;
while guess < 1.0 {
let rate = newton_raphson(guess, &f, &df);
if is_good_rate(rate) {
if utils::is_a_good_rate(rate, f) {
return Ok(rate);
}
guess += step;
Expand Down Expand Up @@ -86,15 +86,15 @@ fn day_count_factor(dates: &[DateLike], day_count: Option<DayCount>) -> Vec<f64>

// \sum_{i=1}^n \frac{P_i}{(1 + rate)^{(d_i - d_0)/365}}
fn xnpv_result(payments: &[f64], deltas: &[f64], rate: f64) -> f64 {
payments.iter().zip(deltas).map(|(p, &e)| p * (1.0 + rate).powf(-e)).sum()
payments.iter().zip(deltas).map(|(p, &e)| p * fast_pow(1.0 + rate, -e)).sum()
}

// XNPV first derivative
// \sum_{i=1}^n P_i * (d_0 - d_i) / 365 * (1 + rate)^{((d_0 - d_i)/365 - 1)}}
// simplify in order to reuse cached deltas (d_i - d_0)/365
// \sum_{i=1}^n \frac{P_i * -(d_i - d_0) / 365}{(1 + rate)^{((d_i - d_0)/365 + 1)}}
fn xnpv_result_deriv(payments: &[f64], deltas: &[f64], rate: f64) -> f64 {
payments.iter().zip(deltas).map(|(p, e)| p * -e * (1.0 + rate).powf(-e - 1.0)).sum()
payments.iter().zip(deltas).map(|(p, e)| p * -e * fast_pow(1.0 + rate, -e - 1.0)).sum()
}

#[cfg(test)]
Expand Down
18 changes: 17 additions & 1 deletion src/core/utils.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
pub(crate) fn fast_pow(a: f64, b: f64) -> f64 {
// works only if a is positive
(a.log2() * b).exp2()
}

pub(crate) fn scale(values: &[f64], factor: f64) -> Vec<f64> {
values.iter().map(|v| v * factor).collect()
}
Expand All @@ -12,7 +17,11 @@ pub(crate) fn pairwise_mul(a: &[f64], b: &[f64]) -> Vec<f64> {

pub(crate) fn series_signum(a: &[f64]) -> f64 {
// returns -1. if any item is negative, otherwise +1.
if a.iter().any(|x| x.is_sign_negative()) { -1. } else { 1. }
if a.iter().any(|x| x.is_sign_negative()) {
-1.
} else {
1.
}
}

pub(crate) fn sum_negatives_positives(values: &[f64]) -> (f64, f64) {
Expand All @@ -24,3 +33,10 @@ pub(crate) fn sum_negatives_positives(values: &[f64]) -> (f64, f64) {
}
})
}

pub(crate) fn is_a_good_rate<F>(rate: f64, f: F) -> bool
where
F: Fn(f64) -> f64,
{
rate.is_finite() && f(rate).abs() < 1e-3
}

0 comments on commit 7e9ca5d

Please sign in to comment.