rust_finprim/tvm/
fv.rs
1use crate::{ONE, ZERO};
2use rust_decimal::prelude::*;
3
4pub fn fv(rate: Decimal, nper: Decimal, pmt: Decimal, pv: Option<Decimal>, due: Option<bool>) -> Decimal {
38 let pv = pv.unwrap_or(ZERO);
39 let due = due.unwrap_or(false);
40
41 if rate == ZERO {
42 return pmt * nper + pv;
44 }
45
46 let nth_power = (ONE + rate).powd(nper);
47 let factor = (ONE - nth_power) / rate;
48 let pv_grown = pv * nth_power;
49
50 if due {
51 pmt * factor * (ONE + rate) + pv_grown
52 } else {
53 pmt * factor + pv_grown
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 #[cfg(not(feature = "std"))]
61 extern crate std;
62 use rust_decimal_macros::*;
63 #[cfg(not(feature = "std"))]
64 use std::assert;
65 #[cfg(not(feature = "std"))]
66 use std::prelude::v1::*;
67
68 #[test]
69 fn test_fv() {
70 struct TestCase {
71 rate: Decimal,
72 nper: Decimal,
73 pmt: Decimal,
74 pv: Option<Decimal>,
75 due: Option<bool>,
76 expected: Decimal,
77 description: &'static str,
78 }
79 impl TestCase {
80 fn new(
81 rate: f64,
82 nper: f64,
83 pmt: f64,
84 pv: Option<f64>,
85 due: Option<bool>,
86 expected: f64,
87 description: &'static str,
88 ) -> TestCase {
89 TestCase {
90 rate: Decimal::from_f64(rate).unwrap(),
91 nper: Decimal::from_f64(nper).unwrap(),
92 pmt: Decimal::from_f64(pmt).unwrap(),
93 pv: pv.map(Decimal::from_f64).unwrap_or(None),
94 due,
95 expected: Decimal::from_f64(expected).unwrap(),
96 description,
97 }
98 }
99 }
100
101 let cases = [
102 TestCase::new(
103 0.05,
104 10.0,
105 -100.0,
106 None,
107 None,
108 1257.78925,
109 "Standard case with 5% rate, 10 periods, and $100 pmt",
110 ),
111 TestCase::new(
112 0.05,
113 10.0,
114 -100.0,
115 None,
116 Some(true),
117 1320.67872,
118 "Payment at the beg of period should result in higher future value",
119 ),
120 TestCase::new(0.0, 10.0, -100.0, None, None, -1000.0, "Zero interest rate no growth"),
121 TestCase::new(
122 0.05,
123 10.0,
124 -100.0,
125 Some(1000.0),
126 None,
127 2886.68388,
128 "Initial investment should result in higher future value",
129 ),
130 ];
131
132 for case in &cases {
133 let calculated_fv = fv(case.rate, case.nper, case.pmt, case.pv, case.due);
134 assert!(
135 (calculated_fv - case.expected).abs() < dec!(1e-5),
136 "Failed on case: {}. Expected {}, got {}",
137 case.description,
138 case.expected,
139 calculated_fv
140 );
141 }
142 }
143}