use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Money(pub i64);
impl Money {
pub const ZERO: Money = Money(0);
pub fn minor(self) -> i64 {
self.0
}
pub fn as_major_f64(self) -> f64 {
self.0 as f64 / 100.0
}
pub fn peso(self) -> String {
format!(
"{}₱{}",
if self.0 < 0 { "-" } else { "" },
group(self.0.abs())
)
}
pub fn plain(self) -> String {
format!(
"{}{}",
if self.0 < 0 { "-" } else { "" },
group(self.0.abs())
)
}
pub fn trimmed_major(self) -> String {
let neg = self.0 < 0;
let abs = self.0.abs();
let whole = abs / 100;
let cents = abs % 100;
let body = if cents == 0 {
whole.to_string()
} else if cents % 10 == 0 {
format!("{whole}.{}", cents / 10)
} else {
format!("{whole}.{cents:02}")
};
if neg { format!("-{body}") } else { body }
}
}
impl core::ops::Add for Money {
type Output = Money;
fn add(self, rhs: Money) -> Money {
Money(self.0 + rhs.0)
}
}
impl core::ops::Sub for Money {
type Output = Money;
fn sub(self, rhs: Money) -> Money {
Money(self.0 - rhs.0)
}
}
fn group(minor_abs: i64) -> String {
let whole = minor_abs / 100;
let cents = minor_abs % 100;
let digits = whole.to_string();
let mut out = String::new();
let bytes = digits.as_bytes();
for (i, b) in bytes.iter().enumerate() {
if i > 0 && (bytes.len() - i).is_multiple_of(3) {
out.push(',');
}
out.push(*b as char);
}
format!("{out}.{cents:02}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn peso_formatting() {
assert_eq!(Money(57100).peso(), "₱571.00");
assert_eq!(Money(100000).peso(), "₱1,000.00");
assert_eq!(Money(123456789).peso(), "₱1,234,567.89");
assert_eq!(Money(-4900).peso(), "-₱49.00");
assert_eq!(Money(0).peso(), "₱0.00");
assert_eq!(Money(5).peso(), "₱0.05");
}
#[test]
fn trimmed_major_strips_zeros() {
assert_eq!(Money(44000).trimmed_major(), "440");
assert_eq!(Money(90950).trimmed_major(), "909.5");
assert_eq!(Money(100325).trimmed_major(), "1003.25");
assert_eq!(Money(81250).trimmed_major(), "812.5");
assert_eq!(Money(9991).trimmed_major(), "99.91");
}
#[test]
fn arithmetic() {
assert_eq!(
(Money(44000) + Money(18000) - Money(4900)).peso(),
"₱571.00"
);
}
}