Skip to content
Advertisement

Calculating tax using moneyphp

I am using the moneyphp/money class to store monetary values. However when calculating the tax owed I have an issue where the calculated tax is a decimal and the library is looking for an integerish value.

Example:

$invoiceTotal = new Money("155" new Currency("USD")); //$1.55
$taxRate= 0.065;
$invoiceTotalWithTax = $invoiceTotal->multiply($taxRate);
echo $invoiceTotalWithTax; //0.10 whereas actual value is 1.55*0.065 = 0.10075
$formatter = new DecimalMoneyFormatter();
$formatter->format($invoiceTotalWithTax); //will return $0.10

From the above example, some fractional cent value is being lost. Individually it’s not a lot, however if we process several thousand invoice in a tax period, the total tax collected will eventually surpass 1 cent.

  • Is there a way to handle these situations with the Money package?
  • If not, then is there another package that can handle this?

Advertisement

Answer

Shameless plug: I don’t know if there’s a way to do it with the moneyphp/money library, but here’s how you can handle this situation with the brick/money library (disclaimer: I authored it).

The option you choose will depend on what you’re trying to achieve.

Option 1: use a Money with the default scale, round up or down

Use this method if you need the result in the default scale for the currency (2 decimal places for USD), and know which rounding to apply:

use BrickMoneyMoney;
use BrickMathRoundingMode;

$invoiceTotal = Money::ofMinor('155', 'USD'); // USD 1.55
// or
$invoiceTotal = Money::of('1.55', 'USD');

$taxRate = '0.065'; // prefer strings over floats!

$totalWithTax = $invoiceTotal->multipliedBy($taxRate, RoundingMode::DOWN); // USD 0.10
$totalWithTax = $invoiceTotal->multipliedBy($taxRate, RoundingMode::UP); // USD 0.11

You have many more rounding modes to choose from. If you don’t provide a rounding mode, and the result does not fit into 2 decimal places, you’ll get an exception.

Option 2: use a Money with a custom scale

If you need to work with a given precision, say 5 decimal places, you can specify this when you create the Money:

use BrickMoneyMoney;
use BrickMoneyContextCustomContext;
use BrickMathRoundingMode;

$invoiceTotal = Money::of('1.55', 'USD', new CustomContext(5)); // USD 1.55000
$taxRate = '0.065';

$totalWithTax = $invoiceTotal->multipliedBy($taxRate); // USD 0.10075

If the result does not fit into 5 decimal places, you’ll need to provide a RoundingMode, or you’ll get an exception.

Option 3: use a Money with auto scale

Use this method to automatically adjust the scale of the result to the correct number of decimal places:

use BrickMoneyMoney;
use BrickMoneyContextAutoContext;
use BrickMathRoundingMode;

$invoiceTotal = Money::of('1.55', 'USD', new AutoContext()); // USD 1.55
$taxRate = '0.065';

$totalWithTax = $invoiceTotal->multipliedBy($taxRate); // USD 0.10075

No rounding mode is involved, but if a division yields a decimal number with an infinite number of digits, you’ll get an exception.

Option 4: use a RationalMoney

A RationalMoney is a money object that represents its amount as a rational number (a fraction). It’s particularly useful when you need to chain several operations with no rounding whatsoever:

use BrickMoneyMoney;
use BrickMathRoundingMode;

$amount = Money::of('1.55', 'USD'); // USD 1.55
$amount = $amount->toRational(); // USD 155/100

$amount = $amount->dividedBy(3); // USD 155/300
$amount = $amount->dividedBy(7); // USD 155/2100

Once you have performed all your operations, you can convert your final number to a decimal Money, using a rounding mode if necessary:

use BrickMoneyContextDefaultContext;
use BrickMoneyContextCustomContext;

$amount->to(new DefaultContext(), RoundingMode::DOWN); // USD 0.07
$amount->to(new CustomContext(6), RoundingMode::DOWN); // USD 0.073809

Final considerations

The brick/money package offers formatting, cash roundings, money allocation, currency conversion, and more. It is based on the brick/math package, that performs calculations on numbers of any scale. Give it a try!

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement