3

In one of my wordpress theme function, this code is being used to calculate total billable amount: ceil(($cost * (1 + ($charges / 100))) * 100);

There is a little miscalculation happening for below scenario.

Scenario:

$charges = 9;
$cost = 100;

echo ceil(($cost * (1 + ($charges / 100))) * 100);

The above code outputs 10901 whereas it should be 10900.

It works fine for other scenarios like:

$charges = 4;
$cost = 90.7;

echo ceil(($cost * (1 + ($charges / 100))) * 100); 
//outputs 9433, which is fine because manual calculation results 9432.8

Question:

  1. Why is that happening?
  2. How can I prevent that?
  3. Any alternate function to round to next nearest integer? (only if amount is floating value)
Alena
  • 1,134
  • 6
  • 19
  • 45
  • @Phil none of those answer helped, First, it's not a math problem. Second, it does work almost fine but causes error only for specific numbers like ending with 00. Kindly unmark the question or share helpful resource. Thanks – Alena Jul 31 '18 at 04:53
  • 3
    It seems you really *are* facing problems caused by numerical accuracy. Although I found that simplifying your equation *does* help: `ceil($cost*(100+$charge));`, see here: http://rextester.com/QQEYOS31143 – Carsten Massmann Jul 31 '18 at 05:42
  • thanks @cars10m, there's another issue. Your equation works fine on rextester.com but on my server, it has same issue. :( I'm running 5.6.32 – Alena Jul 31 '18 at 05:51
  • @Alena it most certainly is a floating point arithmetic problem. For example, `100 * (1 + (9 / 100))` comes out as `109.00000000000001`. This is why `ceil()` appears to be rounding up. Please read both posts I've linked at the top of your question – Phil Jul 31 '18 at 06:21
  • @Phil that makes a little sense now, so how do I calculate exact value using normal floating point methods? – Alena Jul 31 '18 at 06:29
  • 1
    You could try using `round($cost*(100+$charge),6)` to round to the closest number with 6 digits after the decimal point before using your `ceil()` function. However, `round()` is reported to be buggy in some cases. Otherwise you can also do a detour by creating a rounded string, like `sprintf('%0.6f',$cost*(100+$charge))` and then continue to do math with it (it will implicitly be converted back to float). In both cases the `ceil()` function will be applied afterwards to turn the values into "the next larger integer". – Carsten Massmann Jul 31 '18 at 08:06
  • @cars10m bundle of thanks for your help, the round function does the job. Although, the `ceil()` function does pretty good job but it goes wrong in one scenario (when number ends on 00) so I've decided to use round function. – Alena Aug 02 '18 at 04:41

1 Answers1

2

The problem is that you are applying ceil to the outer expression. Try to rewrite it as:

$charges = 9;
$cost = 100;

echo ($cost + ceil($cost * $charges / 100)) * 100;

This outputs 10900 as expected.

UPDATE

As @cars10m suggested, simplifying the expression does help:

echo ceil($cost * 100  + $cost * $charges);

UPDATE 2

You can also use BCMath library to do precise math:

bcscale(6);
echo ceil(bcadd(bcmul($cost, 100), bcmul($cost, $charges)));
Olim Saidov
  • 2,796
  • 1
  • 25
  • 32
  • thanks @Olim Saidov, but that does not work fine for other scenario. Try it with these values `$charges = 4; $cost = 90.7;` It results `9470` whereas it should be `9433` – Alena Jul 31 '18 at 04:58
  • Can you explain the calculation logic? Why it should be `9433`? – Olim Saidov Jul 31 '18 at 05:01
  • 1
    1) charges/100 = `0.04` 2) `0.04 + 1 = 1.04` 3) 1.04 * cost = `94.328` 4) `94.328 * 100 = 9432.8` 5) rounding to next integer will make it `9433` – Alena Jul 31 '18 at 05:22
  • the new solution does not work on my server. I'm running `5.6.32` – Alena Jul 31 '18 at 06:18
  • using @cars10m method, the rextester gives correct result but on my server, it gives same result like my older expression. Maybe that's because of php version etc. However, the `bcscale` seems working. I will perform different tests and get back soon. Thanks for the help! :) – Alena Jul 31 '18 at 06:39
  • thanks for providing me help. BTW, I have to use the round function to resolve my issue, as suggested by @cars10m. – Alena Aug 02 '18 at 04:39