finance_solution/tvm/rate.rs
1//! **Periodic rate calculations.** Given an initial investment amount, a final amount, and a number
2//! of periods what does the rate per period need to be?
3//!
4//! For most common usages, we recommend the [`rate_solution`](./fn.rate_solution.html) function to provide the best experience with debugging and additional features.
5//!
6// ! If you need to calculate the future value given a starting value, a number of periods, and one
7// ! or more rates use [`future_value`] or related functions.
8// !
9// ! If you need to calculate the present value given a future value, a number of periods, and one
10// ! or more rates use [`present_value`] or related functions.
11// !
12// ! If you need to calculate the number of periods given a fixed rate and a present and future value
13// ! use [`periods`] or related functions.
14//! # Formulas
15//!
16//! ## Simple Compounding
17//!
18//! With simple compound interest the rate is calculated with:
19//!
20//! > <img src="http://i.upmath.me/svg/rate%20%3D%20%5Csqrt%5Bperiods%5D%7B%5Cfrac%7Bfuture%5C_value%7D%7Bpresent%5C_value%7D%7D%20-%201" />
21//!
22//! Or using a few more common variable names:
23//!
24//! > <img src="http:i.upmath.me/svg/r%20%3D%20%5Csqrt%5Bn%5D%7B%5Cfrac%7Bfv%7D%7Bpv%7D%7D%20-%201" />
25//!
26//! `r` is the periodic rate, though this may appear as `i` for interest. `n` is often used for the
27//! number of periods, though it may be `t` for time if each period is assumed to be one year as in
28//! continuous compounding.
29//!
30//! Throughout this crate we use `pv` for present value and `fv` for future value. You may see these
31//! values called `P` for principal in some references.
32//!
33//! ## Continuous Compounding
34//!
35//! With continuous compounding the formula is:
36//!
37//! > <img src="http://i.upmath.me/svg/rate%20%3D%20%5Cfrac%7B%5Cln%5Cleft(%5Cfrac%7Bfuture%5C_value%7D%7Bpresent%5C_value%7D%5Cright)%7D%7Bperiods%7D" />
38//!
39//! or:
40//!
41//! > <img src="http://i.upmath.me/svg/r%20%3D%20%5Cfrac%7B%5Cln%5Cleft(%5Cfrac%7Bfv%7D%7Bpv%7D%5Cright)%7Dn" />
42//!
43//! With continuous compounding the period is assumed to be years and `t` (time) is often used as
44//! the variable name. Within this crate we stick with `n` for the number of periods so that all of
45//! the functions use the same variables.
46
47use crate::*;
48
49/// Returns the periodic rate of an investment given the number of periods along with the present
50/// and future values.
51///
52/// See the [rate](./index.html) module page for the formulas.
53///
54/// Related functions:
55/// * To calculate a periodic rate and return a struct that shows the formula and optionally
56/// produces the the period-by-period values use [`rate_solution`].
57///
58/// # Arguments
59/// * `periods` - The number of periods such as quarters or periods. Often appears as `n` or `t`.
60/// * `present_value` - The starting value of the investment. May appear as `pv` in formulas, or `C`
61/// for cash flow or `P` for principal.
62/// * `future_value` - The final value of the investment.
63/// * `continuous_compounding` - True for continuous compounding, false for simple compounding.
64///
65/// If present_value and future_value are both zero then any rate will work so the function returns
66/// zero.
67///
68/// # Panics
69/// The call will fail if the present value is zero and the future value is nonzero or vice versa.
70/// It will also fail if the number of periods is zero and the present value is not equal to the
71/// future value. In both cases this is because there's no periodic rate that could make that work.
72///
73/// # Examples
74/// ```
75/// use finance_solution::*;
76///
77/// // The interest will compound for 365 days.
78/// let periods = 365;
79///
80/// // The starting value is $10,000.
81/// let present_value = -10_000.00;
82///
83/// // The ending value is $11,000.
84/// let future_value = 11_000.00;
85///
86/// let continuous_compounding = false;
87///
88/// // Calculate the periodic rate needed.
89/// let rate = rate(periods, present_value, future_value, continuous_compounding);
90/// dbg!(&rate);
91/// // The rate is 0.0261% per day.
92/// assert_rounded_6(0.000261, rate);
93/// ```
94pub fn rate<P, F>(periods: u32, present_value: P, future_value: F, continuous_compounding: bool) -> f64
95 where
96 P: Into<f64> + Copy,
97 F: Into<f64> + Copy
98{
99 rate_internal(periods, present_value.into(), future_value.into(), continuous_compounding)
100}
101
102/// Returns the periodic rate of an investment given the number of periods along with the present
103/// and future values.
104///
105/// See the [rate](./index.html) module page for the formulas.
106///
107/// Related functions:
108/// * To calculate a periodic rate returning an f64 value instead of solution object, use [`rate`](./fn.rate.html).
109///
110/// # Arguments
111/// * `periods` - The number of periods such as quarters or periods. Often appears as `n` or `t`.
112/// * `present_value` - The starting value of the investment. May appear as `pv` in formulas, or `C`
113/// for cash flow or `P` for principal.
114/// * `future_value` - The final value of the investment.
115/// * `continuous_compounding` - True for continuous compounding, false for simple compounding.
116///
117/// If present_value and future_value are both zero then any rate will work so the function returns
118/// zero.
119///
120/// # Panics
121/// The call will fail if the present value is zero and the future value is nonzero or vice versa.
122/// It will also fail if the number of periods is zero and the present value is not equal to the
123/// future value. In both cases this is because there's no periodic rate that could make that work.
124///
125/// # Examples
126/// Calculate a periodic rate and examine the period-by-period values.
127/// ```
128/// use finance_solution::*;
129/// // The interest will compound for ten periods.
130/// // The starting value is $10,000.
131/// // The ending value is $15,000.
132/// let periods = 10;
133/// let present_value = -10_000.00;
134/// let future_value = 15_000.00;
135/// let continuous_compounding = false;
136/// /// // Calculate the periodic rate and create a struct with a record of the
137/// // inputs, a description of the formula, and an option to calculate the
138/// // period-by-period values.
139/// let solution = rate_solution(periods, present_value, future_value, continuous_compounding);
140/// dbg!(&solution);
141///
142/// let rate = solution.rate();
143/// dbg!(&rate);
144/// // The rate is 4.138% per period.
145/// assert_rounded_6(0.041380, rate);
146///
147/// // Examine the formulas.
148/// let formula = solution.formula();
149/// dbg!(&formula);
150/// assert_eq!("0.041380 = ((-15000.0000 / -10000.0000) ^ (1 / 10)) - 1", formula);
151/// let symbolic_formula = solution.symbolic_formula();
152/// dbg!(&symbolic_formula);
153/// assert_eq!("r = ((-fv / pv) ^ (1 / n)) - 1", symbolic_formula);
154///
155/// // Calculate the period-by-period values.
156/// let series = solution.series();
157/// dbg!(&series);
158/// ```
159pub fn rate_solution<P, F>(periods: u32, present_value: P, future_value: F, continuous_compounding: bool) -> TvmSolution
160 where
161 P: Into<f64> + Copy,
162 F: Into<f64> + Copy
163{
164 rate_solution_internal(periods, present_value.into(), future_value.into(), continuous_compounding)
165}
166
167fn rate_internal(periods: u32, present_value: f64, future_value: f64, continuous_compounding: bool) -> f64 {
168 if present_value + future_value == 0.0 {
169 // This is a special case where any rate will work.
170 return 0.0;
171 }
172 if future_value == 0.0 {
173 // This is a special case where the rate must be -100% because present value is nonzero.
174 return -1.0;
175 }
176 check_rate_parameters(periods, present_value, future_value);
177
178 let rate = if continuous_compounding {
179 // http://www.edmichaelreggie.com/TMVContent/APR.htm
180 (-future_value / present_value).ln() / periods as f64
181 } else {
182 (-future_value / present_value).powf(1.0 / periods as f64) - 1.0
183 };
184
185 if !rate.is_finite() {
186 dbg!(periods, present_value, future_value, continuous_compounding, rate);
187 }
188
189 assert!(rate.is_finite());
190 rate
191}
192
193pub (crate) fn rate_solution_internal(periods: u32, present_value: f64, future_value: f64, continuous_compounding: bool) -> TvmSolution {
194 if present_value == 0.0 && future_value == 0.0 {
195 // This is a special case where any rate will work.
196 let formula = "{special case}";
197 let symbolic_formula = "***";
198 let rate = 0.0;
199 return TvmSolution::new(TvmVariable::Rate, continuous_compounding, rate, periods, present_value, future_value, formula, symbolic_formula);
200 }
201
202 let rate = rate_internal(periods, present_value, future_value, continuous_compounding);
203 let (formula, symbolic_formula) = if continuous_compounding {
204 let formula = format!("{:.6} = ln({:.4} / {:.4}) / {}", rate, -future_value, present_value, periods);
205 let symbolic_formula = "r = ln(-fv / pv) / t";
206 (formula, symbolic_formula)
207 } else {
208 let formula = format!("{:.6} = (({:.4} / {:.4}) ^ (1 / {})) - 1", rate, -future_value, present_value, periods);
209 let symbolic_formula = "r = ((-fv / pv) ^ (1 / n)) - 1";
210 (formula, symbolic_formula)
211 };
212 TvmSolution::new(TvmVariable::Rate, continuous_compounding, rate, periods, present_value, future_value, &formula, symbolic_formula)
213}
214
215fn check_rate_parameters(periods: u32, present_value: f64, future_value: f64) {
216 assert!(present_value.is_finite(), "The present value must be finite (not NaN or infinity)");
217 assert!(future_value.is_finite(), "The future value must be finite (not NaN or infinity)");
218 assert!(!(present_value < 0.0 && future_value < 0.0), "The present value and future value are both negative. They must have opposite signs.");
219 assert!(!(present_value > 0.0 && future_value > 0.0), "The present value and future value are both positive. They must have opposite signs.");
220 assert!(!(present_value == 0.0 && future_value != 0.0), "The present value is zero and the future value is nonzero so there's no way to solve for rate.");
221 assert!(!(periods == 0 && present_value + future_value != 0.0), "The number of periods is zero and the present value plus the future value is nonzero so there's no way to solve for rate.");
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn test_rate_edge() {
230 // Zero periods, values add up to zero.
231 assert_rounded_6(0.0, rate(0, 10_000.0, -10_000.0, false));
232
233 // Nonzero periods, values add up to zero.
234 assert_rounded_6(0.0, rate(12, -10_000.0, 10_000.0, false));
235 }
236
237 #[should_panic]
238 #[test]
239 fn test_rate_err_present_value_nan() {
240 // The present value is not a number.
241 rate(12, std::f64::NAN, 1_000.0, false);
242 }
243
244 #[should_panic]
245 #[test]
246 fn test_rate_err_present_value_inf() {
247 // The present value is infinite.
248 rate(12, std::f64::INFINITY, 1_000.0, false);
249 }
250
251 #[should_panic]
252 #[test]
253 fn test_rate_err_future_value_nan() {
254 // The future value is not a number.
255 rate(12, 1_000.0, std::f64::NAN, false);
256 }
257
258 #[should_panic]
259 #[test]
260 fn test_rate_err_future_value_inf() {
261 // The future value is infinite.
262 rate(12, 1_000.0, std::f64::NEG_INFINITY, false);
263 }
264
265 #[should_panic]
266 #[test]
267 fn test_rate_err_zero_periods() {
268 // Zero periods, values don't add up to zero.
269 rate(0, 10_000.0, 10_000.0, false);
270 }
271
272 /*
273 macro_rules! compare_to_excel {
274 ( $n:expr, $pv:expr, $fv:expr, $r_excel:expr, $r_manual_simple:expr, $r_manual_cont:expr ) => {
275 println!("$n = {}, $pv = {}, $fv = {}, $r_excel: {}, $r_manual_simple = {}, $r_manual_cont = {}", $n, $pv, $fv, $r_excel, $r_manual_simple, $r_manual_cont);
276 assert_approx_equal!($r_excel, $r_manual_simple);
277
278 let r_calc_simple = rate($n, $pv, $fv, false);
279 println!("r_calc_simple = {}", r_calc_simple);
280 assert_approx_equal!($r_excel, r_calc_simple);
281
282 let r_calc_cont = rate($n, $pv, $fv, true);
283 println!("r_calc_cont = {}", r_calc_cont);
284 assert_approx_equal!($r_manual_cont, r_calc_cont);
285
286 if is_approx_equal!(0.0, r_calc_simple) {
287 assert_approx_equal!(0.0, r_calc_cont);
288 } else {
289 let ratio = r_calc_cont / r_calc_simple;
290 println!("ratio = {}", ratio);
291 if $r_excel < 0.0 {
292 assert!(ratio >= 1.0);
293 assert!(ratio <= 2.0);
294 } else {
295 assert!(ratio >= 0.0);
296 assert!(ratio <= 1.0);
297 }
298 }
299 }
300 }
301 */
302
303 fn compare_to_excel (test_case: usize, n: u32, pv: f64, fv: f64, r_excel: f64, r_manual_simple: f64, r_manual_cont: f64) {
304 let display = false;
305
306 if display { println!("test_case = {}, n = {}, pv = {}, fv = {}, r_excel: {}, r_manual_simple = {}, r_manual_cont = {}", test_case, n, pv, fv, r_excel, r_manual_simple, r_manual_cont) }
307 assert_approx_equal!(r_excel, r_manual_simple);
308
309 let r_calc_simple = rate(n, pv, fv,false);
310 if display { println!("r_calc_simple = {}", r_calc_simple) }
311 assert_approx_equal!(r_excel, r_calc_simple);
312
313 let r_calc_cont = rate(n, pv, fv, true);
314 if display { println!("r_calc_cont = {}", r_calc_cont); }
315 assert_approx_equal!(r_manual_cont, r_calc_cont);
316
317 if is_approx_equal!(0.0, r_calc_simple) {
318 assert_approx_equal!(0.0, r_calc_cont);
319 } else {
320 let ratio = r_calc_cont / r_calc_simple;
321 if display { println!("ratio = {}", ratio) };
322 if r_excel < 0.0 {
323 assert!(ratio >= 1.0);
324 assert!(ratio <= 2.0);
325 } else {
326 assert!(ratio >= 0.0);
327 assert!(ratio <= 1.0);
328 }
329 }
330
331 // Solution with simple compounding.
332 let solution = rate_solution(n, pv, fv, false);
333 if display { dbg!(&solution); }
334 solution.invariant();
335 assert!(solution.calculated_field().is_rate());
336 assert_eq!(false, solution.continuous_compounding());
337 assert_approx_equal!(r_excel, solution.rate());
338 assert_eq!(n, solution.periods());
339 assert_approx_equal!(n as f64, solution.fractional_periods());
340 assert_approx_equal!(pv, solution.present_value());
341 assert_approx_equal!(fv, solution.future_value());
342
343 // Solution with continuous compounding.
344 let solution = rate_solution(n, pv, fv, true);
345 if display { dbg!(&solution); }
346 solution.invariant();
347 assert!(solution.calculated_field().is_rate());
348 assert!(solution.continuous_compounding());
349 assert_approx_equal!(r_manual_cont, solution.rate());
350 assert_eq!(n, solution.periods());
351 assert_approx_equal!(n as f64, solution.fractional_periods());
352 assert_approx_equal!(pv, solution.present_value());
353 assert_approx_equal!(fv, solution.future_value());
354 }
355
356 #[test]
357 fn test_rate_against_excel() {
358 compare_to_excel(1, 90, -0.1f64, 1f64, 0.0259143654700119f64, 0.0259143654700098f64, 0.025584278811045f64);
359 compare_to_excel(2, 85, 1.05f64, -1.5f64, 0.00420499208399443f64, 0.00420499208399305f64, 0.00419617581104391f64);
360 compare_to_excel(3, 80, -2.25f64, 2.25f64, 8.10490132135311E-16f64, 0f64, 0f64);
361 compare_to_excel(4, 75, 4.3875f64, -3.375f64, -0.00349207865283533f64, -0.00349207865410572f64, -0.00349819019289988f64);
362 compare_to_excel(5, 70, -10.125f64, 5.0625f64, -0.00985323817917932f64, -0.00985323818144335f64, -0.00990210257942779f64);
363 compare_to_excel(6, 65, 0.759375f64, -7.59375f64, 0.0360593046264088f64, 0.0360593046256343f64, 0.0354243860460622f64);
364 compare_to_excel(7, 60, -7.9734375f64, 11.390625f64, 0.00596228650143506f64, 0.00596228649269048f64, 0.00594458239897887f64);
365 compare_to_excel(8, 55, 17.0859375f64, -17.0859375f64, 4.31995919490311E-13f64, 0f64, 0f64);
366 compare_to_excel(9, 50, -33.317578125f64, 25.62890625f64, -0.00523354233611077f64, -0.00523354233613538f64, -0.00524728528934982f64);
367 compare_to_excel(10, 45, 76.88671875f64, -38.443359375f64, -0.0152852470655657f64, -0.0152852470655688f64, -0.0154032706791099f64);
368 compare_to_excel(11, 40, -5.76650390625f64, 57.6650390625f64, 0.0592537251772898f64, 0.0592537251772889f64, 0.0575646273248511f64);
369 compare_to_excel(12, 35, 60.548291015625f64, -86.49755859375f64, 0.010242814832087f64, 0.0102428148320715f64, 0.0101907126839638f64);
370 compare_to_excel(13, 30, -129.746337890625f64, 129.746337890625f64, 2.53808542775445E-15f64, 0f64, 0f64);
371 compare_to_excel(14, 25, 253.005358886719f64, -194.619506835937f64, -0.0104396946981842f64, -0.0104396947068867f64, -0.0104945705786996f64);
372 compare_to_excel(15, 20, -583.858520507812f64, 291.929260253906f64, -0.0340636710696604f64, -0.0340636710751544f64, -0.0346573590279973f64);
373 compare_to_excel(16, 15, 43.7893890380859f64, -437.893890380859f64, 0.165914401180033f64, 0.165914401179832f64, 0.15350567286627f64);
374 compare_to_excel(17, 12, -459.788584899902f64, 656.840835571289f64, 0.0301690469250706f64, 0.0301690469166949f64, 0.0297229119948944f64);
375 compare_to_excel(18, 10, 985.261253356933f64, -985.261253356933f64, 3.26110008113999E-16f64, 0f64, 0f64);
376 compare_to_excel(19, 7, -1921.25944404602f64, 1477.8918800354f64, -0.0367869049970667f64, -0.0367869049970667f64, -0.0374806092096416f64);
377 compare_to_excel(20, 5, 4433.6756401062f64, -2216.8378200531f64, -0.129449436703859f64, -0.129449436703876f64, -0.138629436111989f64);
378 compare_to_excel(21, 4, -332.525673007965f64, 3325.25673007965f64, 0.778279410038923f64, 0.778279410038923f64, 0.575646273248511f64);
379 compare_to_excel(22, 3, 3491.51956658363f64, -4987.88509511947f64, 0.126247880443697f64, 0.126247880443606f64, 0.118891647979577f64);
380 compare_to_excel(23, 2, -7481.82764267921f64, 7481.82764267921f64, -9.19973496824152E-17f64, 0f64, 0f64);
381 compare_to_excel(24, 1, 14589.5639032245f64, -11222.7414640188f64, -0.230769230769231f64, -0.230769230769231f64, -0.262364264467491f64);
382 }
383}