将Python的小数四舍五入到最近的0.05

4

我试图将小数中的货币数字四舍五入至最接近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 Decimal(使用量化?)而不涉及浮点数的情况下完成这个操作?

为什么不在Python中使用round函数? - Seek Addo
@SeekAddo。我该如何将数字四舍五入到最近的0.05?同时,我需要控制舍入方向(即我需要向上/向下舍入)。 - gurch101
def round_num(amount): print(round(amount,2)) round_num(23.045954) 将会得到 23.05,让我问一下,你想要向下取整是怎样的呢 @gurch101 - Seek Addo
@SeekAddo 我相当确定上述两个函数的工作方式符合我的预期。只是在寻找更加优雅的解决方案。 - gurch101
3个回答

12

使用浮点数时,只需使用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')

1

一个适用于任何舍入基数的更通用的解决方案。

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')

如果我调用 round_decimal(Decimal('123.36'), base=Decimal('0.09')),我得到的结果是 Decimal('123.30')。难道我不应该得到 Decimal('123.29') 吗?我可能漏掉了什么。 - Guillaume Ansanay-Alex
使用十进制基数时,它的工作方式就好像您首先将小数点后的数字与基数乘以最低有效数字“乘数”一样。 base = 0.09 decimal_number = 123.36 multiplier = 100 然后 base = 9 decimal_numer = 12336 12336 / 9 = 1370.666666666666667 1370 * 9 = 12330 12330 / 100 = 123.30 - t4z

0

首先需要注意的是,这个问题(意外地向下舍入)只有在您要舍入的数字左侧比其低一位的数字是 5有时会出现。

例如:

>>> 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

但是有一个简单的解决方案,我发现它似乎总是有效的,并且不依赖于导入其他库。解决方案是在你尝试使用round的数字字符串长度上加上11e-X

>>> round(0.075,2)

0.07

>>> round(0.075+10**(-2*6),2)

0.08

啊哈!所以基于这个,我们可以制作一个方便的包装函数,它是独立的,不需要额外的import调用...
def roundTraditional(val,digits):
   return round(val+10**(-len(str(val))-1))

基本上,这会添加一个保证比您尝试使用round的字符串的最小给定数字更小的值。通过添加这个小量,它在大多数情况下保留了round的行为,同时现在确保如果低于要舍入到的数字的数字是5,则向上舍入,如果是4,则向下舍入。

使用10**(-len(val)-1)的方法是有意为之的,因为它是可以添加以强制移位的最大小数,同时还确保您添加的值即使缺少小数点.也不会改变舍入。我可以只使用10**(-len(val))和条件语句if (val>1)来减去1,但总体而言,始终减去1更简单,因为这不会改变此解决方法可以正确处理的十进制数范围。如果您的值达到类型的极限,则此方法将失败,但对于几乎所有有效十进制值的范围,它应该可以正常工作。

你也可以使用decimal库来完成这个任务,但是我提出的包装器更简单,某些情况下可能更受欢迎。


编辑:感谢Blckknght指出5边缘情况仅在这里的某些值中发生。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接