Skip to main content

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}