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
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
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With