finance_solution/cashflow/
net_present_value.rs

1//! **Net Present Value calculations**. Given a series of cashflows, an initial investment (the cashflow at time0), a number of periods such as years, and fixed
2//! or varying interest rates, what is the net value of the series of cashflows right now?
3//!
4//! For most common usages, we recommend the [`net_present_value_schedule_solution`](./fn.net_present_value_schedule_solution.html) function, to provide a better debugging experience and additional features.
5//! This function allows you to provide varying cashflows and/or varying rates.
6//! 
7//! For very simple NPV calculations involving a constant cashflow and constant rate, the [`net_present_value_solution`](./fn.net_present_value_solution.html) function can be used.
8//! 
9//! ## Examples
10//! 
11//! **Simple Usage:**
12//! ```
13//! # use finance_solution::net_present_value_solution;
14//! let (rate, periods, initial_investment, cashflow) = (0.034, 3, -1000, 400);
15//! let npv = net_present_value_solution(rate, periods, initial_investment, cashflow);
16//! dbg!(npv.print_table());
17//! ```
18//! > outputs to terminal:
19//! ```text
20//! period   rate   present_value  future_value  investment_value 
21//! ------  ------  -------------  ------------  ---------------- 
22//! 0       0.0000    -1_000.0000   -1_000.0000       -1_000.0000 
23//! 1       0.0340       386.8472      400.0000         -613.1528 
24//! 2       0.0340       374.1269      400.0000         -239.0259 
25//! 3       0.0340       361.8248      400.0000          122.7989 
26//! ```
27//! 
28//! **More typical usage (varying cashflows):**
29//! ```
30//! # use finance_solution::net_present_value_schedule_solution;
31//! let rates = vec![0.034, 0.034, 0.034];
32//! let cashflows = vec![-1000, 300, 400, 500];
33//! let npv = net_present_value_schedule_solution(&rates, &cashflows);
34//! dbg!(npv.print_table());
35//! ```
36//! > outputs to terminal:
37//! ```text
38//! period   rate   present_value  future_value  investment_value 
39//! ------  ------  -------------  ------------  ---------------- 
40//! 0       0.0000    -1_000.0000   -1_000.0000       -1_000.0000 
41//! 1       0.0340       290.1354      300.0000         -709.8646 
42//! 2       0.0340       374.1269      400.0000         -335.7377 
43//! 3       0.0340       452.2810      500.0000          116.5433 
44//! ```
45
46
47// use crate::cashflow::*;
48// Needed for the Rustdoc comments.
49#[allow(unused_imports)]
50use crate::present_value_annuity::present_value_annuity;
51use crate::*;
52
53use std::ops::Deref;
54
55/// Returns the net present value of a future series of constant cashflows and constant rate, subtracting the initial investment cost. Returns f64.
56///
57/// Related functions:
58/// * To calculate a net present value with a varying rate or varying cashflow or both, use [`net_present_value_schedule`].
59///
60/// The net present value annuity formula is:
61///
62/// npv = initial_investment + sum( cashflow / (1 + rate)<sup>period</sup> )
63/// 
64/// or
65/// 
66/// npv = initial_investment +  cashflow * ((1. - (1. / (1. + rate)).powf(periods)) / rate)
67///
68/// # Arguments
69/// * `rate` - The rate at which the investment grows or shrinks per period,
70/// expressed as a floating point number. For instance 0.05 would mean 5%. Often appears as
71/// `r` or `i` in formulas.
72/// * `periods` - The number of periods such as quarters or years. Often appears as `n` or `t`.
73/// * `cashflow` - The value of the constant cashflow (aka payment).
74/// * `initial investment` - The value of the initial investment (should be negative, or 0).
75///
76/// # Panics
77/// The call will fail if `initial_investment` is positive. This value should always be negative, and cashflows be positive, or the reverse, because these monies are going opposite directions.
78///
79/// # Examples
80/// Net Present Value of a series of -$1000 investment which will payback $500 yearly for 10 years.
81/// ```
82/// use finance_solution::*;
83/// let (rate, periods, initial_investment, cashflow) = (0.034, 10, -1000, 500);
84///
85/// // Find the present value of this scenario.
86/// let net_present_value = net_present_value(rate, periods, initial_investment, cashflow);
87///
88/// // Confirm that the present value is correct to four decimal places (one hundredth of a cent).
89/// assert_approx_equal!(3179.3410288, net_present_value);
90/// ```
91pub fn net_present_value<C, I>(rate: f64, periods: u32, initial_investment: I, cashflow: C) -> f64 
92where I: Into<f64> + Copy, C: Into<f64> + Copy
93{
94    let annuity = cashflow.into();
95    let ii = initial_investment.into();
96    let pv_cashflow = annuity * ((1. - (1. / (1. + rate)).powf(periods as f64)) / rate);
97    let npv = ii + pv_cashflow;
98    npv
99}
100
101/// Returns the net present value of a future series of constant cashflows and constant rate, subtracting the initial investment cost. Returns a solution struct with additional features..
102///
103/// Related functions:
104/// * To calculate a net present value with a varying rate or varying cashflow or both, use [`net_present_value_schedule`].
105///
106/// The net present value annuity formula is:
107///
108/// npv = initial_investment + sum( cashflow / (1 + rate)<sup>period</sup> )
109/// 
110/// or
111/// 
112/// npv = initial_investment +  cashflow * ((1. - (1. / (1. + rate)).powf(periods)) / rate)
113///
114/// # Arguments
115/// * `rate` - The rate at which the investment grows or shrinks per period,
116/// expressed as a floating point number. For instance 0.05 would mean 5%. Often appears as
117/// `r` or `i` in formulas.
118/// * `periods` - The number of periods such as quarters or years. Often appears as `n` or `t`.
119/// * `cashflow` - The value of the constant cashflow (aka payment).
120/// * `initial investment` - The value of the initial investment (should be negative, or 0).
121pub fn net_present_value_solution<C, I>(rate: f64, periods: u32, initial_investment: I, cashflow: C) -> NpvSolution 
122where I: Into<f64> + Copy, C: Into<f64> + Copy
123{
124    let annuity = cashflow.into();
125    let ii = initial_investment.into();
126    let rates = repeating_vec![rate, periods];
127    let mut cashflows = repeating_vec![annuity, periods];
128    cashflows.insert(0, ii);
129    net_present_value_schedule_solution(&rates, &cashflows)
130
131}
132
133/// Returns the net present value of a schedule of rates and cashflows (can be varying), subtracting the initial investment cost. Returns f64.
134///
135/// # Examples
136/// Net Present Value of a series of -$1000 investment which will payback $500 yearly for 10 years.
137/// ```
138/// use finance_solution::*;
139/// let (rates, cashflows) = (vec![0.034, 0.089, 0.055], vec![-1000, 200, 300, 500]);
140///
141/// // Find the present value of this scenario.
142/// let net_present_value = net_present_value_schedule(&rates, &cashflows);
143///
144/// // Confirm that the present value is correct to four decimal places (one hundredth of a cent).
145/// assert_approx_equal!(-127.8016238, net_present_value);
146/// 
147/// // present_value(0.034, 1, 200): $193.42
148/// // present_value(0.089, 2, 300): $252.97
149/// // present_value(0.055, 3, 500): $425.81
150/// // initial investment:          -$1000
151/// // sum of the above:            -$127.80 (net present value)
152/// 
153/// ```
154pub fn net_present_value_schedule<C>(rates: &[f64], cashflows: &[C]) -> f64 
155where C: Into<f64> + Copy
156{
157    let (periods, r, c, initial_investment) = check_schedule(rates, cashflows);
158    // let mut cflows = vec![];
159    // for i in 0..cashflows.len() {
160    //     cflows.push(cashflows[i].into());
161    // }
162    // let cashflows = &cflows;
163    // assert!(cashflows[0] <= 0.0, "The initial investment (cashflows[0]) should be negative or zero");
164    // assert!(cashflows.len() >= 2, "Must provide at least 2 values in cashflows, the initial investment at the 0 position and the following cashflows, or a single cashflow representing a repeating constant cashflow.");
165    // assert!(rates.len() >= 1, "Must provide at least 1 rate.");
166    // let rate_length = rates.len();
167    // let cashflow_length = cashflows.len();
168    // let initial_investment = cashflows[0];
169    // let mut cashflow_vec = vec![initial_investment];
170    // let mut rate_vec = vec![];
171    // let periods: u32;
172    // let r: &[f64];
173    // let c: &[f64];
174
175    // if rate_length == 1 && cashflow_length == 2 {
176    //     r = &rates;
177    //     c = &cashflows;
178    //     periods = 1_u32;
179    // } else if rate_length > 1 && cashflow_length > 2 {
180    //     r = &rates;
181    //     c = &cashflows;
182    //     periods = rate_length as u32;
183    // } else if rate_length > 1 && cashflow_length == 2 {
184    //     r = &rates;
185    //     periods = rate_length as u32;
186    //     for _i in 0..periods {
187    //         cashflow_vec.push(cashflows[1])
188    //     }
189    //     c = &cashflow_vec;
190    // } else if rate_length == 1 && cashflow_length > 2 {
191    //     c = &cashflows;
192    //     periods = cashflow_length as u32 - 1;
193    //     for _i in 0..periods {
194    //         rate_vec.push(rates[0])
195    //     }
196    //     r = &rate_vec;
197    // } else {
198    //     // revise this panic message
199    //     panic!("At least rates or cashflows for net_present_value_schedule must provide the full series of inputs. Only one input can be a shorthand expression of a repeating input. If both are repeating constant inputs, use the net_present_value function.");
200    // }
201
202    let mut pv_accumulator = 0_f64;
203    for i in 0..periods { 
204        let present_value = -present_value(r[i as usize], (i+1) as u32, c[i as usize + 1], false);
205        pv_accumulator = pv_accumulator + present_value;
206    }
207    let npv = initial_investment + pv_accumulator;
208    npv
209}
210
211fn check_schedule<C>(rates:&[f64], cashflows: &[C]) -> (u32, Vec<f64>, Vec<f64>, f64) 
212where C: Into<f64> + Copy
213{
214    let mut cflows = vec![];
215    for i in 0..cashflows.len() {
216        cflows.push(cashflows[i].into());
217    }
218    let cashflows = &cflows;
219    assert!(cashflows[0] <= 0.0, "The initial investment (cashflows[0]) should be negative or zero");
220    assert!(cashflows.len() >= 2, "Must provide at least 2 values in cashflows, the initial investment at the 0 position and the following cashflows, or a single cashflow representing a repeating constant cashflow.");
221    assert!(rates.len() >= 1, "Must provide at least 1 rate.");
222    let rate_length = rates.len();
223    let cashflow_length = cashflows.len();
224    let initial_investment = cashflows[0];
225    let mut cashflow_vec = vec![initial_investment];
226    let mut rate_vec = vec![];
227    let periods: u32;
228    let r: &[f64];
229    let c: &[f64];
230
231    if rate_length == 1 && cashflow_length == 2 {
232        r = &rates;
233        c = &cashflows;
234        periods = 1_u32;
235    } else if rate_length > 1 && cashflow_length > 2 {
236        r = &rates;
237        c = &cashflows;
238        periods = rate_length as u32;
239    } else if rate_length > 1 && cashflow_length == 2 {
240        r = &rates;
241        periods = rate_length as u32;
242        for _i in 0..periods {
243            cashflow_vec.push(cashflows[1])
244        }
245        c = &cashflow_vec;
246    } else if rate_length == 1 && cashflow_length > 2 {
247        c = &cashflows;
248        periods = cashflow_length as u32 - 1;
249        for _i in 0..periods {
250            rate_vec.push(rates[0])
251        }
252        r = &rate_vec;
253    } else {
254        // revise this panic message
255        panic!("At least rates or cashflows for net_present_value_schedule must provide the full series of inputs. Only one input can be a shorthand expression of a repeating input. If both are repeating constant inputs, use the net_present_value function.");
256    }
257    (periods, r.to_vec(), c.to_vec(), initial_investment)
258}
259
260
261/// Returns the net present value of a schedule of rates and cashflows (can be varying), subtracting the initial investment cost. 
262/// Returns a custom solution struct with detailed information and additional functionality.
263/// 
264/// # Example
265/// ```
266/// let rates = vec![0.034, 0.034, 0.034];
267/// let cashflows = vec![-1000, 300, 400, 500];
268/// let npv = finance_solution::net_present_value_schedule_solution(&rates, &cashflows);
269/// dbg!(npv.print_table());
270/// ```
271/// > outputs to terminal:
272/// ```text
273/// period   rate   present_value  future_value  investment_value 
274/// ------  ------  -------------  ------------  ---------------- 
275/// 0       0.0000    -1_000.0000   -1_000.0000       -1_000.0000 
276/// 1       0.0340       290.1354      300.0000         -709.8646 
277/// 2       0.0340       374.1269      400.0000         -335.7377 
278/// 3       0.0340       452.2810      500.0000          116.5433 
279/// ```
280pub fn net_present_value_schedule_solution<C>(rates: &[f64], cashflows: &[C]) -> NpvSolution 
281where C: Into<f64> + Copy
282{
283    let (periods, rates, cashflows, initial_investment) = check_schedule(rates, cashflows);
284
285    let mut sum_accumulator = 0_f64;
286    let mut pv_accumulator = 0_f64;
287    for i in 0..periods { 
288        let present_value = -present_value(rates[i as usize], (i+1) as u32, cashflows[i as usize + 1], false);
289        pv_accumulator = pv_accumulator + present_value;
290        sum_accumulator = sum_accumulator + cashflows[i as usize + 1];
291    }
292    let sum_of_cashflows = sum_accumulator;
293    let sum_of_discounted_cashflows = pv_accumulator;
294    let net_present_value = initial_investment + pv_accumulator;
295
296    NpvSolution::new(rates, periods, initial_investment, cashflows, sum_of_cashflows, sum_of_discounted_cashflows, net_present_value)
297}
298
299/// The custom solution information of a NPV scenario. 
300/// The struct values are immutable by the user of the library.
301#[derive(Debug)]
302pub struct NpvSolution {
303    rates: Vec<f64>,
304    periods: u32,
305    cashflows: Vec<f64>,
306    initial_investment: f64,
307    sum_of_cashflows: f64,
308    sum_of_discounted_cashflows: f64,
309    net_present_value: f64,
310}
311impl NpvSolution {
312    /// Create a new instance of the struct
313    pub fn new(
314        rates: Vec<f64>, 
315        periods: u32, 
316        initial_investment: f64, 
317        cashflows: Vec<f64>, 
318        sum_of_cashflows: f64, 
319        sum_of_discounted_cashflows:f64,
320        net_present_value:f64) -> Self {
321            Self {
322                rates, 
323                periods, 
324                initial_investment, 
325                cashflows, 
326                sum_of_cashflows, 
327                sum_of_discounted_cashflows,
328                net_present_value,
329            }
330    }
331
332    pub fn series(&self) -> NpvSeries {
333         net_present_value_schedule_series(self)
334    }
335
336    /// Call `rate_avg` on a NpvSolution to get the simple average rate of a schedule;
337    pub fn rate_avg(&self) -> f64 {
338        let mut rate_accumulator = 0_f64;
339        for r in &self.rates {
340            rate_accumulator = rate_accumulator + r;
341        }
342        rate_accumulator / self.periods as f64
343    }
344
345    /// Returns the rate schedule
346    pub fn rates(&self) -> &[f64] {
347        &self.rates
348    }
349    /// Returns the number of periods as u32.
350    pub fn periods(&self) -> u32 {
351        self.periods
352    }
353    /// Returns the initial investment as f64.
354    pub fn initial_investment(&self) -> f64 {
355        self.initial_investment
356    }
357    /// Returns a Vec<f64> of the cashflows.
358    pub fn cashflows(&self) -> &[f64] {
359        &self.cashflows
360    }
361    /// Returns the sum of the cashflows at their future value.
362    pub fn sum_of_cashflows(&self) -> f64 {
363        self.sum_of_cashflows
364    }
365    /// Returns the sum of the cashflows at their present value.
366    pub fn sum_of_discounted_cashflows(&self) -> f64 {
367        self.sum_of_discounted_cashflows
368    }
369    /// Returns the net present value as f64.
370    pub fn net_present_value(&self) -> f64 {
371        self.net_present_value
372    }
373    /// Alias for net_present_value()
374    pub fn npv(&self) -> f64 {
375        self.net_present_value
376    }
377
378    /// Pretty-print a table of the calculations at each period for visual analysis. 
379    pub fn print_table(&self) {
380        self.series().print_table();
381    }
382
383    /// Pretty-print a table of the calculations at each period for visual analysis, and provide a Locale for monetary formatting and preferred decimal precision.
384    pub fn print_table_locale(&self, locale: &num_format::Locale, precision: usize) {
385        self.series().print_table_locale(locale, precision);
386    }
387
388    /// Return the max discounted cashflow (present value of the cashflow)
389    pub fn max_discounted_cashflow(&self) -> f64 {
390        self.series().max_discounted_cashflow() 
391    }
392    /// Return the min discounted cashflow (present value of the cashflow)
393    pub fn min_discounted_cashflow(&self) -> f64 {
394        self.series().min_discounted_cashflow() 
395    }
396
397}
398
399#[derive(Debug)]
400pub struct NpvSeries(Vec<NpvPeriod>);
401impl NpvSeries {
402    pub(crate) fn new(series: Vec<NpvPeriod>) -> Self {
403        Self {
404            0: series,
405        }
406    }
407    pub fn filter<P>(&self, predicate: P) -> Self
408        where P: Fn(&&NpvPeriod) -> bool
409    {
410        Self {
411            0: self.iter().filter(|x| predicate(x)).map(|x| x.clone()).collect()
412        }
413    }
414
415    pub fn print_table(&self) {
416        self.print_table_locale_opt(None, None);
417    }
418
419    pub fn print_table_locale(&self, locale: &num_format::Locale, precision: usize) {
420        self.print_table_locale_opt(Some(locale), Some(precision));
421    }
422
423    fn print_table_locale_opt(&self, locale: Option<&num_format::Locale>, precision: Option<usize>) {
424        let columns = columns_with_strings(&[("period", "i", true), ("rate", "f", true), ("present_value", "f", true), ("future_value", "f", true), ("investment_value", "f", true)]);
425        let data = self.iter()
426            .map(|entry| vec![entry.period.to_string(), entry.rate.to_string(), entry.present_value.to_string(), entry.future_value.to_string(), entry.investment_value.to_string()])
427            .collect::<Vec<_>>();
428        print_table_locale_opt(&columns, data, locale, precision);
429    }
430
431    pub fn print_ab_comparison(&self, other: &NpvSeries) {
432        self.print_ab_comparison_locale_opt(other, None, None);
433    }
434
435    pub fn print_ab_comparison_locale(&self, other: &NpvSeries, locale: &num_format::Locale, precision: usize) {
436        self.print_ab_comparison_locale_opt(other, Some(locale), Some(precision));
437    }
438
439    fn print_ab_comparison_locale_opt (&self, other: &NpvSeries, locale: Option<&num_format::Locale>, precision: Option<usize>) {
440        let columns = columns_with_strings(&[
441            ("period", "i", true),
442            ("rate_a", "f", true), ("rate_b", "f", true),
443            ("present_value_a", "f", true), ("present_value_b", "f", true),
444            ("future_value_a", "f", true), ("future_value_b", "f", true),
445            ("investment_value_a", "f", true), ("investment_value_b", "f", true)]);
446        let mut data = vec![];
447        let rows = max(self.len(), other.len());
448        for row_index in 0..rows {
449            data.push(vec![
450                row_index.to_string(),
451                self.get(row_index).map_or("".to_string(), |x| x.rate.to_string()),
452                other.get(row_index).map_or("".to_string(), |x| x.rate.to_string()),
453                self.get(row_index).map_or("".to_string(), |x| x.present_value.to_string()),
454                other.get(row_index).map_or("".to_string(), |x| x.present_value.to_string()),
455                self.get(row_index).map_or("".to_string(), |x| x.future_value.to_string()),
456                other.get(row_index).map_or("".to_string(), |x| x.future_value.to_string()),
457                self.get(row_index).map_or("".to_string(), |x| x.investment_value.to_string()),
458                other.get(row_index).map_or("".to_string(), |x| x.investment_value.to_string()),
459            ]);
460        }
461        print_table_locale_opt(&columns, data, locale, precision);
462    }
463
464    /// Return the max discounted cashflow (present value of the cashflow)
465    pub fn max_discounted_cashflow(&self) -> f64 {
466        assert!(self.len() > 1); 
467        self.iter().skip(1).fold(std::f64::MIN, |acc, x| acc.max(x.present_value()))
468    }
469
470    /// Return the min discounted cashflow (present value of the cashflow)
471    pub fn min_discounted_cashflow(&self) -> f64 {
472        assert!(self.len() > 1); 
473        self.iter().skip(1).fold(std::f64::MAX, |acc, x| acc.min(x.present_value()))
474    }
475}
476impl Deref for NpvSeries {
477    type Target = Vec<NpvPeriod>;
478
479    fn deref(&self) -> &Self::Target {
480        &self.0
481    }
482}
483
484#[derive(Clone, Debug)]
485pub struct NpvPeriod {
486    period: u32,
487    rate: f64,
488    present_value: f64,
489    future_value: f64,
490    investment_value: f64,
491    formula: String,
492    formula_symbolic: String,
493}
494impl NpvPeriod {
495    pub fn new(
496        period: u32,
497        rate: f64,
498        present_value: f64,
499        future_value: f64,
500        investment_value: f64,
501        formula: String,
502        formula_symbolic: String,
503    ) -> Self {
504        Self {
505            period,
506            rate,
507            present_value,
508            future_value,
509            investment_value,
510            formula,
511            formula_symbolic,
512        }
513    }
514    /// Returns the period number. The first real period is 1 but there's also a period 0 which
515    /// which shows the starting conditions.
516    pub fn period(&self) -> u32 {
517        self.period
518    }
519
520    /// Returns the periodic rate for the current period. If the containing struct is a
521    /// [`TvmSolution`] every period will have the same rate. If it's a [`TvmSchedule`] each period
522    /// may have a different rate.
523    pub fn rate(&self) -> f64 {
524        self.rate
525    }
526
527    /// Returns the present value of the cashflow.
528    pub fn present_value(&self) -> f64 {
529        self.present_value
530    }
531
532    /// Returns the future value of the cashflow.
533    pub fn future_value(&self) -> f64 {
534        self.future_value
535    }
536    
537    /// Returns the investment value of the Npv scenario at the time of the current period.
538    pub fn investment_value(&self) -> f64 {
539        self.investment_value
540    }
541
542    /// Returns a text version of the formula used to calculate the value for the current period.
543    /// The formula includes the actual values rather than variable names. For the formula with
544    /// variables such as pv for present value call `formula_symbolic`.
545    pub fn formula(&self) -> &str {
546        &self.formula
547    }
548
549    /// Returns a text version of the formula used to calculate the value for the current period.
550    /// The formula includes variables such as r for the rate. For the formula with actual values
551    /// rather than variables call `formula`.
552    pub fn formula_symbolic(&self) -> &str {
553        &self.formula_symbolic
554    }
555}
556
557pub(crate) fn net_present_value_schedule_series(schedule: &NpvSolution) -> NpvSeries {
558    let mut series = vec![];
559  
560    let periods = schedule.periods();
561    let mut investment_value = 0_f64;
562
563    for period in 0..=periods {         
564        let rate = if period == 0 {
565            0.0
566        } else {
567            schedule.rates()[(period-1) as usize]
568        };
569        let future_value = schedule.cashflows[period as usize];
570        let present_value = schedule.cashflows[period as usize] / (1. + rate).powf(period as f64);
571        assert!(present_value.is_finite());
572        investment_value += present_value;
573        let formula = format!("{:.4} = {:.4} / (1 + {:.6})^{}", present_value, future_value, rate, period);
574        let formula_symbolic = "present_value = fv / (1 + rate)^periods".to_string();
575        series.push(NpvPeriod::new(period, rate, present_value, future_value, investment_value, formula, formula_symbolic))
576    }
577    NpvSeries::new(series)
578}
579
580#[cfg(test)]
581mod tests {
582    use super::*;
583    //use crate::*;
584
585    #[test]
586    fn test_net_present_value_1() {
587        let rate = 0.034;
588        let periods = 10;
589        let ii = -1000;
590        let cf = 500;
591        let npv = net_present_value(rate, periods, ii, cf);
592        assert_approx_equal!(3179.3410288, npv);
593    }
594
595    #[test]
596    fn test_net_present_value_2() {
597        let rate = 0.034;
598        let periods = 400;
599        let ii = -1000;
600        let cf = 500;
601        let npv = net_present_value(rate, periods, ii, cf);
602        assert_eq!(13_705.85948, (100_000. * npv).round() / 100_000.);
603    }
604
605    #[test]
606    fn test_net_present_value_3() {
607        let rates = vec![0.034,0.089,0.055];
608        let cashflows = vec![-1000,200,300,500];
609        let npv = net_present_value_schedule(&rates, &cashflows);
610        assert_eq!(-127.80162, (100_000. * npv).round() / 100_000.);
611    }
612
613    #[test]
614    fn test_net_present_value_4() {
615        let rates = vec![0.034,0.089,0.055];
616        let cashflows = vec![-1000,200,300,500];
617        let npv = net_present_value_schedule_solution(&rates, &cashflows);
618        assert_eq!(-127.80162, (100_000. * npv.npv()).round() / 100_000.);
619    }
620
621    #[test]
622    fn test_net_present_value_5() {
623        // wildcard use case: positive and negatives
624        let rates = vec![0.034,-0.0989,0.055,-0.02];
625        let cashflows = vec![-1000,1000,500,-250,-250];
626        let npv = net_present_value_schedule_solution(&rates, &cashflows);
627        assert_eq!(98.950922304, (10_000_000_000. * npv.npv()).round() / 10_000_000_000.);
628    }
629}