PHP bcmath vs Python Decimal

I use the bcmath PHP bcmath to perform fixed point number operations. I expected to get the same behavior of the Python Decimal class, but I was completely surprised to find the following behavior:

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

when using Decimal in Python, I get:

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

Why? Since I use this to perform very sensitive operations, I would like to find a way to get the same result in PHP as in Python (i.e. (x / y) * y == x )

+5
source share
1 answer

After some experimentation, I realized this. This is a problem with rounding and truncation. Python defaults to rounding ROUND_HALF_EVEN , while PHP just truncates with the specified precision. Python also has a standard precision of 28, while you use 26 in 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]) 

If you want Python to mimic the behavior of PHP truncation, we just need to change the rounding property:

 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 

Creating PHP behavior, as with Python by default, is a bit more complicated. We need to create a custom function for division and multiplication that rounds half even, like 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); } 

Here's a demo:

 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
source

All Articles