Shod
Shod

Reputation: 935

Is "Decimal.quantize" better than "round" for the same precision?

I came across two methods to get precision in floating numbers - using round or using the Decimal package.

What i observed (with few examples tried in REPL) that both produce the same results:

>>> from decimal import Decimal
>>> 
>>> 1/3
0.3333333333333333
>>> 
>>> round(1/3)
0
>>> Decimal(1)/Decimal(3)
Decimal('0.3333333333333333333333333333')
>>> 
>>> round(1/3, 2)
0.33
>>> (Decimal(1)/Decimal(3)).quantize(Decimal('0.01'))
Decimal('0.33')
>>>

This makes me think which method to use out of these two. Do both the methods always give same the same result for the same level of precision? Or am i missing out something here?

Upvotes: 3

Views: 1223

Answers (2)

Shod
Shod

Reputation: 935

I think I was able to figure out few use cases where things might be different (I will update here as I find more differences):

One use case is division that results in recurring fractions and adding back, say, 10 / 3:

using round:

>>> r = round(10/3, 2)
>>> r
3.33
>>> 
>>> sum([r, r, r])
>>> 9.99
>>>

using Decimal (case 1):

>>> Q = Decimal("0.01")
>>> 
>>> d1 = Decimal(10/3)
>>> d1
Decimal('3.333333333333333481363069950020872056484222412109375')
>>> 
>>> sum([d1, d1, d1])
Decimal('10.00000000000000044408920985')
>>> 
>>> sum([d1, d1, d1]).quantize(Q)
Decimal('10.00')
>>>

using Decimal (case 2):

>>> Q = Decimal("0.01")
>>> 
>>> d2 = Decimal(10) / Decimal(3)
>>> d2
Decimal('3.333333333333333333333333333')
>>> 
>>> sum([d2, d2, d2]).quantize(Q)
Decimal('10.00')
>>> 

Second use case is when you receive number with more decimal places than your precision value (something similar to what @H.Doebler mentioned in his answer here). Basically exploiting the round_half_to_even concept. Let's say you have set precision to 2, and get amounts like 0.125 and 0.145 and add them, the value should be 0.27:

using round:

>>> 0.125 + 0.145 
0.27
>>> 
>>> round(0.125, 2) + round(0.145, 2)
0.26
>>> 

using Decimal (case 1 - store quantized): (ERROR)

>>> Q = Decimal("0.01")
>>> d1 = Decimal(0.125).quantize(Q) + Decimal(0.145).quantize(Q)
>>> d1
Decimal('0.26')
>>> d1.quantize(Q)
Decimal('0.26')
>>> 

using Decimal (case 2 - store raw, quantize the result): (CORRECT)

>>> Q = Decimal("0.01")
>>> d2 = Decimal(0.125) + Decimal(0.145)
>>> d2
Decimal('0.2699999999999999900079927784')
>>> d2.quantize(Q)
Decimal('0.27')
>>> 

I think round and Decimal (case 1) give wrong results because we are rounding (quantizing in case of decimal) the numbers before adding, but in case 2 we are quantizing after correctly representing the values in Decimal. I guess it depends a lot on when you quantize..

Upvotes: 1

H. Doebler
H. Doebler

Reputation: 651

No, they do not always give the same result:

>>> (Decimal(645)/Decimal(1000)).quantize(Decimal("0.01"))
Decimal('0.64')

>>> round(645/1000, 2)
0.65

Builtin floats rely on IEEE 754 double precision floating point representation with base 2, whereas decimal uses a base 10 representation. If you really want to round exactly to decimal places, float will not work, because most non-periodic non-trivial decimal number do not lie in the float domain.

>>> f'{round(1/3, 2):.64f}'
'0.3300000000000000155431223447521915659308433532714843750000000000'

>>> f'{(Decimal(1)/Decimal(3)).quantize(Decimal("0.01")):.64f}'
'0.3300000000000000000000000000000000000000000000000000000000000000'

Upvotes: 2

Related Questions