finance_solution/cashflow/present_value_annuity.rs
1#![allow(unused_imports)]
2
3//! **Present value _annuity_ calculations**. Given a series of constant cashflows, a number of periods
4//! such as years, and a fixed interest rate, what is the current value of the series right now?
5//!
6//! Timing uses [`crate::PaymentTiming`] (or Excel-style `bool` via [`From`]):
7//! - [`PaymentTiming::EndOfPeriod`] / `false` — ordinary annuity (Excel `type=0`)
8//! - [`PaymentTiming::BeginningOfPeriod`] / `true` — annuity due (Excel `type=1`)
9//!
10//! Prefer the enum in new code; `bool` remains for spreadsheet parity.
11//!
12//! For teaching / debugging, use [`present_value_annuity_solution`].
13//!
14//! ## Examples
15//!
16//! Ordinary annuity with `bool`:
17//! ```
18//! use finance_solution::present_value_annuity_solution;
19//! let (rate, periods, annuity, due) = (0.034, 10, 500, false);
20//! let pv_ann = present_value_annuity_solution(rate, periods, annuity, due).unwrap();
21//! assert!(pv_ann.present_value().abs() > 4_000.0);
22//! ```
23//!
24//! Enum timing (preferred) — due has larger magnitude:
25//! ```
26//! use finance_solution::{present_value_annuity, PaymentTiming};
27//! let ordinary = present_value_annuity(0.034, 10, 500, PaymentTiming::EndOfPeriod).unwrap();
28//! let due = present_value_annuity(0.034, 10, 500, PaymentTiming::BeginningOfPeriod).unwrap();
29//! assert!(due.abs() > ordinary.abs());
30//! ```
31//!
32//! Integer money via `Into<f64>` still works:
33//! ```
34//! use finance_solution::{present_value_annuity, PaymentTiming};
35//! let pv = present_value_annuity(0.021, 12, 2_000, PaymentTiming::EndOfPeriod).unwrap();
36//! assert!(pv.is_finite());
37//! ```
38//!
39
40// to do: add "use log::warn;" and helper logs
41
42// Needed for the Rustdoc comments.
43use crate::cashflow::*;
44use crate::future_value::future_value;
45use crate::present_value::present_value;
46
47/// Returns the **present value of an annuity** (series of constant cashflows) at a constant rate. Returns f64.
48///
49/// The present value annuity formula is (both yield the same result):
50///
51/// present_value = sum( cashflow / (1 + rate)<sup>period</sup> )
52///
53/// or
54///
55/// present value = annuity * ((1. - (1. / (1. + rate)).powf(periods)) / rate)
56///
57/// # Arguments
58/// * `rate` - The rate at which the investment grows or shrinks per period,
59/// expressed as a floating point number. For instance 0.05 would mean 5%. Often appears as
60/// `r` or `i` in formulas.
61/// * `periods` - The number of periods such as quarters or years. Often appears as `n` or `t`.
62/// * `cashflow` - The value of the constant cashflow (aka payment, or annuity).
63/// * `timing` - [`PaymentTiming`] or `bool` (`false` = end of period / Excel `type=0`).
64///
65/// # Errors
66/// Returns [`crate::FinanceError`] if `rate` is less than or equal to -1.0, money is non-finite,
67/// or `periods` is zero.
68///
69/// # Examples
70/// Solution API with spreadsheet-style `bool`:
71/// ```
72/// # use finance_solution::*;
73/// let my_annuity = present_value_annuity_solution(0.034, 10, 21_000, false).unwrap();
74/// assert!(my_annuity.present_value().is_finite());
75/// ```
76///
77/// Scalar PV of twelve $2,000 cashflows at 2.1% monthly:
78/// ```
79/// # use finance_solution::*;
80/// let present_value_ann = present_value_annuity(0.021, 12, 2_000, false).unwrap();
81/// assert_approx_equal!(-21021.368565, present_value_ann);
82/// ```
83///
84/// Annuity due with the enum:
85/// ```
86/// # use finance_solution::*;
87/// let due = present_value_annuity(0.034, 10, 500, PaymentTiming::BeginningOfPeriod).unwrap();
88/// let ordinary = present_value_annuity(0.034, 10, 500, PaymentTiming::EndOfPeriod).unwrap();
89/// assert!(due.abs() > ordinary.abs());
90/// ```
91pub fn present_value_annuity<T, D>(
92 rate: f64,
93 periods: u32,
94 annuity: T,
95 timing: D,
96) -> crate::FinanceResult<f64>
97where
98 T: Into<f64> + Copy,
99 D: Into<crate::PaymentTiming>,
100{
101 let pmt = annuity.into();
102 let timing = timing.into();
103 crate::util::error::require_rate_gt_minus_one(rate)?;
104 crate::util::error::require_money("annuity", pmt)?;
105 if periods == 0 {
106 return Err(crate::FinanceError::InvalidPeriod {
107 period: 0,
108 periods: 0,
109 message: "annuity requires at least one period",
110 });
111 }
112 // Ordinary (end): PV = -pmt * (1 - (1+r)^-n) / r
113 // Due (beginning): multiply by (1 + r)
114 // Zero rate: PV = -pmt * n for both timings
115 let pv_ann = match (rate == 0.0, timing) {
116 (true, _) => -pmt * periods as f64,
117 (false, crate::PaymentTiming::EndOfPeriod) => {
118 -pmt * ((1.0 - (1.0 / (1.0 + rate)).powf(periods as f64)) / rate)
119 }
120 (false, crate::PaymentTiming::BeginningOfPeriod) => {
121 -pmt * (1.0 + rate) * ((1.0 - (1.0 / (1.0 + rate)).powf(periods as f64)) / rate)
122 }
123 };
124 if pv_ann.is_finite() {
125 Ok(pv_ann)
126 } else {
127 Err(crate::FinanceError::NonFinite {
128 field: "present_value_annuity",
129 value: pv_ann,
130 })
131 }
132}
133
134pub fn present_value_annuity_accumulator<T, D>(
135 rate: f64,
136 periods: u32,
137 annuity: T,
138 timing: D,
139) -> crate::FinanceResult<f64>
140where
141 T: Into<f64> + Copy,
142 D: Into<crate::PaymentTiming>,
143{
144 let pmt = annuity.into();
145 let timing = timing.into();
146 crate::util::error::require_rate_gt_minus_one(rate)?;
147 crate::util::error::require_money("annuity", pmt)?;
148
149 let mut pv_accumulator = match timing {
150 crate::PaymentTiming::BeginningOfPeriod => (1.0 + rate) * pmt,
151 crate::PaymentTiming::EndOfPeriod => 0.0,
152 };
153 for i in 1..=periods {
154 let present_value = present_value(rate, i as u32, pmt, false)?;
155 pv_accumulator += present_value;
156 }
157 if pv_accumulator.is_finite() {
158 Ok(pv_accumulator)
159 } else {
160 Err(crate::FinanceError::NonFinite {
161 field: "present_value_annuity",
162 value: pv_accumulator,
163 })
164 }
165}
166
167// / Returns the present value of a series of cashflows and rates, which can be varying. Receives vectors and returns f64.
168// /
169// / Related functions:
170// / * To calculate a present value with a constant cashflow and rate, use [`present_value_annuity`].
171// /
172// / The present value annuity formula is:
173// /
174// / present_value = sum( cashflow / (1 + rate)<sup>period</sup> )
175// /
176// / # Arguments
177// / * `rate` - The rate at which the investment grows or shrinks per period,
178// / expressed as a floating point number. For instance 0.05 would mean 5%. Often appears as
179// / `r` or `i` in formulas.
180// / * `periods` - The number of periods such as quarters or years. Often appears as `n` or `t`.
181// / * `cashflow` - The value of the cashflow at the time of that period (ie, future value).
182// /
183// / # Errors
184// / The call will fail if `rate` is less than -1.0 as this would mean the investment is
185// / losing more than its full value every period.
186// /
187// / # Examples
188// / Present value of a series of $2000 cashflows.
189// / ```
190// / // The rate is varying each month.
191// / let rates = vec![0.021, 0.028, 0.019];
192// /
193// / // The cashflow will be $2,000.
194// / // The number of periods is inferred by the length of the vector.
195// / // The rep! macro is used to create a vector of repeating values.
196// / // let cashflows = finance_solution::repeat!(2_000, rate.len());
197// / let cashflows = vec![2000,2000,2000];
198// /
199// / // Find the current value.
200// / let present_value_ann = finance_solution::present_value_annuity_schedule(rates, cashflows);
201// / dbg!(&present_value_ann);
202// /
203// / // Confirm that the present value is correct to four decimal places (one hundredth of a cent).
204// / // finance_solution::assert_approx_equal!( , present_value_ann);
205// / ```
206// /
207
208// pub fn present_value_annuity_schedule<T>(rates: &[f64], cashflows: &[T]) -> f64
209// where T: Into<f64> + Copy
210// {
211// // check_present_value__annuity_varying_parameters(rate, periods, cashflow);
212
213// // update
214// let periods = rates.len();
215
216// let mut pv_accumulator = 0_f64;
217// for i in 0..periods {
218// let pmt = cashflows[i].into();
219// let rate = rates[i];
220// let present_value = present_value(rate, i as u32, pmt);
221// pv_accumulator = pv_accumulator + present_value;
222// }
223// pv_accumulator
224// }
225
226/// Returns the present value of a future series of constant cashflows and constant rate. Returns custom solution type with additional information and functionality.
227///
228/// Related functions:
229/// * To calculate a present value returning an f64, use [`present_value_annuity`].
230/// * To calculate a present value with a varying rate or varying cashflow or both, use [`present_value_annuity_schedule`].
231///
232/// The present value annuity formula is:
233///
234/// present_value = sum( cashflow / (1 + rate)<sup>period</sup> )
235/// or
236/// present value = annuity * ((1. - (1. / (1. + rate)).powf(periods)) / rate)
237///
238/// # Arguments
239/// * `rate` - The rate at which the investment grows or shrinks per period,
240/// expressed as a floating point number. For instance 0.05 would mean 5%. Often appears as
241/// `r` or `i` in formulas.
242/// * `periods` - The number of periods such as quarters or years. Often appears as `n` or `t`.
243/// * `cashflow` - The value of the constant cashflow (aka payment).
244/// * `timing` - [`PaymentTiming`] or `bool` (`false` = end of period).
245///
246/// # Errors
247/// Same domain failures as [`present_value_annuity`].
248///
249/// # Examples
250/// Present value of a $500 annuity for 10 periods at 3.4%:
251/// ```
252/// # use finance_solution::*;
253/// let present_value_ann = present_value_annuity_solution(
254/// 0.034, 10, 500, PaymentTiming::EndOfPeriod
255/// ).unwrap();
256/// assert!(present_value_ann.present_value().abs() > 4_000.0);
257/// assert!(!present_value_ann.due_at_beginning());
258/// ```
259///
260/// Annuity due solution:
261/// ```
262/// # use finance_solution::*;
263/// let due = present_value_annuity_solution(
264/// 0.034, 10, 500, PaymentTiming::BeginningOfPeriod
265/// ).unwrap();
266/// assert!(due.due_at_beginning());
267/// ```
268pub fn present_value_annuity_solution<T, D>(
269 rate: f64,
270 periods: u32,
271 cashflow: T,
272 timing: D,
273) -> crate::FinanceResult<CashflowSolution>
274where
275 T: Into<f64> + Copy,
276 D: Into<crate::PaymentTiming>,
277{
278 let annuity = cashflow.into();
279 let timing = timing.into();
280 let due_at_beginning = timing.is_beginning();
281 let pv = present_value_annuity(rate, periods, annuity, timing)?;
282 let pvann_type = match timing {
283 crate::PaymentTiming::BeginningOfPeriod => CashflowVariable::PresentValueAnnuityDue,
284 crate::PaymentTiming::EndOfPeriod => CashflowVariable::PresentValueAnnuity,
285 };
286 let (formula, formula_symbolic) = match timing {
287 crate::PaymentTiming::EndOfPeriod => (
288 format!(
289 "-{} * ((1. - (1. / (1. + {})).powf({})) / {});",
290 annuity, rate, periods, rate
291 ),
292 "-annuity * ((1. - (1. / (1. + rate)).powf(periods)) / rate);".to_string(),
293 ),
294 crate::PaymentTiming::BeginningOfPeriod => (
295 format!(
296 "-{} * ((1. - (1. / (1. + {})).powf({})) / {}) * (1. + {});",
297 annuity, rate, periods, rate, rate
298 ),
299 "-annuity * ((1. - (1. / (1. + rate)).powf(periods)) / rate) * (1. + rate);"
300 .to_string(),
301 ),
302 };
303 let fv = future_value(rate, periods, pv, false)?;
304 Ok(CashflowSolution::new(
305 pvann_type,
306 rate,
307 periods,
308 pv,
309 fv,
310 due_at_beginning,
311 annuity,
312 &formula,
313 &formula_symbolic,
314 ))
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use crate::*;
321
322 #[test]
323 fn test_present_value_annuity_1() {
324 // one period
325 let (rate, periods, annuity) = (0.034, 1, 500);
326 let pv = present_value_annuity(rate, periods, annuity, false).unwrap();
327 assert_eq!(-483.55899, (pv * 100000.).round() / 100000.);
328 }
329 #[test]
330 fn test_present_value_annuity_2() {
331 // big periods
332 let (rate, periods, annuity) = (0.034, 400, 500);
333 let pv = present_value_annuity(rate, periods, annuity, false).unwrap();
334 assert_eq!(-14705.85948, (pv * 100000.).round() / 100000.);
335 }
336 #[test]
337 fn test_present_value_annuity_due_2() {
338 // big periods, due
339 let (rate, periods, annuity) = (0.034, 400, 500);
340 let pv = present_value_annuity(rate, periods, annuity, true).unwrap();
341 assert_eq!(-15205.8587, (pv * 100000.).round() / 100000.);
342 }
343
344 #[test]
345 fn test_present_value_annuity_3() {
346 // negative rate
347 let (rate, periods, annuity) = (-0.034, 52, 500);
348 let pv = present_value_annuity(rate, periods, annuity, false).unwrap();
349 assert_eq!(-74_148.8399, (pv * 100000.).round() / 100000.);
350 }
351
352 #[test]
353 fn test_present_value_annuity_4() {
354 // big negative rate
355 let (rate, periods, annuity) = (-0.999, 3, 500);
356 let pv = present_value_annuity(rate, periods, annuity, false).unwrap();
357 assert_eq!(-500_500_499_999.999, (pv * 1000.).round() / 1000.);
358 }
359
360 #[test]
361 fn test_present_value_annuity_due_4() {
362 // big negative rate, due
363 let (rate, periods, annuity) = (-0.999, 3, 500);
364 let pv = present_value_annuity(rate, periods, annuity, true).unwrap();
365 assert_eq!(-500_500_499.999999, (pv * 1000000.).round() / 1000000.);
366 }
367
368 #[test]
369 fn test_present_value_annuity_5() {
370 // big precision
371 let (rate, periods, annuity) = (0.00034, 2_800, 5_000_000);
372 let pv = present_value_annuity(rate, periods, annuity, false).unwrap();
373 assert_eq!(-9028959259.06, (pv * 100.).round() / 100.);
374 }
375
376 #[test]
377 fn test_present_value_annuity_payment_timing_parity() {
378 use crate::PaymentTiming;
379 let ordinary_bool = present_value_annuity(0.034, 10, 500, false).unwrap();
380 let ordinary_enum =
381 present_value_annuity(0.034, 10, 500, PaymentTiming::EndOfPeriod).unwrap();
382 assert_eq!(ordinary_bool, ordinary_enum);
383
384 let due_bool = present_value_annuity(0.034, 10, 500, true).unwrap();
385 let due_enum =
386 present_value_annuity(0.034, 10, 500, PaymentTiming::BeginningOfPeriod).unwrap();
387 assert_eq!(due_bool, due_enum);
388 assert!(due_enum.abs() > ordinary_enum.abs());
389 }
390}