将 python 小数舍入到最接近的 0.05
Round python decimal to nearest 0.05
我正在尝试将十进制的货币数字四舍五入到最接近的 0.05。现在,我正在这样做:
def round_down(amount):
amount *= 100
amount = (amount - amount % 5) / Decimal(100)
return Decimal(amount)
def round_up(amount):
amount = int(math.ceil(float(100 * amount) / 5)) * 5 / Decimal(100)
return Decimal(amount)
有什么方法可以更优雅地做到这一点,而无需使用 python 小数(也许使用量化)来处理浮点数?
对于浮点数,只需使用 round(x * 2, 1) / 2
。不过,这并不能控制舍入方向。
使用 Decimal.quantize
您还可以完全控制舍入的类型和方向 (Python 3.5.1):
>>> from decimal import Decimal, ROUND_UP
>>> x = Decimal("3.426")
>>> (x * 2).quantize(Decimal('.1'), rounding=ROUND_UP) / 2
Decimal('3.45')
>>> x = Decimal("3.456")
>>> (x * 2).quantize(Decimal('.1'), rounding=ROUND_UP) / 2
Decimal('3.5')
First note this problem (unexpected rounding down) only sometimes occurs when the digit immediately inferior (to the left of) the digit you're rounding to has a 5
.
i.e.
>>> round(1.0005,3)
1.0
>>> round(2.0005,3)
2.001
>>> round(3.0005,3)
3.001
>>> round(4.0005,3)
4.0
>>> round(1.005,2)
1.0
>>> round(5.005,2)
5.0
>>> round(6.005,2)
6.0
>>> round(7.005,2)
7.0
>>> round(3.005,2)
3.0
>>> round(8.005,2)
8.01
But there's an easy solution, I've found that seems to always work, and which doesn't rely upon the import
of additional libraries. The solution is to add a 1e-X
where X
is the length of the number string you're trying to use round
on plus 1
.
>>> round(0.075,2)
0.07
>>> round(0.075+10**(-2*6),2)
0.08
啊哈! So based on this we can make a handy wrapper function, which is standalone and does not need additional import
calls...
def roundTraditional(val,digits):
return round(val+10**(-len(str(val))-1))
Basically this adds a value guaranteed to be smaller than the least given digit of the string you're trying to use round
on. By adding that small quantity it preserve's round
's behavior in most cases, while now ensuring if the digit inferior to the one being rounded to is 5
it rounds up, and if it is 4
it rounds down.
The approach of using 10**(-len(val)-1)
was deliberate, as it the largest small number you can add to force the shift, while also ensuring that the value you add never changes the rounding even if the decimal .
is missing. I could use just 10**(-len(val))
with a condiditional if (val>1)
to subtract 1
more... but it's simpler to just always subtract the 1
as that won't change much the applicable range of decimal numbers this workaround can properly handle. This approach will fail if your values reaches the limits of the type, this will fail, but for nearly the entire range of valid decimal values it should work.
You can also use the decimal library to accomplish this, but the wrapper I propose is simpler and may be preferred in some cases.
Edit: Thanks Blckknght for pointing out that the 5
fringe case occurs only for certain values .
适用于任何舍入基础的更通用的解决方案。
from decimal import ROUND_DOWN
def round_decimal(decimal_number, base=1, rounding=ROUND_DOWN):
"""
Round decimal number to the nearest base
:param decimal_number: decimal number to round to the nearest base
:type decimal_number: Decimal
:param base: rounding base, e.g. 5, Decimal('0.05')
:type base: int or Decimal
:param rounding: Decimal rounding type
:rtype: Decimal
"""
return base * (decimal_number / base).quantize(1, rounding=rounding)
示例:
>>> from decimal import Decimal, ROUND_UP
>>> round_decimal(Decimal('123.34'), base=5)
Decimal('120')
>>> round_decimal(Decimal('123.34'), base=6, rounding=ROUND_UP)
Decimal('126')
>>> round_decimal(Decimal('123.34'), base=Decimal('0.05'))
Decimal('123.30')
>>> round_decimal(Decimal('123.34'), base=Decimal('0.5'), rounding=ROUND_UP)
Decimal('123.5')
我正在尝试将十进制的货币数字四舍五入到最接近的 0.05。现在,我正在这样做:
def round_down(amount):
amount *= 100
amount = (amount - amount % 5) / Decimal(100)
return Decimal(amount)
def round_up(amount):
amount = int(math.ceil(float(100 * amount) / 5)) * 5 / Decimal(100)
return Decimal(amount)
有什么方法可以更优雅地做到这一点,而无需使用 python 小数(也许使用量化)来处理浮点数?
对于浮点数,只需使用 round(x * 2, 1) / 2
。不过,这并不能控制舍入方向。
使用 Decimal.quantize
您还可以完全控制舍入的类型和方向 (Python 3.5.1):
>>> from decimal import Decimal, ROUND_UP
>>> x = Decimal("3.426")
>>> (x * 2).quantize(Decimal('.1'), rounding=ROUND_UP) / 2
Decimal('3.45')
>>> x = Decimal("3.456")
>>> (x * 2).quantize(Decimal('.1'), rounding=ROUND_UP) / 2
Decimal('3.5')
First note this problem (unexpected rounding down) only sometimes occurs when the digit immediately inferior (to the left of) the digit you're rounding to has a 5
.
i.e.
>>> round(1.0005,3)
1.0
>>> round(2.0005,3)
2.001
>>> round(3.0005,3)
3.001
>>> round(4.0005,3)
4.0
>>> round(1.005,2)
1.0
>>> round(5.005,2)
5.0
>>> round(6.005,2)
6.0
>>> round(7.005,2)
7.0
>>> round(3.005,2)
3.0
>>> round(8.005,2)
8.01
But there's an easy solution, I've found that seems to always work, and which doesn't rely upon the import
of additional libraries. The solution is to add a 1e-X
where X
is the length of the number string you're trying to use round
on plus 1
.
>>> round(0.075,2)
0.07
>>> round(0.075+10**(-2*6),2)
0.08
啊哈! So based on this we can make a handy wrapper function, which is standalone and does not need additional import
calls...
def roundTraditional(val,digits):
return round(val+10**(-len(str(val))-1))
Basically this adds a value guaranteed to be smaller than the least given digit of the string you're trying to use round
on. By adding that small quantity it preserve's round
's behavior in most cases, while now ensuring if the digit inferior to the one being rounded to is 5
it rounds up, and if it is 4
it rounds down.
The approach of using 10**(-len(val)-1)
was deliberate, as it the largest small number you can add to force the shift, while also ensuring that the value you add never changes the rounding even if the decimal .
is missing. I could use just 10**(-len(val))
with a condiditional if (val>1)
to subtract 1
more... but it's simpler to just always subtract the 1
as that won't change much the applicable range of decimal numbers this workaround can properly handle. This approach will fail if your values reaches the limits of the type, this will fail, but for nearly the entire range of valid decimal values it should work.
You can also use the decimal library to accomplish this, but the wrapper I propose is simpler and may be preferred in some cases.
Edit: Thanks Blckknght for pointing out that the 5
fringe case occurs only for certain values
适用于任何舍入基础的更通用的解决方案。
from decimal import ROUND_DOWN
def round_decimal(decimal_number, base=1, rounding=ROUND_DOWN):
"""
Round decimal number to the nearest base
:param decimal_number: decimal number to round to the nearest base
:type decimal_number: Decimal
:param base: rounding base, e.g. 5, Decimal('0.05')
:type base: int or Decimal
:param rounding: Decimal rounding type
:rtype: Decimal
"""
return base * (decimal_number / base).quantize(1, rounding=rounding)
示例:
>>> from decimal import Decimal, ROUND_UP
>>> round_decimal(Decimal('123.34'), base=5)
Decimal('120')
>>> round_decimal(Decimal('123.34'), base=6, rounding=ROUND_UP)
Decimal('126')
>>> round_decimal(Decimal('123.34'), base=Decimal('0.05'))
Decimal('123.30')
>>> round_decimal(Decimal('123.34'), base=Decimal('0.5'), rounding=ROUND_UP)
Decimal('123.5')