5

PHP のbcmathライブラリを使用して、固定小数点数の演算を実行しています。私は Python のクラスと同じ動作をすることを期待していましたが、Decimal代わりに次の動作を見つけて非常に驚きました。

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

DecimalPython で sを使用すると、次のようになります。

# Python:
from decimal import Decimal
a = Decimal('15.80') / Decimal('483.49870000')
b = a * Decimal('483.49870000')
print(b)  # prints 15.80000000000000000000000000

何故ですか?これを使用して非常に機密性の高い操作を実行しているため、Python と同じ結果を PHP で取得する方法を見つけたいと思います (つまり(x / y) * y == x)

4

1 に答える 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"
于 2016-06-03T21:43:06.217 に答える