finance_solution/util/
primitives.rs1use crate::util::error::{
38 require_finite, require_rate, require_rate_gt_minus_one, FinanceError, FinanceResult,
39};
40use std::fmt;
41
42#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
51pub struct Rate(f64);
52
53impl Rate {
54 pub fn finite(value: f64) -> FinanceResult<Self> {
56 require_finite("rate", value)?;
57 Ok(Rate(value))
58 }
59
60 pub fn tvm(value: f64) -> FinanceResult<Self> {
62 require_rate(value)?;
63 Ok(Rate(value))
64 }
65
66 pub fn payment(value: f64) -> FinanceResult<Self> {
68 require_rate_gt_minus_one(value)?;
69 Ok(Rate(value))
70 }
71
72 pub fn positive(value: f64) -> FinanceResult<Self> {
74 require_finite("rate", value)?;
75 if value == 0.0 {
76 return Err(FinanceError::ZeroValue { field: "rate" });
77 }
78 if value < 0.0 {
79 return Err(FinanceError::InvalidRate { rate: value });
80 }
81 Ok(Rate(value))
82 }
83
84 #[inline]
86 pub fn get(self) -> f64 {
87 self.0
88 }
89}
90
91impl From<Rate> for f64 {
92 #[inline]
93 fn from(r: Rate) -> f64 {
94 r.0
95 }
96}
97
98impl TryFrom<f64> for Rate {
99 type Error = FinanceError;
100 fn try_from(value: f64) -> Result<Self, Self::Error> {
102 Rate::tvm(value)
103 }
104}
105
106impl fmt::Display for Rate {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 write!(f, "{}", self.0)
109 }
110}
111
112#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
118pub struct Periods(u32);
119
120impl Periods {
121 pub fn new(value: u32) -> FinanceResult<Self> {
123 Ok(Periods(value))
124 }
125
126 pub fn at_least_one(value: u32) -> FinanceResult<Self> {
128 if value == 0 {
129 return Err(FinanceError::InvalidPeriod {
130 period: 0,
131 periods: 0,
132 message: "periods must be at least 1",
133 });
134 }
135 Ok(Periods(value))
136 }
137
138 #[inline]
139 pub fn get(self) -> u32 {
140 self.0
141 }
142}
143
144impl From<Periods> for u32 {
145 #[inline]
146 fn from(p: Periods) -> u32 {
147 p.0
148 }
149}
150
151impl TryFrom<u32> for Periods {
152 type Error = FinanceError;
153 fn try_from(value: u32) -> Result<Self, Self::Error> {
154 Periods::new(value)
155 }
156}
157
158impl fmt::Display for Periods {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 write!(f, "{}", self.0)
161 }
162}
163
164#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
172pub struct PeriodLength(usize);
173
174impl PeriodLength {
175 pub fn new(n: usize) -> FinanceResult<Self> {
177 if n == 0 {
178 return Err(FinanceError::InvalidPeriod {
179 period: 0,
180 periods: 0,
181 message: "period length must be at least 1",
182 });
183 }
184 Ok(PeriodLength(n))
185 }
186
187 pub const fn new_const(n: usize) -> Self {
192 assert!(n >= 1, "PeriodLength::new_const requires n >= 1");
193 PeriodLength(n)
194 }
195
196 #[inline]
197 pub const fn get(self) -> usize {
198 self.0
199 }
200}
201
202impl From<PeriodLength> for usize {
203 #[inline]
204 fn from(p: PeriodLength) -> usize {
205 p.0
206 }
207}
208
209impl TryFrom<usize> for PeriodLength {
210 type Error = FinanceError;
211 fn try_from(value: usize) -> Result<Self, Self::Error> {
212 PeriodLength::new(value)
213 }
214}
215
216impl fmt::Display for PeriodLength {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 write!(f, "{}", self.0)
219 }
220}
221
222#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
228pub struct PositivePrice(f64);
229
230impl PositivePrice {
231 pub fn new(value: f64) -> FinanceResult<Self> {
232 require_finite("price", value)?;
233 if value <= 0.0 {
234 return Err(FinanceError::InvalidCashflow {
235 message: "price must be strictly positive",
236 });
237 }
238 Ok(PositivePrice(value))
239 }
240
241 #[inline]
242 pub fn get(self) -> f64 {
243 self.0
244 }
245}
246
247impl From<PositivePrice> for f64 {
248 #[inline]
249 fn from(p: PositivePrice) -> f64 {
250 p.0
251 }
252}
253
254impl TryFrom<f64> for PositivePrice {
255 type Error = FinanceError;
256 fn try_from(value: f64) -> Result<Self, Self::Error> {
257 PositivePrice::new(value)
258 }
259}
260
261impl fmt::Display for PositivePrice {
262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263 write!(f, "{}", self.0)
264 }
265}
266
267#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
273pub struct Money(f64);
274
275impl Money {
276 pub fn new(value: f64) -> FinanceResult<Self> {
277 require_finite("money", value)?;
278 Ok(Money(value))
279 }
280
281 pub fn nonzero(value: f64) -> FinanceResult<Self> {
283 require_finite("money", value)?;
284 if value == 0.0 {
285 return Err(FinanceError::ZeroValue { field: "money" });
286 }
287 Ok(Money(value))
288 }
289
290 #[inline]
291 pub fn get(self) -> f64 {
292 self.0
293 }
294}
295
296impl From<Money> for f64 {
297 #[inline]
298 fn from(m: Money) -> f64 {
299 m.0
300 }
301}
302
303impl TryFrom<f64> for Money {
304 type Error = FinanceError;
305 fn try_from(value: f64) -> Result<Self, Self::Error> {
306 Money::new(value)
307 }
308}
309
310impl fmt::Display for Money {
311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312 write!(f, "{}", self.0)
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn rate_domains() {
322 assert!(Rate::tvm(-1.0).is_ok());
323 assert!(Rate::tvm(-1.1).is_err());
324 assert!(Rate::payment(-1.0).is_err());
325 assert!(Rate::positive(0.08).is_ok());
326 assert!(Rate::positive(0.0).is_err());
327 }
328
329 #[test]
330 fn periods_and_length() {
331 assert_eq!(Periods::new(0).unwrap().get(), 0);
332 assert!(Periods::at_least_one(0).is_err());
333 assert_eq!(PeriodLength::new(20).unwrap().get(), 20);
334 assert!(PeriodLength::new(0).is_err());
335 assert_eq!(PeriodLength::new_const(14).get(), 14);
336 }
337
338 #[test]
339 fn price_and_money() {
340 assert!(PositivePrice::new(100.0).is_ok());
341 assert!(PositivePrice::new(0.0).is_err());
342 assert!(Money::new(-50.0).is_ok());
343 assert!(Money::nonzero(0.0).is_err());
344 }
345}