1#![deny(rust_2018_idioms)]
2#![deny(unused)]
3
4pub trait AnnuityRegular {
5 type Output;
6 type Error;
7
8 fn present_value(self) -> Result<Self::Output, Self::Error>;
9 fn future_value(self) -> Result<Self::Output, Self::Error>;
10}
11pub trait AnnuityDue {
12 type Output;
13 type Error;
14
15 fn present_value(self) -> Result<Self::Output, Self::Error>;
16 fn future_value(self) -> Result<Self::Output, Self::Error>;
17}
18
19#[cfg(not(feature = "rust_decimal"))]
20type F64 = f64;
21
22#[cfg(feature = "rust_decimal")]
23use rust_decimal::dec;
24#[cfg(feature = "rust_decimal")]
25type F64 = rust_decimal::Decimal;
26
27#[cfg(feature = "rust_decimal")]
28use rust_decimal::MathematicalOps;
29
30#[derive(PartialEq, Eq, Debug)]
31pub enum TimeValueError {
32 EmptyCashFlow,
33 NegativeDiscount,
34}
35
36#[derive(Clone, Copy)]
37pub struct Annuity<T, I>
38where
39 I: IntoIterator<Item = T>,
40 T: Into<F64> + Copy,
41{
42 cashflows: I,
43 rate: F64,
44}
45
46impl<T, I> Annuity<T, I>
47where
48 I: IntoIterator<Item = T>,
49 T: Into<F64> + Copy,
50{
51 pub fn new(cashflows: I, rate: impl Into<F64>) -> Self {
52 Self {
53 cashflows,
54 rate: rate.into(),
55 }
56 }
57}
58
59#[cfg(not(feature = "rust_decimal"))]
60const ZERO: F64 = 0.0;
61
62#[cfg(feature = "rust_decimal")]
63const ZERO: F64 = dec!(0.0);
64
65#[cfg(not(feature = "rust_decimal"))]
66const ONE: F64 = 1.0;
67
68#[cfg(feature = "rust_decimal")]
69const ONE: F64 = dec!(1.0);
70
71impl<T, I> AnnuityRegular for Annuity<T, I>
72where
73 I: IntoIterator<Item = T>,
74 T: Into<F64> + Copy,
75{
76 type Output = F64;
77
78 type Error = TimeValueError;
79
80 fn present_value(self) -> Result<Self::Output, Self::Error> {
81 if self.rate < ZERO {
82 return Err(TimeValueError::NegativeDiscount);
83 }
84
85 let mut is_empty = true;
86 let mut sum = ZERO;
87
88 for (n, cf) in self.cashflows.into_iter().enumerate() {
89 is_empty = false;
90 let val: F64 = cf.into();
91
92 #[cfg(not(feature = "rust_decimal"))]
93 let exp: i32 = n as i32 + 1;
94
95 #[cfg(feature = "rust_decimal")]
96 let exp: i64 = n as i64 + 1;
97
98 let disc = val / (ONE + self.rate).powi(exp);
99 sum += disc;
100 }
101
102 if is_empty {
103 return Err(TimeValueError::EmptyCashFlow);
104 }
105
106 Ok(sum)
107 }
108
109 fn future_value(self) -> Result<Self::Output, Self::Error> {
110 if self.rate < ZERO {
111 return Err(TimeValueError::NegativeDiscount);
112 }
113
114 let cash_flows: Vec<_> = self.cashflows.into_iter().map(|i| i.into()).collect();
115 if cash_flows.is_empty() {
116 return Err(TimeValueError::EmptyCashFlow);
117 }
118
119 let mut f: Vec<_> = cash_flows
120 .iter()
121 .enumerate()
122 .map(|(n, &cash_flow)| {
123 #[cfg(not(feature = "rust_decimal"))]
124 let exp: i32 = n as i32 + 1;
125
126 #[cfg(feature = "rust_decimal")]
127 let exp: i64 = n as i64 + 1;
128
129 cash_flow * (ONE + self.rate).powi(exp)
130 })
131 .collect();
132 f.pop();
133
134 if let Some(value) = cash_flows.last() {
135 f.push(*value);
136 }
137 let future_value = f.iter().sum::<F64>();
138
139 Ok(future_value)
140 }
141}
142
143impl<T, I> AnnuityDue for Annuity<T, I>
144where
145 I: IntoIterator<Item = T>,
146 T: Into<F64> + Copy,
147{
148 type Output = F64;
149
150 type Error = TimeValueError;
151
152 fn present_value(self) -> Result<Self::Output, Self::Error> {
153 let rate = self.rate;
154 let pv = <Self as AnnuityRegular>::present_value(self)?;
155
156 Ok(pv * (ONE + rate))
157 }
158
159 fn future_value(self) -> Result<Self::Output, Self::Error> {
160 if self.rate < ZERO {
161 return Err(TimeValueError::NegativeDiscount);
162 }
163 let cash_flows: Vec<_> = self.cashflows.into_iter().map(|i| i.into()).collect();
164 if cash_flows.is_empty() {
165 return Err(TimeValueError::EmptyCashFlow);
166 }
167
168 let future_value = cash_flows
169 .iter()
170 .enumerate()
171 .map(|(i, &cash_flow)| {
172 #[cfg(not(feature = "rust_decimal"))]
173 let exp: i32 = i as i32 + 1;
174
175 #[cfg(feature = "rust_decimal")]
176 let exp: i64 = i as i64 + 1;
177
178 cash_flow * (ONE + self.rate).powi(exp)
179 })
180 .sum();
181
182 Ok(future_value)
183 }
184}
185
186#[cfg(test)]
187mod tests {
188
189 use super::*;
190
191 macro_rules! f64 {
192 ($val:literal) => {{
193 #[cfg(not(feature = "rust_decimal"))]
194 {
195 $val as f64
196 }
197 #[cfg(feature = "rust_decimal")]
198 {
199 dec!($val)
200 }
201 }};
202 }
203
204 #[test]
205 fn annuity_pv() {
206 let rate = f64!(0.12);
207
208 let f = Annuity::new(
209 [
210 5_000, 5_000, 5_000, 5_000, 5_000, 5_000, 5_000, 5_000, 5_000, 5_000,
211 ],
212 rate,
213 );
214
215 let pv = super::AnnuityRegular::present_value(f).unwrap();
216
217 #[cfg(not(feature = "rust_decimal"))]
218 let exp: F64 = 28251.115142054317;
219
220 #[cfg(feature = "rust_decimal")]
221 let exp: F64 = dec!(28251.115142054324467040310164);
222
223 #[cfg(feature = "rust_decimal")]
224 assert_eq!(pv, exp); #[cfg(not(feature = "rust_decimal"))]
227 assert!(
228 (pv - exp).abs() < f64::EPSILON,
229 "Left: {}, Right: {}",
230 pv,
231 exp
232 );
233 }
234 #[test]
235 fn annuity_pv_due() {
236 let f = Annuity::new([5_000].repeat(10), f64!(0.12));
237 let pv = super::AnnuityDue::present_value(f).unwrap();
238
239 #[cfg(not(feature = "rust_decimal"))]
240 assert_eq!(pv, f64!(31641.248959100838));
241
242 #[cfg(feature = "rust_decimal")]
243 assert_eq!(pv, f64!(31641.248959100843403085147384));
244 }
245 #[test]
246 fn annuity_fv_reg() {
247 #[cfg(not(feature = "rust_decimal"))]
248 let rate: F64 = 0.09;
249
250 #[cfg(feature = "rust_decimal")]
251 let rate: F64 = dec!(0.09);
252
253 let f = Annuity::new([50_000].repeat(7), rate);
254 let pv = super::AnnuityRegular::future_value(f).unwrap();
255
256 #[cfg(not(feature = "rust_decimal"))]
257 let exp: F64 = 460021.7337870501;
258
259 #[cfg(feature = "rust_decimal")]
260 let exp: F64 = dec!(460021.733787050000);
261
262 assert_eq!(pv, exp);
263 }
264
265 #[test]
266 fn annuity_fv_due() {
267 let f = Annuity::new(
268 [
269 200_000, 200_000, 200_000, 200_000, 200_000, 200_000, 200_000,
270 ],
271 f64!(0.12),
272 );
273 let pv = super::AnnuityDue::future_value(f).unwrap();
274
275 #[cfg(not(feature = "rust_decimal"))]
276 assert_eq!(pv, f64!(2259938.6271580164));
277
278 #[cfg(feature = "rust_decimal")]
279 assert_eq!(pv, f64!(2259938.62715801600000));
280 }
281}