在下面的代码中,为什么乘法方法不会产生舍入误差,而累积加法方法是什么?
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];
这是我的输出:
Array
(
[0] => 26820
[1] => 32180
)
5360.0000000005
Array
(
[0] => 26820
[1] => 32180
)
5360
答案 0 :(得分:6)
首先,考虑在基数10中有许多数是合理的,但在二进制浮点表示中则没有。例如,浮点值26.82实际上是26.8200000000000002842170943040400743484497
当然,如果你继续添加它自己一些错误,但最多15个有效数字你应该没问题 - 添加1000次,总和实际上是26819.9999999997671693563461303710937500000000
有趣的问题是,当我们得到{0.}时,我们得到26820.0000000000000000000000000000000000000000
多次26.82时,它是如何做到的?
答案就是26820.0 具有精确的二进制表示,并且乘法运算足够智能以发现 - 即使乘以1001.0并减去26.82仍然可以得到一个确切的答案。
以下是一些有趣的链接
答案 1 :(得分:3)
问题可能出在machine representation of float values中(请参阅警告阻止)
e.g。 21470.73可能是21470.729999..9994561或21470.73000 ... 0001231,取决于它们是如何计算的。
在计算gross_total_so_far
之前尝试对nett_total_so_far
和$total
等临时值进行舍入
答案 2 :(得分:1)
我无法猜测浮点数表示和减法的问题但是,
当您在没有回合的情况下减去这些值时,您将获得结果** 3578.455
当你将它四舍五入到两位小数时,它会以 3578.46 向上舍入。
所以php有一个解决这个问题的方法。
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.
这些常量提供圆函数
echo round(100.675, 2, PHP_ROUND_HALF_UP); // 100.68
echo round(100.675, 2, PHP_ROUND_HALF_DOWN); // 100.67
所以 PHP_ROUND_HALF_DOWN 会对你的情况有用