rfinancial/
ppmt.rs

1use crate::{
2    get_f64, get_u32, get_when, Error, InterestPayment, ParaMap, Payment, Result, WhenType,
3};
4/// # Compute the payment against loan principal
5
6/// ## Parameters
7/// * `rate` : an interest rate compounded once per period
8/// * `per` : the payment period to calculate the interest amount
9/// * `nper` : number of compounding periods
10/// * `pv` : a present value
11/// * `fv` : a future value
12/// * `when` : when payments are due [`WhenType`]. Defaults to `When::End`
13///
14/// ## Return:
15/// * `ppmt`: the payment against loan principal
16///
17/// ## Example
18/// ```rust
19/// use rfinancial::*;
20/// let ppmt = PrincipalPayment::from_tuple((0.1 / 12.0, 1, 24, 2000.0, 0.0, WhenType::End));
21/// println!("{:#?}'s ppmt is {:?}", ppmt, ppmt.get());
22/// ```
23
24#[derive(Debug)]
25pub struct PrincipalPayment {
26    rate: f64,
27    per: u32,
28    nper: u32,
29    pv: f64,
30    fv: f64,
31    when: WhenType,
32}
33
34impl PrincipalPayment {
35    /// Instantiate a `PrincipalPayment` instance from a tuple of (`rate`, `per`, `nper`, `pv`, `fv` and `when`) in said order
36    pub fn from_tuple(tup: (f64, u32, u32, f64, f64, WhenType)) -> Self {
37        PrincipalPayment {
38            rate: tup.0,
39            per: tup.1,
40            nper: tup.2,
41            pv: tup.3,
42            fv: tup.4,
43            when: tup.5,
44        }
45    }
46
47    /// Instantiate a `PrincipalPayment` instance from a hash map with keys of (`rate`, `per`, `nper`,`pv`, `fv`, and `when`) in said order
48    /// Since [`HashMap`] requires values of same type, we need to wrap into a variant of enum
49    pub fn from_map(map: ParaMap) -> Result<Self> {
50        let op = |err: Error| {
51            Error::OtherError(format!(
52                "Failed construct an instance of `PrincipalPayment` from: `{:?}` <- {}",
53                map, err
54            ))
55        };
56
57        let rate = get_f64(&map, "rate").map_err(|err| op(err))?;
58        let per = get_u32(&map, "per").map_err(|err| op(err))?;
59        let nper = get_u32(&map, "nper").map_err(|err| op(err))?;
60        let pv = get_f64(&map, "pv").map_err(|err| op(err))?;
61        let fv = get_f64(&map, "fv").map_err(|err| op(err))?;
62        let when = get_when(&map, "when").map_err(|err| op(err))?;
63        Ok(PrincipalPayment {
64            rate,
65            per,
66            nper,
67            pv,
68            fv,
69            when,
70        })
71    }
72
73    fn ppmt(&self) -> Result<Option<f64>> {
74        /*
75            The total payment is made up of payment against principal plus interest.
76            pmt = ppmt + ipmt
77        */
78
79        // total payment
80        let total_pmt =
81            Payment::from_tuple((self.rate, self.nper, self.pv, self.fv, self.when.clone()))
82                .get()?;
83        // interest payment
84        let ipmt = InterestPayment::from_tuple((
85            self.rate,
86            self.per,
87            self.nper,
88            self.pv,
89            self.fv,
90            self.when.clone(),
91        ))
92        .get()?;
93
94        let ppmt = match ipmt {
95            Some(value) => Some(total_pmt - value),
96            None => None,
97        };
98
99        Ok(ppmt)
100    }
101
102    /// Get the interet payment from an instance of `PrincipalPayment`
103    pub fn get(&self) -> Result<Option<f64>> {
104        self.ppmt()
105    }
106}
107
108#[allow(unused_imports)]
109#[cfg(test)]
110mod tests {
111    use crate::*;
112
113    #[test]
114    fn test_ppmt_from_tuple() {
115        let ppmt = PrincipalPayment::from_tuple((0.1 / 12.0, 1, 60, 55000.0, 0.0, WhenType::End));
116        // npf.ppmt(0.1 / 12, 1, 60, 55000)
117        // -710.254125786425
118        let res = ppmt.get().unwrap().unwrap();
119        let tgt = -710.254125786425;
120        assert!(
121            float_close(res, tgt, RTOL, ATOL),
122            "{:#?} v.s. {:#?}",
123            res,
124            tgt
125        );
126    }
127
128    #[test]
129    fn test_ppmt_from_map() {
130        let mut map = ParaMap::new();
131        map.insert("rate".into(), ParaType::F64(0.1 / 12.0));
132        map.insert("per".into(), ParaType::U32(1));
133        map.insert("nper".into(), ParaType::U32(60));
134        map.insert("pv".into(), ParaType::F64(55000.0));
135        map.insert("fv".into(), ParaType::F64(0.0));
136        map.insert("when".into(), ParaType::When(WhenType::End));
137        let ppmt = PrincipalPayment::from_map(map).unwrap();
138        // npf.ppmt(0.1 / 12, 1, 60, 55000)
139        // -710.254125786425
140        let res = ppmt.get().unwrap().unwrap();
141        let tgt = -710.254125786425;
142        assert!(
143            float_close(res, tgt, RTOL, ATOL),
144            "{:#?} v.s. {:#?}",
145            res,
146            tgt
147        );
148    }
149
150    #[test]
151    fn test_ppmt_with_end() {
152        let rate = 0.1 / 12.0;
153        let per = 1;
154        let nper = 60;
155        let pv = 55000.0;
156        let fv = 0.0;
157        let when = WhenType::End;
158
159        let ppmt = PrincipalPayment {
160            rate,
161            per,
162            nper,
163            pv,
164            fv,
165            when,
166        };
167        // npf.ppmt(0.1 / 12, 1, 60, 55000)
168        // -710.254125786425
169        let res = ppmt.get().unwrap().unwrap();
170        let tgt = -710.254125786425;
171        assert!(
172            float_close(res, tgt, RTOL, ATOL),
173            "{:#?} v.s. {:#?}",
174            res,
175            tgt
176        );
177    }
178
179    #[test]
180    fn test_ppmt_with_begin() {
181        let rate = 0.1 / 12.0;
182        let per = 1;
183        let nper = 60;
184        let pv = 55000.0;
185        let fv = 0.0;
186        let when = WhenType::Begin;
187
188        let ppmt = PrincipalPayment {
189            rate,
190            per,
191            nper,
192            pv,
193            fv,
194            when,
195        };
196        // npf.ppmt(0.1 / 12, 1, 60, 55000, 0, 'begin')
197        // -1158.9297115237273
198        let res = ppmt.get().unwrap().unwrap();
199        let tgt = -1158.9297115237273;
200        assert!(
201            float_close(res, tgt, RTOL, ATOL),
202            "{:#?} v.s. {:#?}",
203            res,
204            tgt
205        );
206    }
207
208    #[test]
209    fn test_ppmt_zero_per() {
210        let rate = 0.1 / 12.0;
211        let per = 0;
212        let nper = 24;
213        let pv = 2000.0;
214        let fv = 0.0;
215        let when = WhenType::End;
216
217        let ppmt = PrincipalPayment {
218            rate,
219            per,
220            nper,
221            pv,
222            fv,
223            when,
224        };
225        let res = ppmt.get().unwrap();
226        let tgt = None;
227        assert_eq!(res, tgt, "{:#?} v.s. {:#?}", res, tgt);
228    }
229
230    #[test]
231    fn test_ppmt_err() {
232        let mut map = ParaMap::new();
233        map.insert("Rate".into(), ParaType::F64(0.1 / 12.0));
234        map.insert("per".into(), ParaType::U32(1));
235        map.insert("nper".into(), ParaType::U32(60));
236        map.insert("pv".into(), ParaType::F64(55000.0));
237        map.insert("fv".into(), ParaType::F64(0.0));
238        map.insert("when".into(), ParaType::When(WhenType::End));
239        let ppmt = PrincipalPayment::from_map(map);
240        let cond = ppmt.is_err();
241        assert!(cond);
242    }
243}