rust_finprim/tvm/
fv.rs

1use crate::FloatLike;
2
3/// FV - Future Value
4///
5/// A general future value calculation, similar to the Excel `FV` function.
6///
7///
8/// The future value (FV) is the value of an asset or cash at a specified date in the future based on a certain rate of return.
9/// The future value is the amount of money that an investment made today will grow to by a future date.
10/// It is calculated by applying a rate of return to the initial investment over a specified period of time.
11///
12/// # Arguments
13/// * `rate` - The interest rate per period
14/// * `nper` - The number of compounding periods
15/// * `pmt` - The payment amount per period
16/// * `pv` (optional) - The present value, default is 0
17/// * `due` (optional) - The timing of the payment (false = end of period, true = beginning of period), default is false
18/// (ordinary annuity)
19///
20/// At least one of `pmt` or `pv` should be non-zero.
21///
22/// # Returns
23/// * The future value (FV)
24///
25/// # Example
26/// * 5% interest rate
27/// * 10 compounding periods
28/// * $100 payment per period
29/// ```
30///
31/// use rust_finprim::tvm::fv;
32///
33/// let rate = 0.05; let nper = 10.0; let pmt = -100.0;
34/// fv(rate, nper, pmt, None, None);
35/// ```
36pub fn fv<T: FloatLike>(rate: T, nper: T, pmt: T, pv: Option<T>, due: Option<bool>) -> T {
37    let pv = pv.unwrap_or(T::zero());
38    let due = due.unwrap_or(false);
39
40    if rate.is_zero() {
41        // Simplified formula when rate is zero
42        return pmt * nper + pv;
43    }
44
45    let nth_power = (T::one() + rate).powf(nper);
46    let factor = (nth_power - T::one()) / rate;
47    let pv_grown = pv * nth_power;
48
49    let fv = if due {
50        pmt * factor * (T::one() + rate) + pv_grown
51    } else {
52        pmt * factor + pv_grown
53    };
54    -fv
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    #[cfg(not(feature = "std"))]
61    extern crate std;
62    #[cfg(not(feature = "std"))]
63    use std::assert;
64
65    #[test]
66    fn test_fv() {
67        struct TestCase {
68            rate: f64,
69            nper: f64,
70            pmt: f64,
71            pv: Option<f64>,
72            due: Option<bool>,
73            expected: f64,
74            description: &'static str,
75        }
76        impl TestCase {
77            fn new(
78                rate: f64,
79                nper: f64,
80                pmt: f64,
81                pv: Option<f64>,
82                due: Option<bool>,
83                expected: f64,
84                description: &'static str,
85            ) -> TestCase {
86                TestCase {
87                    rate,
88                    nper,
89                    pmt,
90                    pv,
91                    due,
92                    expected,
93                    description,
94                }
95            }
96        }
97
98        let cases = [
99            TestCase::new(
100                0.05,
101                10.0,
102                -100.0,
103                None,
104                None,
105                1257.78925,
106                "Standard case with 5% rate, 10 periods, and $100 pmt",
107            ),
108            TestCase::new(
109                0.05,
110                10.0,
111                -100.0,
112                None,
113                Some(true),
114                1320.67872,
115                "Payment at the beg of period should result in higher future value",
116            ),
117            TestCase::new(0.0, 10.0, -100.0, None, None, -1000.0, "Zero interest rate no growth"),
118            TestCase::new(
119                0.05,
120                10.0,
121                -100.0,
122                Some(1000.0),
123                None,
124                -371.10537,
125                "Initial investment should result in higher future value",
126            ),
127            TestCase::new(
128                0.05,
129                10.0,
130                -100.0,
131                Some(-1000.0),
132                None,
133                2886.68388,
134                "Initial investment should result in higher future value",
135            ),
136        ];
137
138        for case in &cases {
139            let calculated_fv = fv(case.rate, case.nper, case.pmt, case.pv, case.due);
140            assert!(
141                (calculated_fv - case.expected).abs() < 1e-5,
142                "Failed on case: {}. Expected {}, got {}",
143                case.description,
144                case.expected,
145                calculated_fv
146            );
147        }
148    }
149}