1use rust_decimal::Decimal;
4use std::fmt;
5use std::str::FromStr;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub struct Quantity(Decimal);
10
11impl Quantity {
12 pub const ZERO: Self = Self(Decimal::ZERO);
14 pub const ONE: Self = Self(Decimal::ONE);
16
17 pub fn new(value: Decimal) -> Self {
19 Self(value)
20 }
21
22 pub fn parse(s: &str) -> Result<Self, rust_decimal::Error> {
24 Ok(Self(Decimal::from_str(s.trim())?))
25 }
26
27 pub fn raw(self) -> Decimal {
29 self.0
30 }
31
32 pub fn is_negative(self) -> bool {
34 self.0.is_sign_negative() && !self.0.is_zero()
35 }
36}
37
38impl fmt::Display for Quantity {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(f, "{}", self.0)
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
46pub struct Percentage(Decimal);
47
48impl Percentage {
49 pub const ZERO: Self = Self(Decimal::ZERO);
51
52 pub fn new(percent: Decimal) -> Self {
54 Self(percent)
55 }
56
57 pub fn from_fraction(fraction: Decimal) -> Option<Self> {
59 fraction
60 .checked_mul(Decimal::ONE_HUNDRED)
61 .map(|d| Self(d.normalize()))
62 }
63
64 pub fn as_percent(self) -> Decimal {
66 self.0
67 }
68
69 pub fn as_fraction(self) -> Decimal {
71 self.0 / Decimal::ONE_HUNDRED
72 }
73
74 pub fn is_zero(self) -> bool {
76 self.0.is_zero()
77 }
78
79 pub fn is_positive(self) -> bool {
81 self.0 > Decimal::ZERO
82 }
83
84 pub fn is_negative(self) -> bool {
86 self.0 < Decimal::ZERO
87 }
88}
89
90impl fmt::Display for Percentage {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 write!(f, "{}", self.0.normalize())
93 }
94}
95
96impl From<Decimal> for Percentage {
97 fn from(value: Decimal) -> Self {
98 Self::new(value)
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn quantity_may_be_negative() {
108 let q = Quantity::new(Decimal::NEGATIVE_ONE);
109 assert!(q.is_negative());
110 assert_eq!(q.to_string(), "-1");
111 }
112
113 #[test]
114 fn ten_percent_is_not_point_one() {
115 let p = Percentage::new(Decimal::from(10));
116 assert_eq!(p.as_percent(), Decimal::from(10));
117 assert_eq!(p.as_fraction(), Decimal::new(10, 2));
118 assert_eq!(
119 Percentage::new(Decimal::from(19)),
120 Percentage::new(Decimal::new(1900, 2))
121 );
122 }
123}