0

I have a project that stores money as bigint column in a database (storing in cents). I'm planning to rewrite this thing to use BCMATH instead. I don't mind integers, but they give me some terrible rounding errors event stored in cents and I suspect I might have the same rounding errors in BCMATH. The problem arises in situations, like in this pseudocode:

$price = $some_price_in_cents * $store_price_increase; // second value is a float for final price calculation, so the result might have fractions of a cent
$price_total = $price * $qty;
$discount = // some discount in cents (might have fractions of a cent)
$discount *= $qty;
$discounted_price = $price_total - $discount;

When inserting into a database, I do round() on all values in cents. And now I have a record which says:

total price = 12134
discount = 460
discounted price = 11675

Now if I do 12134 - 460 ... I obviously get 11674 and not 11675. I also suspect that if I changed the way things are calculated (eg. multiply everything by the QTY at the end), I'd get even different results.

Would I get this kind of behaviour using BCMATH? Would the result depend on the order of math operations? How would I properly calculate the above using BCMATH and store it in DB (assuming 2 decimal places are required)?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Marius
  • 3,976
  • 5
  • 37
  • 52
  • 1
    I would suggest using a DECIMAL data type to store money instead. –  May 27 '16 at 07:39
  • Unless you're overflowing integers (>= 2^31 on 32-bit or 2^63 on 64-bit), and unless you're performing divisions, you cannot have errors using integer arithmetic. `round()` actually [returns a float](http://php.net/manual/fr/function.round.php), and I suspect you were actually using floats without knowing at some point. I personally store all monies as cents in the database, using integer columns; this is perfectly safe, and works well with multiple currencies (`DECIMAL` has a fixed precision). Storing the money as an integer (in minor units) together with a currency code solves the problem. – BenMorel Jan 10 '18 at 21:37
  • About your money operations, I'd suggest you use a proper [PHP Money library](https://github.com/brick/money) (disclaimer: I authored this one). This one uses pure PHP, GMP or BCMath under the hood, based on what's available, and guarantees safe calculations on monies of any size. Plus, you can convert a Money to and from an integer, to store it safely in the database. – BenMorel Jan 10 '18 at 21:54

1 Answers1

2

I believe this is what you need. Note that bcmath requires strings. The number 2 is to specify how many decimals you need.

$price = bcmul($some_price_in_cents, $store_price_increase, 2);
$price_total = bcmul($price, $qty, 2);
$discount = bcmul($qty, "discount amount", 2);
$discounted_price = bcsub($price_total, $discount, 2);
Hackmodford
  • 3,901
  • 4
  • 35
  • 78