Skip to content

Commit 7e9ca5d

Browse files
committed
faster pow
1 parent 090c169 commit 7e9ca5d

File tree

3 files changed

+30
-11
lines changed

3 files changed

+30
-11
lines changed

src/core/periodic.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,11 @@ pub fn npv(rate: f64, values: &[f64], start_from_zero: Option<bool>) -> f64 {
427427
}
428428

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

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

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

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

457-
if is_good_rate(rate) {
460+
if utils::is_a_good_rate(rate, f) {
458461
return Ok(rate);
459462
}
460463

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

463-
if is_good_rate(rate) {
466+
if utils::is_a_good_rate(rate, f) {
464467
return Ok(rate);
465468
}
466469

src/core/scheduled/xirr.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use super::{year_fraction, DayCount};
22
use crate::core::{
33
models::{validate, DateLike, InvalidPaymentsError},
44
optimize::{brentq, newton_raphson},
5+
utils::{self, fast_pow},
56
};
67

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

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

29-
if is_good_rate(rate) {
29+
if utils::is_a_good_rate(rate, f) {
3030
return Ok(rate);
3131
}
3232

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

35-
if is_good_rate(rate) {
35+
if utils::is_a_good_rate(rate, f) {
3636
return Ok(rate);
3737
}
3838

3939
let mut step = 0.01;
4040
let mut guess = -0.99999999999999;
4141
while guess < 1.0 {
4242
let rate = newton_raphson(guess, &f, &df);
43-
if is_good_rate(rate) {
43+
if utils::is_a_good_rate(rate, f) {
4444
return Ok(rate);
4545
}
4646
guess += step;
@@ -86,15 +86,15 @@ fn day_count_factor(dates: &[DateLike], day_count: Option<DayCount>) -> Vec<f64>
8686

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

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

100100
#[cfg(test)]

src/core/utils.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
pub(crate) fn fast_pow(a: f64, b: f64) -> f64 {
2+
// works only if a is positive
3+
(a.log2() * b).exp2()
4+
}
5+
16
pub(crate) fn scale(values: &[f64], factor: f64) -> Vec<f64> {
27
values.iter().map(|v| v * factor).collect()
38
}
@@ -12,7 +17,11 @@ pub(crate) fn pairwise_mul(a: &[f64], b: &[f64]) -> Vec<f64> {
1217

1318
pub(crate) fn series_signum(a: &[f64]) -> f64 {
1419
// returns -1. if any item is negative, otherwise +1.
15-
if a.iter().any(|x| x.is_sign_negative()) { -1. } else { 1. }
20+
if a.iter().any(|x| x.is_sign_negative()) {
21+
-1.
22+
} else {
23+
1.
24+
}
1625
}
1726

1827
pub(crate) fn sum_negatives_positives(values: &[f64]) -> (f64, f64) {
@@ -24,3 +33,10 @@ pub(crate) fn sum_negatives_positives(values: &[f64]) -> (f64, f64) {
2433
}
2534
})
2635
}
36+
37+
pub(crate) fn is_a_good_rate<F>(rate: f64, f: F) -> bool
38+
where
39+
F: Fn(f64) -> f64,
40+
{
41+
rate.is_finite() && f(rate).abs() < 1e-3
42+
}

0 commit comments

Comments
 (0)