PHP bcmath 与 Python 十进制
PHP bcmath versus Python Decimal
我正在使用 PHP 的 bcmath
库对定点数执行运算。我期待得到与 Python 的 Decimal
class 相同的行为,但我很惊讶地发现以下行为:
// 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
)
经过一些实验,我明白了。这是舍入与截断的问题。 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 的默认值有点棘手。我们需要为除法和乘法创建一个自定义函数,将 "half even" 舍入为 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"
我正在使用 PHP 的 bcmath
库对定点数执行运算。我期待得到与 Python 的 Decimal
class 相同的行为,但我很惊讶地发现以下行为:
// 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
)
经过一些实验,我明白了。这是舍入与截断的问题。 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 的默认值有点棘手。我们需要为除法和乘法创建一个自定义函数,将 "half even" 舍入为 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"