6

Possible Duplicate:
PHP Math Precision
Best practice for working with currency values in PHP?

I really hope someone can help. I've been hitting my head against a brick wall on this one.

Here's the situation: I have a checkout which calculates the subtotal after discount to be -£11.50. There is then £11.50 delivery cost to add which IMHO should equal £0.

However when the calculation is run it returns a float 2.8421709430404E-14

In my debug efforts I have done this:

var_dump(
    $build['total'], // float(-11.5)
    $build['delivery'], // float(11.5)
    (($build['total'])+($build['delivery'])) // float(2.8421709430404E-14)
);

However when I do a static calculation:

var_dump((-11.5 + 11.5)); // float(0)

Any ideas?

Community
  • 1
  • 1
Mark Smith
  • 315
  • 2
  • 10
  • 9
    [Float math is flawed by design](http://stackoverflow.com/questions/3726721/php-math-precision). And that's exactly why money-related operations should deal with integers, not floats. – raina77ow Aug 16 '12 at 15:17
  • @raina77ow Yeah, totally. I always ignore fractions of dollars when I balance my check book. – Matt Aug 16 '12 at 15:17
  • 3
    Maybe @raina77ow writes everything in terms of "number of pennies"? Still, the overall point is valid. :-) – ceejayoz Aug 16 '12 at 15:19
  • +1 use DECIMAL instead or as @ceejayoz suggests INTEGER pennies and display using $pennies/100 – Waygood Aug 16 '12 at 15:19
  • @raina77ow but i think integer is not work every time like in bank there is interest in float like 8.25 than how to manage it with integer ? – NullPoiиteя Aug 16 '12 at 15:19
  • More than likely it's a roundoff error due to the imprecision of using float. You should as suggested above use cents or the currency's equivalent. – Chris McKnight Aug 16 '12 at 15:19
  • @ceejayoz That's what I was trying to point out. raina77ow wasn't specific enough, IMHO, when describing *how* to use the integers in money-related operations. – Matt Aug 16 '12 at 15:20
  • 1
    Use integers.Store them in 1000's. Then divide the result by 100 An by that you will have integer +/- operations – MayTheSchwartzBeWithYou Aug 16 '12 at 15:20
  • @Mayankswami There's actually a difference between rounding off pennies and pounds, I think. ) – raina77ow Aug 16 '12 at 15:20
  • 1
    @Matt Of course I was, the linked thread is quite comprehensive, in my opinion. And yes, the question 'why 0.1 + 0.2 != 0.3 ???' seem to appear here every day, so I don't see any point in duplicating the answers as well. ) – raina77ow Aug 16 '12 at 15:22
  • @raina77ow but like in the bank there is no option of rounding off reason being if the money in millions than the 8.25 and a does matter even 8.25 and 8.30 does matter so would you tell hat should do in that switchwation ? – NullPoiиteя Aug 16 '12 at 15:23
  • Thanks All, however please remember that when I did a static calculation the result is correct. I don't believe changing to integers or rounding will help the issue – Mark Smith Aug 16 '12 at 15:31
  • @Mark I think there is something in precision setup my php setup and codepad gives 0 see http://codepad.org/5tvwWdkp – PoX Aug 16 '12 at 15:33

1 Answers1

8

2.8421709430404E-14 is the closest to (but not equal to) zero number PHP can output. The reason it's not exactly 0 lies in the definition of floating point numbers - they're never fully precise.

If you need to work with sensible information that also contains fraction points, I suggest you bring it to integer by multiplying to the fraction.

So $100.54 becomes 10054. After all calculations are done you can then divide back to the fraction.

Example:

$build['total'] = -11.5;
$build['delivery'] = 11.5;

var_dump(
    $build['total'], // float(-11.5)
    $build['delivery'], // float(11.5)
    (int)($build['total']*1000)+(int)($build['delivery']*1000) // int(0)
    (int)(round($build['total'], 4)*1000)+(int)(round($build['delivery'], 4)*1000) // int(0)
);
Inoryy
  • 8,365
  • 2
  • 39
  • 40
  • Integers yield *exact* values. But you can't have any fractions, the the maximum value is relatively small. Floating point numbers allow you to have fractions, very large values, and very small values ... but floating point numbers are always an *approximation*: [PHP Floating point numbers: Warning](http://php.net/manual/en/language.types.float.php) – paulsm4 Aug 16 '12 at 15:27
  • Well then wouldn't that be based on setup. http://codepad.org/5tvwWdkp – PoX Aug 16 '12 at 15:28
  • @Inori I actually tried this before I posted the question but it returned the same result. As I mentioned, if I did the calculation with static values the result is correct. It's only when I use my veriables which when I `var_dump` the contents are the same. – Mark Smith Aug 16 '12 at 15:29
  • @PoX - Exactly my point. Any ideas why when I var_dump the variables they are floats but the calculation fails? – Mark Smith Aug 16 '12 at 15:33
  • @Mark Smith: Please 1) look at this link: [Best practices for working with money in PHP](http://stackoverflow.com/questions/3819508/best-practice-for-working-with-currency-values-in-php), 2) accept Inori's answer. – paulsm4 Aug 16 '12 at 15:33
  • @MarkSmith updated my post with example – Inoryy Aug 16 '12 at 15:34
  • @Inori - Thanks, in my code `(int)($build['total']*1000)+(int)($build['delivery']*1000) // int(1)` – Mark Smith Aug 16 '12 at 15:39
  • @MarkSmith that's interesting, I guess different setups handle `(int)` differently. Anyway, see the updated example, this should yield the right answer on any system. – Inoryy Aug 16 '12 at 15:45
  • Actually, I would do something like this: `(int)($build['total']+$build['delivery'])*1000.0)`. In other words, cast to "int" *LAST*. – paulsm4 Aug 16 '12 at 15:45
  • @paulsm4 yep in this particular case your solution is better. I'm just assuming that data comes already casted from the model, i.e. in real world my example would look like this `$order->getPreciseTotal() + $order->getPreciseDelivery()` – Inoryy Aug 16 '12 at 15:51
  • 1
    OK, sorted it now. Thanks in part to @Inori. I ending up rounding to 2 decimal places. `((round($build['total'], 2))+(round($build['delivery'], 2))) // 0` – Mark Smith Aug 16 '12 at 15:56