Money Operations

The world.money.Money Money type represents monetary values with type-safe operations.

Creating Money Values

Direct Construction

Create world.money.Money values with explicit currency types:

import world.money.*
import world.money.currency.Currencies

val price = Money[Currencies.KES](100.50)
val cost = Money[Currencies.GBP](75.00)
val amount = Money[Currencies.OMR](BigDecimal("123.456"))

From String

Parse monetary values from strings:

import world.money.*
import world.money.currency.Currencies

val result = Money.fromString[Currencies.KES]("250.75")
result match
  case Right(money) => println(s"Parsed: ${money.value.unwrap}")
  case Left(error)  => println(s"Error: ${error.message}")

Money Properties

world.money.Money values expose two properties:

import world.money.*
import world.money.currency.Currencies

val price = Money[Currencies.KES](100.50)

price.value     // CurrencyValue(100.50)
price.currency  // Currencies.KES

Note: The property is value, not amount. Access the underlying BigDecimal via value.unwrap.

Arithmetic Operations

Addition and Subtraction

Money values of the same currency can be added or subtracted:

import world.money.*
import world.money.currency.Currencies

val base = Money[Currencies.KES](1000)
val additional = Money[Currencies.KES](500)

val sum = base + additional       // Money[KES](1500)
val difference = base - additional // Money[KES](500)

Attempting to add or subtract different currencies results in a compile-time type error.

Multiplication

Money values can be multiplied by numeric values:

import world.money.*
import world.money.currency.Currencies

val price = Money[Currencies.OMR](50.000)

val doubled = price * 2              // Money[OMR](100.000)
val tripled = price * 3L             // Money[OMR](150.000)
val adjusted = price * BigDecimal(1.5) // Money[OMR](75.000)

Supports Int, Long, Double, and BigDecimal multipliers.

Division

Division operations return Either[ArithmeticError, Money[C]] to handle division by zero:

import world.money.*
import world.money.currency.Currencies

val price = Money[Currencies.GBP](100)

val half = price / 2              // Right(Money[GBP](50))
val third = price / 3             // Right(Money[GBP](33.33...))
val invalid = price / 0           // Left(ArithmeticError(...))

Negation

Money values can be negated:

import world.money.*
import world.money.currency.Currencies

val credit = Money[Currencies.KES](1000)
val debit = -credit               // Money[KES](-1000)

Comparison Operations

Money values of the same currency can be compared:

import world.money.*
import world.money.currency.Currencies

val price1 = Money[Currencies.GBP](100)
val price2 = Money[Currencies.GBP](150)

price1 < price2   // true
price1 <= price2  // true
price1 > price2   // false
price1 >= price2  // false
price1 == price2  // false

// Or use compare explicitly
price1.compare(price2)  // -1

Attempting to compare different currencies results in a compile-time type error.

Utility Methods

Absolute Value and Signum

import world.money.*
import world.money.currency.Currencies

val negative = Money[Currencies.OMR](-50.000)
val positive = negative.abs         // Money[OMR](50.000)

val sign = negative.signum          // -1
val zeroSign = Money[Currencies.OMR](0).signum  // 0

Min and Max

import world.money.*
import world.money.currency.Currencies

val amount1 = Money[Currencies.KES](1000)
val amount2 = Money[Currencies.KES](1500)

val smaller = amount1.min(amount2)  // Money[KES](1000)
val larger = amount1.max(amount2)   // Money[KES](1500)

Precision and Rounding

Money uses BigDecimal for precise decimal arithmetic:

import world.money.*
import world.money.currency.Currencies

val precise = Money[Currencies.KES](0.1) + Money[Currencies.KES](0.2)
// Money[KES](0.3) - no floating-point errors

Rounding to Currency Precision

Round to the currency's defined minor units:

import world.money.*
import world.money.currency.Currencies

val amount = Money[Currencies.OMR](10.6666)

// Round to default precision (3 decimal places for OMR)
val rounded = amount.roundToDefault  // Money[OMR](10.667)

// Or simply
val alsoRounded = amount.rounded     // Money[OMR](10.667)

Custom Rounding

import world.money.*
import world.money.currency.Currencies
import java.math.RoundingMode

val amount = Money[Currencies.KES](10.666)

// Round with specific rounding mode
val down = amount.rounded(RoundingMode.DOWN)  // Money[KES](10.66)
val up = amount.rounded(RoundingMode.UP)      // Money[KES](10.67)

Working with Zero

Create zero-value money:

import world.money.*
import world.money.currency.Currencies

val zero = Money.zero[Currencies.KES]
val alsoZero = Money[Currencies.GBP](0)

Check for zero:

import world.money.*
import world.money.currency.Currencies

val amount = Money[Currencies.OMR](0)
amount.value.unwrap == 0  // true
amount.signum == 0        // true

Allocation

Allocate money proportionally whilst preserving accuracy:

import world.money.*
import world.money.currency.Currencies

val total = Money[Currencies.KES](1000)

// Allocate in ratio 3:2:1
val allocated = total.allocate(3, 2, 1)

allocated match
  case Right(List(first, second, third)) =>
    // first:  Money[KES](500.00)
    // second: Money[KES](333.33)
    // third:  Money[KES](166.67)
    // Total preserved: 500 + 333.33 + 166.67 = 1000
  case Left(error) =>
    println(s"Allocation error: ${error.message}")

The allocation algorithm ensures the sum of allocated amounts equals the original total, distributing any rounding remainder across the parts.

Working with Collections

Aggregation

import world.money.*
import world.money.currency.Currencies

val prices = List(
  Money[Currencies.GBP](10.99),
  Money[Currencies.GBP](25.50),
  Money[Currencies.GBP](5.00)
)

// Calculate total
val total = prices.total              // Money[GBP](41.49)

// Calculate average
val average = prices.average          // Some(Money[GBP](13.83))

// Empty list
val empty = List.empty[Money[Currencies.GBP]]
empty.total                           // Money[GBP](0)
empty.average                         // None

Filtering and Mapping

import world.money.*
import world.money.currency.Currencies

val prices = List(
  Money[Currencies.KES](1000),
  Money[Currencies.KES](2500),
  Money[Currencies.KES](500)
)

// Filter by value
val expensive = prices.filter(_.value.unwrap > 1000)

// Apply discount
val discounted = prices.map(_ * 0.9)

Formatting

world.money.Money integrates with the world.format.Formatter typeclass:

import world.money.*
import world.money.currency.Currencies
import world.format.Formatter

val amount = Money[Currencies.OMR](1234.567)
val text = amount.formatted  // Uses Formatter[Money[Currencies.OMR]]

Note: The method is formatted, not format.

Custom Formatting

Implement custom money formatters:

import world.money.*
import world.money.currency.Currencies

def formatWithSymbol[C <: Currency](money: Money[C]): String =
  val symbol = money.currency.code.value match
    case "GBP" => "£"
    case "OMR" => "ر.ع."
    case "KES" => "KSh"
    case code  => code

  s"$symbol ${money.value.unwrap}"

val price = Money[Currencies.GBP](99.99)
formatWithSymbol(price)  // "£ 99.99"

Validation

Validate monetary amounts:

import world.money.*
import world.money.currency.Currencies

def validatePositive[C <: Currency](amount: Money[C]): Either[String, Money[C]] =
  if amount.signum > 0 then
    Right(amount)
  else
    Left("Amount must be positive")

validatePositive(Money[Currencies.KES](1000))   // Right(Money[KES](1000))
validatePositive(Money[Currencies.KES](-500))   // Left("Amount must be positive")

API Reference

See world.money.Money for the complete API documentation.