use crate::Currency;
use crate::Decimal;
use crate::MoneyError;
use crate::dec;
use crate::fmt::format_with_separator;
use crate::fmt::{CODE_FORMAT, CODE_FORMAT_MINOR, SYMBOL_FORMAT, SYMBOL_FORMAT_MINOR, format};
use crate::split_alloc_ops::Split;
use rust_decimal::MathematicalOps;
use rust_decimal::RoundingStrategy as DecimalRoundingStrategy;
use rust_decimal::prelude::FromPrimitive;
use std::fmt::Debug;
use std::ops::Neg;
use std::str::FromStr;
pub trait BaseMoney<C: Currency>: Clone {
fn from_decimal(amount: Decimal) -> Self;
fn amount(&self) -> Decimal;
fn minor_amount(&self) -> Option<i128>;
#[inline]
fn new(amount: impl DecimalNumber) -> Result<Self, MoneyError> {
Ok(Self::from_decimal(
amount.get_decimal().ok_or(MoneyError::OverflowError)?,
))
}
#[inline]
fn from_minor(minor_amount: i128) -> Result<Self, MoneyError> {
Ok(Self::from_decimal(
Decimal::from_i128(minor_amount)
.ok_or(MoneyError::OverflowError)?
.checked_div(
dec!(10)
.checked_powu(C::MINOR_UNIT.into())
.ok_or(MoneyError::OverflowError)?,
)
.ok_or(MoneyError::OverflowError)?,
))
}
#[inline]
fn round(self) -> Self {
Self::from_decimal(self.amount().round_dp(C::MINOR_UNIT.into()))
}
#[inline]
fn round_with(self, decimal_points: u32, strategy: RoundingStrategy) -> Self {
Self::from_decimal(
self.amount()
.round_dp_with_strategy(decimal_points, strategy.into()),
)
}
#[inline]
fn truncate(&self) -> Self {
Self::from_decimal(self.amount().trunc())
}
#[inline]
fn truncate_with(&self, scale: u32) -> Self {
Self::from_decimal(self.amount().trunc_with_scale(scale))
}
#[inline]
fn name(&self) -> &str {
C::NAME
}
#[inline]
fn symbol(&self) -> &str {
C::SYMBOL
}
#[inline]
fn code(&self) -> &str {
C::CODE
}
#[inline]
fn numeric_code(&self) -> i32 {
C::NUMERIC.into()
}
#[inline]
fn minor_unit(&self) -> u16 {
C::MINOR_UNIT
}
#[inline]
fn thousand_separator(&self) -> &str {
C::THOUSAND_SEPARATOR
}
#[inline]
fn decimal_separator(&self) -> &str {
C::DECIMAL_SEPARATOR
}
#[inline]
fn is_zero(&self) -> bool {
self.amount().is_zero()
}
#[inline]
fn is_positive(&self) -> bool {
if self.is_zero() {
return false;
}
self.amount().is_sign_positive()
}
#[inline]
fn is_negative(&self) -> bool {
if self.is_zero() {
return false;
}
self.amount().is_sign_negative()
}
#[inline]
fn mantissa(&self) -> i128 {
self.amount().mantissa()
}
#[inline]
fn fraction(&self) -> Decimal {
self.amount().fract()
}
#[inline]
fn scale(&self) -> u32 {
self.amount().scale()
}
fn format_code(&self) -> String {
format(self, CODE_FORMAT)
}
fn format_symbol(&self) -> String {
format(self, SYMBOL_FORMAT)
}
fn format_code_minor(&self) -> String {
format(self, CODE_FORMAT_MINOR)
}
fn format_symbol_minor(&self) -> String {
format(self, SYMBOL_FORMAT_MINOR)
}
fn display(&self) -> String {
self.format_code()
}
}
pub trait BaseOps<C: Currency>: BaseMoney<C> + Neg<Output = Self> {
#[inline]
fn is_approx<M, T>(&self, m: M, tolerance: T) -> bool
where
M: BaseMoney<C> + BaseOps<C> + Amount<C>,
T: DecimalNumber,
{
self.checked_sub(m).is_some_and(|diff| {
tolerance
.get_decimal()
.is_some_and(|tol| tol >= diff.abs().amount())
})
}
#[inline(always)]
fn abs(&self) -> Self {
Self::from_decimal(self.amount().abs())
}
#[inline(always)]
fn checked_add<RHS>(&self, rhs: RHS) -> Option<Self>
where
RHS: Amount<C>,
{
Some(Self::from_decimal(
self.amount().checked_add(rhs.get_decimal()?)?,
))
}
fn checked_sub<RHS>(&self, rhs: RHS) -> Option<Self>
where
RHS: Amount<C>,
{
Some(Self::from_decimal(
self.amount().checked_sub(rhs.get_decimal()?)?,
))
}
fn checked_mul<RHS>(&self, rhs: RHS) -> Option<Self>
where
RHS: DecimalNumber,
{
Some(Self::from_decimal(
self.amount().checked_mul(rhs.get_decimal()?)?,
))
}
fn checked_div<RHS>(&self, rhs: RHS) -> Option<Self>
where
RHS: DecimalNumber,
{
Some(Self::from_decimal(
self.amount().checked_div(rhs.get_decimal()?)?,
))
}
fn checked_rem<RHS>(&self, rhs: RHS) -> Option<Self>
where
RHS: DecimalNumber,
{
Some(Self::from_decimal(
self.amount().checked_rem(rhs.get_decimal()?)?,
))
}
fn split<P, R>(&self, p: P) -> Option<R>
where
R: Split<Self, C, P>,
{
R::split(self, p)
}
}
pub trait IterOps<C: Currency> {
type Item;
fn checked_sum(&self) -> Option<Self::Item>;
fn mean(&self) -> Option<Self::Item>;
fn median(&self) -> Option<Self::Item>;
fn mode(&self) -> Option<Vec<Self::Item>>;
}
pub trait Amount<C: Currency> {
fn get_decimal(&self) -> Option<Decimal>;
}
impl<C: Currency> Amount<C> for Decimal {
#[inline(always)]
fn get_decimal(&self) -> Option<Decimal> {
Some(*self)
}
}
impl<C: Currency> Amount<C> for f64 {
#[inline(always)]
fn get_decimal(&self) -> Option<Decimal> {
Decimal::from_f64(*self)
}
}
impl<C: Currency> Amount<C> for i32 {
#[inline(always)]
fn get_decimal(&self) -> Option<Decimal> {
Decimal::from_i32(*self)
}
}
impl<C: Currency> Amount<C> for i64 {
#[inline(always)]
fn get_decimal(&self) -> Option<Decimal> {
Decimal::from_i64(*self)
}
}
impl<C: Currency> Amount<C> for i128 {
#[inline(always)]
fn get_decimal(&self) -> Option<Decimal> {
Decimal::from_i128(*self)
}
}
pub trait DecimalNumber {
fn get_decimal(&self) -> Option<Decimal>;
}
impl DecimalNumber for Decimal {
#[inline(always)]
fn get_decimal(&self) -> Option<Decimal> {
Some(*self)
}
}
impl DecimalNumber for f64 {
#[inline(always)]
fn get_decimal(&self) -> Option<Decimal> {
Decimal::from_f64(*self)
}
}
impl DecimalNumber for i32 {
#[inline(always)]
fn get_decimal(&self) -> Option<Decimal> {
Decimal::from_i32(*self)
}
}
impl DecimalNumber for i64 {
#[inline(always)]
fn get_decimal(&self) -> Option<Decimal> {
Decimal::from_i64(*self)
}
}
impl DecimalNumber for i128 {
#[inline(always)]
fn get_decimal(&self) -> Option<Decimal> {
Decimal::from_i128(*self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RoundingStrategy {
#[default]
BankersRounding,
HalfUp,
HalfDown,
Ceil,
Floor,
}
impl From<RoundingStrategy> for DecimalRoundingStrategy {
fn from(value: RoundingStrategy) -> Self {
match value {
RoundingStrategy::BankersRounding => DecimalRoundingStrategy::MidpointNearestEven,
RoundingStrategy::HalfUp => DecimalRoundingStrategy::MidpointAwayFromZero,
RoundingStrategy::HalfDown => DecimalRoundingStrategy::MidpointTowardZero,
RoundingStrategy::Ceil => DecimalRoundingStrategy::AwayFromZero,
RoundingStrategy::Floor => DecimalRoundingStrategy::ToZero,
}
}
}
pub trait MoneyParser<C: Currency>: BaseMoney<C> {
fn from_str_code_with(
money_str: &str,
thousand_separator: &str,
decimal_separator: &str,
) -> Result<Self, MoneyError> {
let amount = Decimal::from_str(&crate::parse::parse_str_code::<C>(
money_str,
thousand_separator,
decimal_separator,
)?)
.map_err(|err| {
MoneyError::ParseStrError(format!("failed parsing {} into decimal", err).into())
})?;
Ok(Self::from_decimal(amount))
}
fn from_str_symbol_with(
money_str: &str,
thousand_separator: &str,
decimal_separator: &str,
) -> Result<Self, MoneyError> {
let amount = Decimal::from_str(&crate::parse::parse_str_symbol::<C>(
money_str,
thousand_separator,
decimal_separator,
)?)
.map_err(|err| {
MoneyError::ParseStrError(format!("failed parsing {} into decimal", err).into())
})?;
Ok(Self::from_decimal(amount))
}
fn from_str_code(money_str: &str) -> Result<Self, MoneyError> {
let amount = Decimal::from_str(&crate::parse::parse_str_code::<C>(
money_str,
C::THOUSAND_SEPARATOR,
C::DECIMAL_SEPARATOR,
)?)
.map_err(|err| {
MoneyError::ParseStrError(format!("failed parsing {} into decimal", err).into())
})?;
Ok(Self::from_decimal(amount))
}
fn from_str_symbol(money_str: &str) -> Result<Self, MoneyError> {
let amount = Decimal::from_str(&crate::parse::parse_str_symbol::<C>(
money_str,
C::THOUSAND_SEPARATOR,
C::DECIMAL_SEPARATOR,
)?)
.map_err(|err| {
MoneyError::ParseStrError(format!("failed parsing {} into decimal", err).into())
})?;
Ok(Self::from_decimal(amount))
}
}
pub trait MoneyFormatter<C: Currency>: BaseMoney<C> {
fn format(&self, format_str: &str) -> String {
format(self, format_str)
}
fn format_with_separator(
&self,
format_str: &str,
thousand_separator: &str,
decimal_separator: &str,
) -> String {
format_with_separator(self, format_str, thousand_separator, decimal_separator)
}
#[cfg(feature = "locale")]
fn format_locale_amount(
&self,
locale_str: &str,
format_str: &str,
) -> Result<String, MoneyError> {
crate::fmt::format_locale_amount(self, locale_str, format_str)
}
}