简体   繁体   English

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

[英]Pythonic way to ROUND_HALF_UP float with an arbitrary precision

First of all I would like to mention that this question is not a duplicate of:首先,我想提一下,这个问题不是以下问题的重复:

Python Rounding Inconsistently Python舍入不一致

Python 3.x rounding behavior Python 3.x 舍入行为

I know about IEEE 754 and I know that:我了解 IEEE 754 并且我知道:

The simple "always round 0.5 up" technique results in a slight bias toward the higher number.简单的“始终向上取整 0.5”技术会导致略微偏向较高的数字。 With large numbers of calculations, this can be significant.对于大量计算,这可能很重要。 The Python 3.0 approach eliminates this issue. Python 3.0 方法消除了这个问题。

I agree that ROUND_HALF_UP is inferior method to the one implemented by default in Python.我同意 ROUND_HALF_UP 是 Python 中默认实现的方法的次等方法。 Nevertheless there are people who do not know that and one needs to use that method if the specs require that.然而,有些人不知道这一点,如果规范要求,人们需要使用这种方法。 Easy way to make this work is:使这项工作的简单方法是:

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

But my consideration is that this is not Pythonic ... According to the docs I should use something like:但我的考虑是这不是 Pythonic ......根据文档我应该使用类似的东西:

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

The problem is that this would not pass all test cases:问题是这不会通过所有测试用例:

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)

Because of the nature of conversion between float and decimal and because quantize does not seem to be working for exponents like 10 or 100. Is there a Pythonic way to do this?由于浮点数和十进制数之间转换的性质,并且因为 quantize 似乎不适用于 10 或 100 等指数。是否有 Pythonic 方法可以做到这一点?

And I know that I could just add infinitesimally small number and round(num+10**(precission-20),-pricission) would work but this is so wrong that "the puppies would die"...而且我知道我可以添加无穷小的数字和round(num+10**(precission-20),-pricission)会起作用,但这是错误的,以至于“小狗会死”......

As you said that doesn't work if you try to quantize with numbers greater than 1 :正如您所说,如果您尝试使用大于1数字进行quantize ,那将不起作用:

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

But you can simply divide, quantize and multiply:但是您可以简单地进行除法、量化和乘法:

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

However that only passes the tests if you input Decimal and compare to Decimal :但是,如果您输入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')

As noted in the comments it's possible to use scientific notation decimals as "working" quantize arguments, which simplifies the function:正如评论中所指出的,可以使用科学记数法小数作为“工作”量化参数,这简化了函数:

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

This also passes the test cases mentioned above.这也通过了上面提到的测试用例。

Here's a version that matches the behavior and API of the builtin round() function:这是一个与内置round()函数的行为和 API 相匹配的版本:

from decimal import Decimal, ROUND_HALF_UP

def round_half_up(number, ndigits=None):
    return_type = type(number)
    if ndigits is None:
        ndigits = 0
        return_type = int
    if not isinstance(ndigits, int):
        msg = f"'{type(ndigits).__name__}' object cannot be interpreted as an integer"
        raise TypeError(msg)
    quant_level = Decimal(f"10E{-ndigits}")
    return return_type(Decimal(number).quantize(quant_level, ROUND_HALF_UP))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM