Open ElvisIsKing666 opened 1 month ago
Hi,
This behaviour is actually expected: the return value of getMinorAmount()
is a BigDecimal
, which may or may not be convertible to an int
. If it's not convertible, a MathException
is thrown, as mentioned in the toInt() docblock.
Why does getMinorAmount()
return a BigDecimal
? Because nothing prevents you from creating a Money with a scale larger than the default scale for the currency (in your case, the piconero). When this happens, the minor amount has decimal places:
use Brick\Money\Context\CustomContext;
use Brick\Money\Currency;
use Brick\Money\Money;
$monero = new Currency('XMR', 0, 'Monero', 12);
echo Money::of('1', $monero)->getMinorAmount(); // 1000000000000
echo Money::of('1', $monero, new CustomContext(15))->getMinorAmount(); // 1000000000000.000
Similar example with USD
:
echo Money::of('1.23', 'USD')->getAmount(); // 1.23
echo Money::of('1.23', 'USD')->getMinorAmount(); // 123
echo Money::of('1.23', 'USD', new CustomContext(6))->getAmount(); // 1.230000
echo Money::of('1.23', 'USD', new CustomContext(6))->getMinorAmount(); // 123.0000
If the decimal places are not all zeros, the BigDecimal
is not convertible to an int
and toInt()
throws a RoundingNecessaryException
.
To be precise, BigDecimal::toInt()
may throw:
RoundingNecessaryException
if the scale of your money is larger than the default scale of the currency, and the minor amount has non-zero decimal places, ex: 1000000000000.001
piconeros or 123.0001
cents in the examples above;IntegerOverflowException
if the resulting integer would overflow the size of an int
on your platform (2147483647
on 32-bit and 9223372036854775807
on 64-bit).(as you can see, on a 32-bit platform, getMinorAmount()->toInt()
would throw an exception for anything above 0.002147483647 XMR if you're using piconero as default scale, so this can easily happen with cryptocurrencies.)
Do you think we can improve the documentation / docblock to clarify this point?
Thanks for this project. It's very helpful. I've been converting a legacy project that used floating point math for currencies and I'm grateful to have something as robust as Money. I found an issue that I could resolve but it flagged up a potential future problem I wanted to share.
I have a monero cryptocurrency class that uses Money under the hood. I define a Currency like this:
I then access the minor amount (piconero) as follows:
I had a test that gave me a strange error about rounding being needed but saw nowhere in this line that rounding should be happening until I looked deeper at toBigInteger():
which calls dividedBy() if the scale is not zero:
et voila - a hidden inaccessible
RoundingMode::UNNECESSARY
- I was able to solve my bug independently - but have something like a hidden call to divideBy() that has a default parameter that is inaccessible seems like a problem.