use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign};
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
pub struct Money(Decimal);
impl Money {
pub const ZERO: Money = Money(Decimal::ZERO);
pub const ONE: Money = Money(Decimal::ONE);
pub const SATOSHI: Money = Money(Decimal::from_parts(1, 0, 0, false, 8));
pub fn from_decimal(d: Decimal) -> Self {
Money(d.round_dp(8))
}
pub fn from_str_exact(s: &str) -> Result<Self, rust_decimal::Error> {
Ok(Money(Decimal::from_str(s)?.round_dp(8)))
}
pub fn from_major(amount: i64) -> Self {
Money(Decimal::from(amount))
}
pub fn from_minor(amount: i64, scale: u32) -> Self {
let d = Decimal::from(amount) / Decimal::from(10_u64.pow(scale));
Money(d.round_dp(8))
}
pub fn as_decimal(&self) -> Decimal {
self.0
}
pub fn round_dp(&self, dp: u32) -> Self {
Money(self.0.round_dp(dp))
}
pub fn is_zero(&self) -> bool {
self.0.is_zero()
}
pub fn is_positive(&self) -> bool {
self.0.is_sign_positive()
}
pub fn is_negative(&self) -> bool {
self.0.is_sign_negative()
}
pub fn abs(&self) -> Self {
Money(self.0.abs())
}
pub fn min(self, other: Self) -> Self {
Money(self.0.min(other.0))
}
pub fn max(self, other: Self) -> Self {
Money(self.0.max(other.0))
}
pub fn percentage(&self, rate: Decimal) -> Self {
Money((self.0 * rate / Decimal::from(100)).round_dp(8))
}
pub fn apply_rate(&self, annual_rate: Decimal, days: u32) -> Self {
let daily_rate = annual_rate / Decimal::from(365);
let interest = self.0 * daily_rate * Decimal::from(days);
Money(interest.round_dp(8))
}
pub fn compound(&self, rate: Decimal, periods: u32) -> Self {
let mut factor = Decimal::ONE;
for _ in 0..periods {
factor = factor * (Decimal::ONE + rate);
}
Money((self.0 * factor).round_dp(8))
}
}
impl fmt::Display for Money {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for Money {
type Err = rust_decimal::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Money::from_str_exact(s)
}
}
impl From<Decimal> for Money {
fn from(d: Decimal) -> Self {
Money::from_decimal(d)
}
}
impl From<i32> for Money {
fn from(i: i32) -> Self {
Money::from_major(i as i64)
}
}
impl From<u32> for Money {
fn from(i: u32) -> Self {
Money::from_major(i as i64)
}
}
impl Add for Money {
type Output = Money;
fn add(self, other: Money) -> Money {
Money((self.0 + other.0).round_dp(8))
}
}
impl AddAssign for Money {
fn add_assign(&mut self, other: Money) {
self.0 = (self.0 + other.0).round_dp(8);
}
}
impl Sub for Money {
type Output = Money;
fn sub(self, other: Money) -> Money {
Money((self.0 - other.0).round_dp(8))
}
}
impl SubAssign for Money {
fn sub_assign(&mut self, other: Money) {
self.0 = (self.0 - other.0).round_dp(8);
}
}
impl Mul<Decimal> for Money {
type Output = Money;
fn mul(self, other: Decimal) -> Money {
Money((self.0 * other).round_dp(8))
}
}
impl Div<Decimal> for Money {
type Output = Money;
fn div(self, other: Decimal) -> Money {
Money((self.0 / other).round_dp(8))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
pub struct Rate(Decimal);
impl Rate {
pub const ZERO: Rate = Rate(Decimal::ZERO);
pub const ONE: Rate = Rate(Decimal::ONE);
pub fn from_decimal(d: Decimal) -> Self {
Rate(d)
}
pub fn from_percentage(p: u32) -> Self {
Rate(Decimal::from(p) / Decimal::from(100))
}
pub fn from_bps(bps: u32) -> Self {
Rate(Decimal::from(bps) / Decimal::from(10000))
}
pub fn as_decimal(&self) -> Decimal {
self.0
}
pub fn as_percentage(&self) -> Decimal {
self.0 * Decimal::from(100)
}
pub fn as_bps(&self) -> Decimal {
self.0 * Decimal::from(10000)
}
pub fn daily_rate(&self) -> Rate {
Rate(self.0 / Decimal::from(365))
}
pub fn monthly_rate(&self) -> Rate {
Rate(self.0 / Decimal::from(12))
}
}
impl fmt::Display for Rate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}%", self.as_percentage())
}
}
impl From<Decimal> for Rate {
fn from(d: Decimal) -> Self {
Rate::from_decimal(d)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_money_precision() {
let m = Money::from_str_exact("100.123456789").unwrap();
assert_eq!(m.to_string(), "100.12345679"); }
#[test]
fn test_satoshi_precision() {
let btc = Money::from_minor(100_000_000, 8); assert_eq!(btc, Money::from_major(1));
let sat = Money::from_minor(1, 8); assert_eq!(sat, Money::SATOSHI);
}
#[test]
fn test_interest_calculation() {
let principal = Money::from_major(10_000);
let rate = Rate::from_percentage(5);
let daily_interest = principal.apply_rate(rate.as_decimal(), 1);
assert_eq!(daily_interest.round_dp(2).to_string(), "1.37");
let annual_interest = principal.apply_rate(rate.as_decimal(), 365);
assert_eq!(annual_interest.round_dp(2).to_string(), "500.00");
}
#[test]
fn test_compound_interest() {
let principal = Money::from_major(1_000);
let monthly_rate = Rate::from_percentage(12).monthly_rate();
let final_amount = principal.compound(monthly_rate.as_decimal(), 12);
assert!(final_amount > principal);
}
}