在python中,round(val * 10 ** ndigits)与round(val,ndigits)的失败案例是什么?

时间:2017-01-04 04:03:48

标签: python floating-point

我软件的用户抱怨说在某些情况下,存在明显的舍入错误(由于浮点表示问题):

>>> round(4.55, 1)
4.5
>>> '{:.60f}'.format(4.55)
'4.549999999999999822364316059974953532218933105468750000000000'

我正在考虑用以下内容替换当前的舍入功能:

>>> def round_human(val, ndigits):
...     return round(val * 10 ** ndigits) / 10 ** ndigits
... 
>>> round_human(4.55, 1)
4.6

或(repr让我感到不安,但由于此时数字已经通过numpy,我不确定我有什么更好的选择):

>>> def round_decimal(val, ndigits):
...     return float(Decimal(repr(val)).quantize(Decimal(10) ** -ndigits))
... 
>>> round_decimal(4.55, 1)
4.6

是否存在这些功能中的任何一个产生人为检查错误的圆形结果的情况?我并不担心ndigits大于3的情况。

一般来说有更好的方法吗?

2 个答案:

答案 0 :(得分:1)

您可以使用以下功能进行舍入;它通常比round()本身更好:

def my_round(x):
    return int(x*10+(0.5 if x > 0 else -0.5))/10

答案 1 :(得分:0)

我意识到我可以写一个测试来强制所有有趣的案例。下面测试中的奇怪打印语句产生golden_dict,然后手动检查所需的行为。

def test_rounding(self):
    print '        golden_dict = {'

    golden_dict = {
        ('1.005', 2): 1.01,
        ('1.015', 2): 1.02,
        # ...
        ('1.95', 1): 2.0,
    }

    try:
        for a, b, c in itertools.product(range(10), range(10), range(10)):
            s = '1.{}{}{}'.format(a, b, c).rstrip('0')
            self.assertEqual(s.lstrip('+'), repr(float(s)).rstrip('0'))

            for ndigits in [1, 2, 3]:
                q = decimal.Decimal('0.{}1'.format('0' * (ndigits-1)))
                g = golden_dict.get((s, ndigits), round(float(s), ndigits))

                rdp = show.round_decimal(float(s), ndigits)
                rdn = show.round_decimal(float('-' + s), ndigits)

                try:
                    self.assertEqual(rdp, -rdn)
                    self.assertEqual(rdp, g, \
                            "{}: {} != {}".format(s, rdp, g))
                except:
                    print '            ({!r:6}, {!r}): {!r},'\
                            .format(s, ndigits, rdp)
                    # Comment this raise out to produce the 
                    # entire golden_dict all at once.
                    raise

    finally:
        print '        }'

最有效的功能:

def round_decimal(val, places):
    s = repr(val)
    if places < 1:
        q = decimal.Decimal(10 ** -places)
    else:
        q = decimal.Decimal('0.{}1'.format('0' * (places-1)))
    f = float(decimal.Decimal(s).quantize(q, rounding=decimal.ROUND_HALF_UP))

    return f

这取决于python的神奇repr(4.55)行为,这里描述的是:https://docs.python.org/2/tutorial/floatingpoint.htmlhttps://bugs.python.org/issue1580(这是最后一个&#39; sa saga)。