6

在下面的代码中,为什么乘法方法不会产生舍入误差,而累积加法方法会产生?

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
4

3 回答 3

6

首先,考虑有许多以 10 为底的有理数,但不是二进制浮点表示。例如,浮点值 26.82 实际上是26.8200000000000002842170943040400743484497

自然,如果您不断将其添加到自身中,则会出现一些错误,但最多 15 位有效数字应该没问题 - 将其添加 1000 次,总和实际上是26819.9999999997671693563461303710937500000000

有趣的问题是,当我们将 26.82 乘以 1000.0 时,我们得到26820.0000000000000000000000000000000000000000- 它是如何做到的?

答案很简单,26820.0确实具有精确的二进制表示,并且乘法运算足够聪明,可以发现这一点——即使乘以 1001.0 并减去 26.82 仍然可以得到准确的答案。

这里有一些有趣的链接

于 2013-08-12T10:54:42.417 回答
3

问题可能出在浮点值的机器表示中(参见警告块)

例如 21470.73 可能是 21470.729999..9994561 或 21470.73000...0001231,这取决于它们是如何计算的。

尝试在计算之前对临时值gross_total_so_far进行四舍五入nett_total_so_far$total

于 2013-08-09T14:50:21.520 回答
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在你的情况下会很有用

于 2013-08-09T14:58:41.977 回答