1use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
8pub struct Money(pub i64);
9
10impl Money {
11 pub const ZERO: Money = Money(0);
12
13 pub fn minor(self) -> i64 {
14 self.0
15 }
16
17 pub fn as_major_f64(self) -> f64 {
20 self.0 as f64 / 100.0
21 }
22
23 pub fn peso(self) -> String {
25 format!(
26 "{}₱{}",
27 if self.0 < 0 { "-" } else { "" },
28 group(self.0.abs())
29 )
30 }
31
32 pub fn plain(self) -> String {
34 format!(
35 "{}{}",
36 if self.0 < 0 { "-" } else { "" },
37 group(self.0.abs())
38 )
39 }
40
41 pub fn trimmed_major(self) -> String {
44 let neg = self.0 < 0;
45 let abs = self.0.abs();
46 let whole = abs / 100;
47 let cents = abs % 100;
48 let body = if cents == 0 {
49 whole.to_string()
50 } else if cents % 10 == 0 {
51 format!("{whole}.{}", cents / 10)
52 } else {
53 format!("{whole}.{cents:02}")
54 };
55 if neg { format!("-{body}") } else { body }
56 }
57}
58
59impl core::ops::Add for Money {
60 type Output = Money;
61 fn add(self, rhs: Money) -> Money {
62 Money(self.0 + rhs.0)
63 }
64}
65
66impl core::ops::Sub for Money {
67 type Output = Money;
68 fn sub(self, rhs: Money) -> Money {
69 Money(self.0 - rhs.0)
70 }
71}
72
73fn group(minor_abs: i64) -> String {
75 let whole = minor_abs / 100;
76 let cents = minor_abs % 100;
77 let digits = whole.to_string();
78 let mut out = String::new();
79 let bytes = digits.as_bytes();
80 for (i, b) in bytes.iter().enumerate() {
81 if i > 0 && (bytes.len() - i).is_multiple_of(3) {
82 out.push(',');
83 }
84 out.push(*b as char);
85 }
86 format!("{out}.{cents:02}")
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn peso_formatting() {
95 assert_eq!(Money(57100).peso(), "₱571.00");
96 assert_eq!(Money(100000).peso(), "₱1,000.00");
97 assert_eq!(Money(123456789).peso(), "₱1,234,567.89");
98 assert_eq!(Money(-4900).peso(), "-₱49.00");
99 assert_eq!(Money(0).peso(), "₱0.00");
100 assert_eq!(Money(5).peso(), "₱0.05");
101 }
102
103 #[test]
104 fn trimmed_major_strips_zeros() {
105 assert_eq!(Money(44000).trimmed_major(), "440");
106 assert_eq!(Money(90950).trimmed_major(), "909.5");
107 assert_eq!(Money(100325).trimmed_major(), "1003.25");
108 assert_eq!(Money(81250).trimmed_major(), "812.5");
109 assert_eq!(Money(9991).trimmed_major(), "99.91");
110 }
111
112 #[test]
113 fn arithmetic() {
114 assert_eq!(
115 (Money(44000) + Money(18000) - Money(4900)).peso(),
116 "₱571.00"
117 );
118 }
119}