NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Money for Laravel

Arithmetic & comparison

Arithmetic and comparisons work within one currency — mixing currencies throws CurrencyMismatch, and conversion is always an explicit step. Lossless operations never round; lossy ones round exactly once, defaulting to RoundingMode::HalfAwayFromZero:

$price = Money::ofMajor('19.99', 'EUR');
$fee   = Money::ofMinor(250, 'EUR');

$total = $price->add($fee)->multiply(3);   // 67.47 EUR, exact
$net   = $total->subtract($fee);           // 64.97 EUR
$vat   = $total->percentage('20');         // 13.49 EUR — one rounding, HalfAwayFromZero
$net->multiply('1.19');                    // 77.31 EUR — a decimal factor rounds once
$total->divide(4);                         // 16.87 EUR
Money::ofMajor('10.03', 'CHF')->roundTo(5); // 10.05 CHF — 5-Rappen cash rounding
$total->negate();                          // -67.47 EUR
$total->negate()->abs();                   // 67.47 EUR

$total->add(Money::ofMajor('1', 'USD'));   // throws CurrencyMismatch
  • add() / subtract() — variadic, exact, never round.
  • multiply($factor, $rounding) — an integer factor is exact; a decimal string ('1.19') or a Ratio rounds once.
  • divide($divisor, $rounding) — rounds once; zero throws DivisionByZero.
  • percentage($percent, $rounding) — this × p / 100, rounded once.
  • roundTo($minorIncrement, $rounding) — cash rounding to a multiple of the increment, e.g. roundTo(5) for Swiss 5-Rappen steps.
  • negate() / abs() — never round.

Factors and divisors must be plain decimals or integers — '1e3', '1,5' or ' 2' throw InvalidAmount. Results are capped at 65 digits.

Comparison

$a = Money::ofMajor('50', 'EUR');
$b = Money::ofMajor('67.47', 'EUR');

$b->isGreaterThan($a);                    // true
$b->isGreaterThanOrEqualTo($a);           // true
$a->isLessThan($b);                       // true
$a->isLessThanOrEqualTo($b);              // true
$a->compareTo($b);                        // -1
$a->equals(Money::ofMinor(5000, 'EUR'));  // true
$a->equals(Money::ofMajor('50', 'USD'));  // false — equals() never throws
$a->isSameCurrency($b);                   // true
$a->isZero(); $a->isPositive(); $a->isNegative();

$a->isGreaterThan(Money::ofMajor('1', 'USD')); // throws CurrencyMismatch

equals() compares the amount, the currency code and the exponent, and never throws. Every ordering method throws CurrencyMismatch across currencies. Comparisons run on the canonical strings — no bcmath, no float.

Aggregates

$prices = [
    Money::ofMajor('10', 'EUR'),
    Money::ofMajor('25.50', 'EUR'),
    Money::ofMajor('4.99', 'EUR'),
];

Money::sum($prices);                        // 40.49 EUR
Money::sum([], currencyIfEmpty: 'EUR');     // 0.00 EUR
Money::min($prices);                        // 4.99 EUR
Money::max($prices);                        // 25.50 EUR
Money::average($prices);                    // 13.50 EUR — rounded once

sum() of an empty list needs currencyIfEmpty, otherwise it throws EmptyMoneyCollection — as do min(), max() and average() of nothing. Non-Money items throw InvalidMoneyValue; mixed currencies throw CurrencyMismatch.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.