|
| 1 | +/// In economics and finance, present value (PV), also known as present discounted value, |
| 2 | +/// is the value of an expected income stream determined as of the date of valuation. |
| 3 | +/// |
| 4 | +/// -> Uncyclopedia reference: https://en.wikipedia.org/wiki/Present_value |
| 5 | +
|
| 6 | +#[derive(PartialEq, Eq, Debug)] |
| 7 | +pub enum PresentValueError { |
| 8 | + NegetiveDiscount, |
| 9 | + EmptyCashFlow, |
| 10 | +} |
| 11 | + |
| 12 | +pub fn present_value(discount_rate: f64, cash_flows: Vec<f64>) -> Result<f64, PresentValueError> { |
| 13 | + if discount_rate < 0.0 { |
| 14 | + return Err(PresentValueError::NegetiveDiscount); |
| 15 | + } |
| 16 | + if cash_flows.is_empty() { |
| 17 | + return Err(PresentValueError::EmptyCashFlow); |
| 18 | + } |
| 19 | + |
| 20 | + let present_value = cash_flows |
| 21 | + .iter() |
| 22 | + .enumerate() |
| 23 | + .map(|(i, &cash_flow)| cash_flow / (1.0 + discount_rate).powi(i as i32)) |
| 24 | + .sum::<f64>(); |
| 25 | + |
| 26 | + Ok(round(present_value)) |
| 27 | +} |
| 28 | + |
| 29 | +fn round(value: f64) -> f64 { |
| 30 | + (value * 100.0).round() / 100.0 |
| 31 | +} |
| 32 | + |
| 33 | +#[cfg(test)] |
| 34 | +mod tests { |
| 35 | + use super::*; |
| 36 | + |
| 37 | + macro_rules! test_present_value { |
| 38 | + ($($name:ident: $inputs:expr,)*) => { |
| 39 | + $( |
| 40 | + #[test] |
| 41 | + fn $name() { |
| 42 | + let ((discount_rate,cash_flows), expected) = $inputs; |
| 43 | + assert_eq!(present_value(discount_rate,cash_flows).unwrap(), expected); |
| 44 | + } |
| 45 | + )* |
| 46 | + } |
| 47 | + } |
| 48 | + |
| 49 | + macro_rules! test_present_value_Err { |
| 50 | + ($($name:ident: $inputs:expr,)*) => { |
| 51 | + $( |
| 52 | + #[test] |
| 53 | + fn $name() { |
| 54 | + let ((discount_rate,cash_flows), expected) = $inputs; |
| 55 | + assert_eq!(present_value(discount_rate,cash_flows).unwrap_err(), expected); |
| 56 | + } |
| 57 | + )* |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + macro_rules! test_round { |
| 62 | + ($($name:ident: $inputs:expr,)*) => { |
| 63 | + $( |
| 64 | + #[test] |
| 65 | + fn $name() { |
| 66 | + let (input, expected) = $inputs; |
| 67 | + assert_eq!(round(input), expected); |
| 68 | + } |
| 69 | + )* |
| 70 | + } |
| 71 | + } |
| 72 | + |
| 73 | + test_present_value! { |
| 74 | + general_inputs1:((0.13, vec![10.0, 20.70, -293.0, 297.0]),4.69), |
| 75 | + general_inputs2:((0.07, vec![-109129.39, 30923.23, 15098.93, 29734.0, 39.0]),-42739.63), |
| 76 | + general_inputs3:((0.07, vec![109129.39, 30923.23, 15098.93, 29734.0, 39.0]), 175519.15), |
| 77 | + zero_input:((0.0, vec![109129.39, 30923.23, 15098.93, 29734.0, 39.0]), 184924.55), |
| 78 | + |
| 79 | + } |
| 80 | + |
| 81 | + test_present_value_Err! { |
| 82 | + negative_discount_rate:((-1.0, vec![10.0, 20.70, -293.0, 297.0]), PresentValueError::NegetiveDiscount), |
| 83 | + empty_cash_flow:((1.0, vec![]), PresentValueError::EmptyCashFlow), |
| 84 | + |
| 85 | + } |
| 86 | + test_round! { |
| 87 | + test1:(0.55434, 0.55), |
| 88 | + test2:(10.453, 10.45), |
| 89 | + test3:(1111_f64, 1111_f64), |
| 90 | + } |
| 91 | +} |
0 commit comments