以任意精度实现 ROUND_HALF_UP 浮点数的 Pythonic 方式

2024-03-23

首先我想提一下,这个问题不是以下问题的重复:

Python 舍入不一致 https://stackoverflow.com/questions/31258464/python-rounding-inconsistently

Python 3.x 舍入行为 https://stackoverflow.com/questions/10825926/python-3-x-rounding-behavior

我了解 IEEE 754 并且我知道:

简单的“始终向上舍入 0.5”技术会导致稍微偏向较高的数字。对于大量计算,这可能很重要。 Python 3.0 方法消除了这个问题。

我同意 ROUND_HALF_UP 是比 Python 中默认实现的方法差的方法。然而,有些人不知道这一点,如果规范要求的话,就需要使用这种方法。完成这项工作的简单方法是:

def round_my(num, precission):
    exp  = 2*10**(-precission)
    temp = num * exp
    if temp%2 < 1:
        return int(temp - temp%2)/exp
    else:
        return int(temp - temp%2 + 2)/exp

但我的考虑是这不是 Pythonic...根据docs https://docs.python.org/3/library/decimal.html我应该使用类似的东西:

def round_my(num, pricission):
    N_PLACES = Decimal(10) ** pricission       # same as Decimal('0.01')
    # Round to n places
    Decimal(num).quantize(N_PLACES)

问题是这不会通过所有测试用例:

class myRound(unittest.TestCase):
    def test_1(self):
        self.assertEqual(piotrSQL.round_my(1.53, -1), 1.5)
        self.assertEqual(piotrSQL.round_my(1.55, -1), 1.6)
        self.assertEqual(piotrSQL.round_my(1.63, -1), 1.6)
        self.assertEqual(piotrSQL.round_my(1.65, -1), 1.7)
        self.assertEqual(piotrSQL.round_my(1.53, -2), 1.53)
        self.assertEqual(piotrSQL.round_my(1.53, -3), 1.53)
        self.assertEqual(piotrSQL.round_my(1.53,  0), 2)
        self.assertEqual(piotrSQL.round_my(1.53,  1), 0)
        self.assertEqual(piotrSQL.round_my(15.3,  1), 20)
        self.assertEqual(piotrSQL.round_my(157.3,  2), 200)

由于浮点和十进制之间转换的性质,并且因为 quantize 似乎不适用于 10 或 100 这样的指数。有没有 Pythonic 的方法来做到这一点?

我知道我可以添加无限小的数字round(num+10**(precission-20),-pricission)可以,但是这是错误的,“小狗会死”......


正如你所说,如果你尝试这样做,这是行不通的quantize https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize数字大于1:

>>> Decimal('1.5').quantize(Decimal('10'))
Decimal('2')
>>> Decimal('1.5').quantize(Decimal('100'))
Decimal('2')

但你可以简单地除法、量化和乘法:

from decimal import Decimal, ROUND_HALF_UP

def round_my(num, precision):
    N_PLACES = Decimal(10) ** precision
    # Round to n places
    return (Decimal(num) / N_PLACES).quantize(1, ROUND_HALF_UP) * N_PLACES

但是,只有在您输入时才能通过测试Decimal并比较Decimal:

assert round_my('1.53', -1) == Decimal('1.5')
assert round_my('1.55', -1) == Decimal('1.6')
assert round_my('1.63', -1) == Decimal('1.6')
assert round_my('1.65', -1) == Decimal('1.7')
assert round_my('1.53', -2) == Decimal('1.53')
assert round_my('1.53', -3) == Decimal('1.53')
assert round_my('1.53',  0) == Decimal('2')
assert round_my('1.53',  1) == Decimal('0')
assert round_my('15.3',  1) == Decimal('20')
assert round_my('157.3',  2) == Decimal('200')

正如评论中所述,可以使用科学记数法小数作为“工作”量化参数,这简化了函数:

def round_my(num, precision):
    quant_level = Decimal('1e{}'.format(precision))
    return Decimal(num).quantize(quant_level, ROUND_HALF_UP) 

这也通过了上面提到的测试用例。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

以任意精度实现 ROUND_HALF_UP 浮点数的 Pythonic 方式 的相关文章

随机推荐