samba
samba

Reputation: 3091

Python - how to define a rounding scale for Decimal.quantize

I wrote a function for rounding numbers to a certain number of digits on right side of dot. But I still don't understand how to properly define a scale to be used with quantize.

In the below example, res1 and res2 return the expected result. I found different examples for defining scale to have 2 numbers after the floating point. What is the difference between using "1.00" and "0.01"? Both seem to work the same way.

As for res3 when I pass a float to the Decimal constructor to get number_3 - ROUND_HALF_UP doesn't seem to work there. Why does this happen? How should I define scale for Decimals created from float?

In case when I get floats as an input is the only way to get quantize work - first convert a float to a string?

from decimal import Decimal, ROUND_HALF_UP


def custom_round(dec: Decimal, scale, rounding_mode):
    return dec.quantize(Decimal(scale), rounding_mode)


number_1 = Decimal("10.026")
res1 = custom_round(number_1, "1.00", ROUND_HALF_UP)
print(res1) # 10.03

number_2 = Decimal("28.525")
res2 = custom_round(number_2, "0.01", ROUND_HALF_UP)
print(res2) # 28.53

number_3 = Decimal(28.525)
res3 = custom_round(number_3, "0.01", ROUND_HALF_UP)
print(res3) # 28.52 

Upvotes: 6

Views: 7155

Answers (2)

Prune
Prune

Reputation: 77837

Your first two examples work the same way, because you gave them equivalent formats: two decimal places. You gave an input argument of a string, rather than a precision. YOu specified two decimals places of accuracy, and that's what you got. To specify accuracy, you give it a decimal value:

number_1 = Decimal("10.026")
res1 = custom_round(number_1, Decimal(1), ROUND_HALF_UP)
print(res1) # 10

number_2 = Decimal("28.525")
res2 = custom_round(number_2, Decimal(10)**-2, ROUND_HALF_UP)
print(res2) # 28.53

The third one works differently because you gave it an imprecise float value that is not exactly 28.525. See Is floating-point math broken?.

Upvotes: 1

Tim Peters
Tim Peters

Reputation: 70582

There's no difference between "1.00" and "0.01" in this context. Unlike binary floating-point, decimal instances have a concept of significant trailing zeroes, and it's only the internal exponent quantize() uses. Indeed,

>>> custom_round(Decimal("28.525"), "1234.56", ROUND_HALF_UP)
Decimal('28.53')
>>> custom_round(Decimal("28.525"), "000000.00", ROUND_HALF_UP)
Decimal('28.53')

also work the same way.

For your other question, look at what number_3 converts to:

>>> number_3
Decimal('28.52499999999999857891452847979962825775146484375')

float->decimal conversion is exact, and the float literal 28.525 is represented internally by a binary float that only approximates the decimal 28.525 (which cannot be represented exactly as a binary float - you get the closest possible binary approximation instead). The exact decimal value of that binary approximation is - as shown - a ilttle bit less than the decimal 28.525. That's why it rounds down.

Upvotes: 4

Related Questions