PHP bcmath против Python Decimal

Я использую PHP bcmath библиотека для выполнения операций с числами с фиксированной запятой. Я ожидал получить такое же поведение Python Decimal класс, но я был довольно удивлен, обнаружив следующее поведение:

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

при использовании Decimalв Python я получаю:

# 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)

5

Решение

После некоторых экспериментов я понял это. Это проблема округления или усечения. Python по умолчанию использует ROUND_HALF_EVEN округление, в то время как PHP просто усекается с заданной точностью. Python также имеет точность по умолчанию 28, в то время как вы используете 26 в PHP.

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"
4

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]