Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

php

php-5.4

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
like image 518
Alex Avatar asked Aug 09 '13 14:08

Alex


3 Answers

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

  • http://www.h-schmidt.net/FloatConverter/ - this lets you look at single-precision representation of floating point numbers, and can be useful in really understanding floats
  • This RFC on PHP rounding is tangentially interesting as it covers some of the issues around rounding floats
  • and What Every Computer Scientist Should Know About Floating-Point Arithmetic
like image 65
Paul Dixon Avatar answered Sep 19 '22 00:09

Paul Dixon


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

like image 24
SlyChan Avatar answered Sep 22 '22 00:09

SlyChan


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

like image 39
developerCK Avatar answered Sep 20 '22 00:09

developerCK