Rounding & math helpers
All rounding uses PHP 8.4’s native \RoundingMode enum — the same one round() and bcround() take. Value objects default to HalfAwayFromZero as a constant and never read config; services read money.rounding (formatter, avgMoney) and money.exchange.rounding (conversions).
| RoundingMode | Config value | 2.5 | -2.5 | 1.6 | -1.1 |
|---|---|---|---|---|---|
HalfAwayFromZero | half_away_from_zero | 3 | -3 | 2 | -1 |
HalfTowardsZero | half_towards_zero | 2 | -2 | 2 | -1 |
HalfEven | half_even | 2 | -2 | 2 | -1 |
HalfOdd | half_odd | 3 | -3 | 2 | -1 |
TowardsZero | towards_zero | 2 | -2 | 1 | -1 |
AwayFromZero | away_from_zero | 3 | -3 | 2 | -2 |
PositiveInfinity | positive_infinity | 3 | -2 | 2 | -1 |
NegativeInfinity | negative_infinity | 2 | -3 | 1 | -2 |
use RoundlyConsulting\Money\Percentage;
use RoundlyConsulting\Money\Ratio;
$ten = Money::ofMajor('10', 'EUR');
$ten->divide(3); // 3.33 EUR (HalfAwayFromZero)
$ten->divide(3, RoundingMode::PositiveInfinity); // 3.34 EUR
$ten->multiply(Ratio::of(2, 3)); // 6.67 EUR — exact ratio, one rounding
$ten->percentage(Percentage::fromBasisPoints(850)); // 0.85 EUR (8.5 %)
Ratio::of('1.0854'); // 5427/5000
Ratio::of(1, 3)->toDecimal(4); // "0.3333"
Ratio::of(1, 3)->toExactDecimal(); // null — does not terminate
Percentage::of('8.5')->basisPoints(); // 850Ratio and Percentage
Ratio is an exact rational — reduced, with a positive denominator and terms of up to 80 digits. Percentage is a non-negative decimal percent with up to 6 fraction digits (Percentage::of(), fromBasisPoints(), fromRatio()). Both feed multiply(), divide() and percentage() without a single float.
MinorUnits
Currency-less helpers for credits, points and provider SDK amounts — int|string in, strings out, scales 0..36:
use RoundlyConsulting\Money\Math\MinorUnits;
MinorUnits::normalize('007'); // "7"
MinorUnits::toDecimal(123450, 2); // "1234.50"
MinorUnits::fromDecimal('10.5', 2); // "1050" — excess precision needs a rounding mode
MinorUnits::rescale(123450, 2, 0); // "1235"
MinorUnits::toInt('6747'); // 6747 — throws AmountOverflow outside int64Rounding from your own config
RoundingModes maps snake_case config values onto the enum. A value that is not set (null or blank) takes the optional third-argument default, or throws when you pass none. Anything else — including the ambiguous half_up — throws InvalidMoneyConfiguration naming the key:
use RoundlyConsulting\Money\Support\RoundingModes;
RoundingModes::fromValue(config('shop.rounding'), 'shop.rounding'); // RoundingMode
RoundingModes::fromValue(config('shop.rounding'), 'shop.rounding', RoundingMode::HalfEven); // null or blank → HalfEven
RoundingModes::fromConfig('money.rounding'); // RoundingMode::HalfAwayFromZero
RoundingModes::toValue(RoundingMode::HalfEven); // "half_even"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 cryptoBy 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.