PHP bcmath 与 Python Decimal 比较

5

我正在使用PHP的bcmath库对定点数进行操作。我原本期望得到与Python的Decimal类相同的行为,但我却惊讶地发现了以下行为:

// PHP:
$a = bcdiv('15.80', '483.49870000', 26);
$b = bcmul($a, '483.49870000', 26);
echo $b;  // prints 15.79999999999999999999991853

当我在Python中使用Decimal时,会出现以下情况:
# Python:
from decimal import Decimal
a = Decimal('15.80') / Decimal('483.49870000')
b = a * Decimal('483.49870000')
print(b)  # prints 15.80000000000000000000000000

为什么会这样呢?由于我在进行非常敏感的操作,希望找到一种方法,在PHP中获得与Python相同的结果(即(x / y) * y == x)。


是的:$a = bcdiv('15.80', '483.49870000', 26); echo gettype($a); 输出 "string"。 - Simone Bronzini
哪一个是正确的,顺便问一下? - Will
1个回答

5

经过一些试验,我找到了解决方法。这是一个四舍五入和截断的问题。Python默认使用ROUND_HALF_EVEN的四舍五入方式,而PHP则只在指定精度处进行截断。Python的默认精度为28,而您在PHP中使用的是26。

In [57]: import decimal
In [58]: decimal.getcontext()
Out[58]: Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, capitals=1, flags=[], traps=[InvalidOperation, Overflow, DivisionByZero])

如果你想让Python模仿PHP的截断行为,我们只需要更改“rounding”属性:
In [1]: import decimal
In [2]: decimal.getcontext().rounding = decimal.ROUND_DOWN
In [3]: decimal.getcontext().prec = 28
In [4]: a = decimal.Decimal('15.80') / decimal.Decimal('483.49870000')
In [5]: b = a * decimal.Decimal('483.49870000')
In [6]: print(b)
15.79999999999999999999999999

让PHP的行为像Python的默认行为有点棘手。我们需要创建一个自定义函数来进行除法和乘法计算,并像Python一样四舍五入“半偶数”:

function bcdiv_round($first, $second, $scale = 0, $round=PHP_ROUND_HALF_EVEN)
{
    return (string) round(bcdiv($first, $second, $scale+1), $scale, $round);
}

function bcmul_round($first, $second, $scale = 0, $round=PHP_ROUND_HALF_EVEN)
{
    $rounded = round(bcmul($first, $second, $scale+1), $scale, $round);

    return (string) bcmul('1.0', $rounded, $scale);
}

这是一个演示:
php > $a = bcdiv_round('15.80', '483.49870000', 28);
php > $b = bcmul_round($a, '483.49870000', 28);
php > var_dump($b);
string(5) "15.80"

1
非常感谢您的出色回答! - Simone Bronzini
没问题,很高兴能帮忙 :) - Will

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接