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}