Why do I get a different result from two supposedly analogous arithmetic operations?
Asked Answered
B

3

6

In the following code, why is the multiplication approach not producing rounding errors, while the cumulative addition approach is?

function get_value() { return 26.82; }

function a($quantity) {
    $value_excluding_vat = get_value();
    $value_including_vat = round($value_excluding_vat * (1 + (20 / 100)),2);
    $total_nett = 0;
    $total_gross = 0;
    for($i=0; $i<$quantity; $i++) {
        $total_nett += $value_excluding_vat;
        $total_gross += $value_including_vat;
    }
    return array(
        $total_nett,
        $total_gross
    );
}

function b($quantity) {
    $value_excluding_vat = get_value();
    $value_including_vat = round($value_excluding_vat * (1 + (20 / 100)),2);
    return array(
        $quantity * $value_excluding_vat,
        $quantity * $value_including_vat
    );
}

$totals = a(1000);
print_r($totals);
echo $totals[1] - $totals[0];
echo "\n\n";
$totals = b(1000);
print_r($totals);
echo $totals[1] - $totals[0];

Here's my output:

Array
(
    [0] => 26820
    [1] => 32180
)
5360.0000000005

Array
(
    [0] => 26820
    [1] => 32180
)
5360
Barocchio answered 9/8, 2013 at 14:25 Comment(6)
Possible duplicate of #11367022Fabriane
I read your question as: "I had code A (not shown), which returned result X when multiplying and rounding floating point numbers. I changed that into code B (neither shown) which yields result Y. Why does that happen?". The answer is: it depends on your code, and has probably to do with rounding errors. Please show your actual, relevant code and what you have tried.Oversweet
Consider use of the $mode argument to round(): the default is PHP_ROUND_HALF_UPVedette
@CodeCaster, thanks for your comment - I don't think I was sufficiently clear in my question, so I have edited it. The two different versions of the code ("A" and "B") are encapsulated within a function, which in all test cases produces the same return value for both versions of the code. For this reason, it doesn't seem like the question would be at all enhanced by reproducing the two different versions of the (somewhat lengthy) function.Barocchio
If the function is too long to show, then what have you refactored? And what kind of answer do you expect? You changed some code which made the output become different, so the problem is in what you changed. Without showing what you changed, not much useful can be said, apart from what I said: looks like a floating point rounding error.Oversweet
Having got to grips slightly more with what might be going on, I see your point. I have found a way to reproduce what I'm seeing, and have completely revised the question - the original was indeed pretty rubbish in comparison - apologies.Barocchio
F
6

Firstly, consider that there are many numbers which are rational in base 10, but not in a binary floating point representation. For example, the floating point value of 26.82 is actually 26.8200000000000002842170943040400743484497

Naturally, if you keep adding this to itself some errors creep in, but up to 15 significant digits you should be fine - add this 1000 times and the sum is actually 26819.9999999997671693563461303710937500000000

The interesting question though is when we multiple 26.82 by 1000.0 we get 26820.0000000000000000000000000000000000000000 - how does it do that?

The answer there is simply that 26820.0 does have an exact binary representation, and the multiplication operation is smart enough to spot that - even multiplying by 1001.0 and subtracting 26.82 would still get you an exact answer.

Here's a few interesting links

Foreshadow answered 12/8, 2013 at 10:54 Comment(0)
Z
3

Problem may be in machine representation of float values (see warning block)

e.g. 21470.73 may be 21470.729999..9994561 or 21470.73000...0001231, depends on how had they been calculated.

Try to round temporary values like gross_total_so_far and nett_total_so_far before you calculate $total

Zipper answered 9/8, 2013 at 14:50 Comment(0)
O
1

I can't guess the problem of floating point number representation and subtraction But,

When You will subtract these values without round of You will get result **3578.455

And When You round it to two decimal point it round up with 3578.46.

So php has a solution with this issue.

PHP_ROUND_HALF_UP   Round val up to precision decimal places away from zero,    when it is half way there. Making 1.5 into 2 and -1.5 into -2.

PHP_ROUND_HALF_DOWN     Round val down to precision decimal places towards zero, when it is half way there. Making 1.5 into 1 and -1.5 into -1.

PHP_ROUND_HALF_EVEN     Round val to precision decimal places towards the next even value.

PHP_ROUND_HALF_ODD  Round val to precision decimal places towards the next odd value.

These constant are supplied with round function as

echo round(100.675, 2, PHP_ROUND_HALF_UP);   // 100.68
echo round(100.675, 2, PHP_ROUND_HALF_DOWN); // 100.67

So PHP_ROUND_HALF_DOWN Will be useful in your case

Outlier answered 9/8, 2013 at 14:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.