use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::{Add, Sub};
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Serialize,
Deserialize,
PartialOrd,
Ord,
Default,
Hash,
rkyv::Archive,
rkyv::Serialize,
rkyv::Deserialize,
)]
pub struct Money(pub i64);
impl Money {
pub const MONEY_SCALE_PER_USD: i64 = 10_000;
pub const MONEY_SCALE_PER_CENT: i64 = Self::MONEY_SCALE_PER_USD / 100;
pub fn zero() -> Self {
Money(0)
}
pub fn is_positive(self) -> bool {
self.0 > 0
}
pub fn saturating_sub(self, other: Money) -> Money {
Money(self.0.saturating_sub(other.0))
}
pub fn saturating_add(self, other: Money) -> Money {
Money(self.0.saturating_add(other.0))
}
pub fn clamp_non_negative(self) -> Money {
Money(self.0.max(0))
}
pub fn from_cents(cents: i64) -> Self {
Money(cents.saturating_mul(Self::MONEY_SCALE_PER_CENT))
}
pub fn to_cents_trunc(self) -> i64 {
self.0 / Self::MONEY_SCALE_PER_CENT
}
}
impl Add for Money {
type Output = Money;
fn add(self, rhs: Money) -> Money {
match self.0.checked_add(rhs.0) {
Some(v) => Money(v),
None => {
debug_assert!(false, "Money add overflow: {} + {}", self.0, rhs.0);
Money(self.0.saturating_add(rhs.0))
}
}
}
}
impl Sub for Money {
type Output = Money;
fn sub(self, rhs: Money) -> Money {
match self.0.checked_sub(rhs.0) {
Some(v) => Money(v),
None => {
debug_assert!(false, "Money sub overflow: {} - {}", self.0, rhs.0);
Money(self.0.saturating_sub(rhs.0))
}
}
}
}
impl fmt::Display for Money {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let abs = (self.0 as i128).abs();
let whole = abs / Self::MONEY_SCALE_PER_USD as i128;
let frac = abs % Self::MONEY_SCALE_PER_USD as i128;
if self.0 < 0 {
write!(f, "-{}.{:04}", whole, frac)
} else {
write!(f, "{}.{:04}", whole, frac)
}
}
}