package decimal import ( "math/big" "github.com/tech/sendico/pkg/merrors" ) // Money represents a monetary amount with currency and precision handling type Money struct { Amount *big.Rat Currency string Scale uint32 // decimal places for this currency } // NewMoney creates a new Money value from a decimal string func NewMoney(amount, currency string, scale uint32) (*Money, error) { rat, err := RatFromString(amount) if err != nil { return nil, err } return &Money{ Amount: rat, Currency: currency, Scale: scale, }, nil } // Add adds two money values (must have same currency) func (m *Money) Add(other *Money) (*Money, error) { if m.Currency != other.Currency { return nil, merrors.InvalidArgument("decimal: currency mismatch: " + m.Currency + " != " + other.Currency) } return &Money{ Amount: AddRat(m.Amount, other.Amount), Currency: m.Currency, Scale: m.Scale, }, nil } // Sub subtracts two money values (must have same currency) func (m *Money) Sub(other *Money) (*Money, error) { if m.Currency != other.Currency { return nil, merrors.InvalidArgument("decimal: currency mismatch: " + m.Currency + " != " + other.Currency) } return &Money{ Amount: SubRat(m.Amount, other.Amount), Currency: m.Currency, Scale: m.Scale, }, nil } // Mul multiplies money by a rational number (for fees, percentages, etc.) func (m *Money) Mul(factor *big.Rat) *Money { return &Money{ Amount: MulRat(m.Amount, factor), Currency: m.Currency, Scale: m.Scale, } } // Div divides money by a rational number func (m *Money) Div(divisor *big.Rat) (*Money, error) { result, err := DivRat(m.Amount, divisor) if err != nil { return nil, err } return &Money{ Amount: result, Currency: m.Currency, Scale: m.Scale, }, nil } // Negate returns the negative of this money value func (m *Money) Negate() *Money { return &Money{ Amount: NegRat(m.Amount), Currency: m.Currency, Scale: m.Scale, } } // Round rounds the money to its scale using the specified rounding mode func (m *Money) Round(mode RoundingMode) (*Money, error) { rounded, err := RoundRatToScale(m.Amount, m.Scale, mode) if err != nil { return nil, err } return &Money{ Amount: rounded, Currency: m.Currency, Scale: m.Scale, }, nil } // String returns the formatted money value func (m *Money) String() string { return FormatRat(m.Amount, m.Scale) + " " + m.Currency } // StringAmount returns just the formatted amount func (m *Money) StringAmount() string { return FormatRat(m.Amount, m.Scale) } // IsZero checks if the money amount is zero func (m *Money) IsZero() bool { return IsZero(m.Amount) } // IsPositive checks if the money amount is positive func (m *Money) IsPositive() bool { return IsPositive(m.Amount) } // IsNegative checks if the money amount is negative func (m *Money) IsNegative() bool { return IsNegative(m.Amount) } // Cmp compares two money values (must have same currency) // Returns -1 if m < other, 0 if m == other, 1 if m > other func (m *Money) Cmp(other *Money) (int, error) { if m.Currency != other.Currency { return 0, merrors.InvalidArgument("decimal: currency mismatch: " + m.Currency + " != " + other.Currency) } return CmpRat(m.Amount, other.Amount), nil }