2

Given the fact that a floating point is imprecise and that I should use the the BCMath function to get precise results, is it really necessary to use price strings in PHP and add them up with bcadd when making a 'simple' shopping cart?

Given the following example, why shouldn't I use a rounded float?

I'm looking for a best practice for calculating the order total for a shopping cart.

    $floatTotal = 0.00;
$stringTotal = '0.00';

for($i=0; $i<1000; $i++) {
    $floatTotal += 0.1; 
}
echo "float value: ";
printf('%.40f', $floatTotal); //99.9999999999985931253831950016319751739502

echo "<br>\n";

echo "rounded float value: ". round($floatTotal); //100

echo "<br>\n";

for($i=0; $i<1000; $i++) {
    $stringTotal = bcadd($stringTotal, '0.1', 2);
}

echo 'string value: '.$stringTotal.'<br>'; //100.00
Youri
  • 73
  • 1
  • 3
  • 4
    Store your currency amounts as pennies. Then converting to regular dollar values is a simple matter of dividing by 100, and also eliminates any floating point screwiness. – Marc B Apr 19 '11 at 22:32
  • But how necessary is it with a relative simple shopping cart? Do you ever get rounding errors? – Youri Apr 19 '11 at 22:34
  • 1
    If you're dealing with currency values, then don't use floating point, period. No matter how complicated or how simple. Either store the currency values as pennies in an INT field, or use whatever your database's "decimal" type is, which is FIXED point storage. It's not a question or how frequent/rare floating point error is. It exists, and unless you want to become an expert on IEEE 754, take the easy route and don't use them – Marc B Apr 19 '11 at 22:36
  • @Marc B: His question doesn't specifically deal with persistent storage. 25% of 185 pennies yields a 46.25 discount. Floating points will rear their ugly heads, small units or not. – webbiedave Apr 19 '11 at 22:49
  • You'd have to round regardless, but storing as penny-ints removes at least one source of float-induced errors. The difference might only come out to a single penny for "small" errors, but somewhere there's someone watching Superman III and cackling evilly. – Marc B Apr 19 '11 at 22:58
  • But for rounding tax for example the best way it to use bcmul? for example: echo bcmul('150', '1.19', 2); and then round it for 150 cents and 19% tax – Youri Apr 19 '11 at 23:00

2 Answers2

7

You should never use floating point values for currencies. They are not accurate. Instead, store monetary values in the smallest units (cents, pennies, etc.) so you can use integers. In that case there's no reason to use the bcmath library unless you are dealing with huge values which is rather unlikely.

ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
  • I store my currencies as DECIMAL in a MySQL db, I know the theory but how necessary is it for a simple shopping cart? – Youri Apr 19 '11 at 22:36
  • 1
    Couldn't agree more. Working with floating point numbers for currency is nothing but trouble. Stick to cents and you'll be glad you did. – 0x6A75616E Apr 19 '11 at 22:37
  • See http://stackoverflow.com/questions/812815/php-intval-and-floor-return-value-that-is-too-low – ThiefMaster Apr 19 '11 at 22:38
  • 1
    @Youri regardless of the application, storing money as cents is much more precise. Even in a small shopping cart, once you have to apply taxes, coupons, discounts, etc you'll have to stard rounding up or down which is bad. Using cents is not any more complicated and the advantages are huge. – 0x6A75616E Apr 19 '11 at 22:39
  • So working with cents is even more best practice than storing the values as DECIMAL in the db and working with BCMath in php? – Youri Apr 19 '11 at 22:42
0

Despite this being asked a while ago and aside from storing the values using the smallest denominator.

PHP has a way of messing things up in the long run without using BCMath, but it depends on the precision you set in PHP, regardless of the float value size. IIRC MySQL will output your decimal values as strings to PHP not floats. Performing any math on the string values from within PHP will convert them to a float. The BC math functions also do not round the values, they are truncated to the defined precision.

That said, since you are storing your values as decimal in MySQL you can perform your math from the database instead of utilizing the very costly PHP bcmath functions.

Reference: https://dev.mysql.com/doc/refman/5.0/en/precision-math.html

Possibly even create views, triggers, or stored procedures for processing the values to save you time and for consistency or for working with an ORM.

SELECT ((quantity * price) * tax) AS total FROM Table1;  

http://sqlfiddle.com/#!9/05435/1

*Note I set size and scale as 32,16 for the decimal and float columns to display binary precision. I also used the common 0.2*0.3 example.

Will B.
  • 17,883
  • 4
  • 67
  • 69