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