【问题标题】:Python 3 rounding behavior in Python 2Python 2 中的 Python 3 舍入行为
【发布时间】:2014-02-17 20:58:06
【问题描述】:

在 Python 2.x 中,内置的 round 具有以下行为:

如果两个倍数相等,则舍入 远离 0(例如,round(0.5) 为 1.0,round(-0.5) 为 -1.0)

在 Python 3.x 中,this has changed 更常见:

如果两个倍数相等,则舍入向偶数选择(例如,round(0.5) 和 round(-0.5) 均为 0,round(1.5) 为2).

有没有一种简单的方法可以在 Python 2.x 中实现这种行为?不幸的是,future_builtins 模块不包括这个。也许我还没有找到另一个类似的模块?或者,另一种将 Python 3.x 函数拉入 Python 2.x 的方法?

显然,我可以编写一个产生所需行为的新函数,但我更好奇是否存在使用实际 Python 3.x 函数的解决方案,以避免增加不必要的复杂性和需要维护的代码。

【问题讨论】:

  • 我没有意识到 3.x 的行为是一回事。我想在某个地方有一个无休止的长邮件列表线程......
  • @AndrewGorcester:几乎可以肯定。目的(在任何给定情况下可能合适,也可能不合适)是避免在任何方向上引入偏见。例如,想象一个小数点后有 2 位数字的设定值(也就是说,它们被测量到一个单位的 1/100)。如果您从 0 舍入,并且值都是正数,并且 100 个可能的小数部分中的每一个均等表示,那么您将平均值增加 1/200。低于最初的准确性,但系统性,因此有时相当糟糕。 en.wikipedia.org/wiki/Rounding#Round_half_to_even
  • 是的,请参阅IEEE 754 Rounding Rules
  • numpy 依赖会是个问题吗?
  • @AndrewGorcester:我想我找到了邮件列表线程的开头:mail.python.org/pipermail/python-list/2008-April/509669.html

标签: python


【解决方案1】:

除非你介意 numpy 依赖,否则numpy.around 可能会这样做:

>>> from numpy import around
>>> around(0.5)
0
>>> around(-0.5)
-0
>>> around(1.5)
2.0

【讨论】:

  • 但我们正在尝试获取 Python 2.x 的行为,以便 around(-0.5)==-1.0around(0.5)==1.0
  • @Teepeemm:不,其他方式(在 py2 中获取 py3 行为)。此解决方案有效,但并不理想。
  • +1 如果你碰巧有 numpy 撒谎around,这是一个很好的解决方案。
  • @Teepeemm round(x+10**(-y-1),y) 给出 2.x 的行为
【解决方案2】:

Python 2 中的 Python 3 轮

函数可能如下所示:

def py3round(f):
    if abs(round(f)-f) == 0.5:
        return 2.0*round(f/2.0);
    return round(f)

# Python 3            apply round to ... -.1 -.75 -.5 -.25 0 .25 .5 .75 ...
>>> ' '.join(map(str, map(int, [round(i * 0.25) for i in range(-20, 20)])))
'-5 -5 -4 -4 -4 -4 -4 -3 -3 -3 -2 -2 -2 -2 -2 -1 -1 -1 0 0 0 0 0 1 1 1 2 2 2 2 2 3 3 3 4 4 4 4 4 5'
# Python 2            apply round to ... -.1 -.75 -.5 -.25 0 .25 .5 .75 ...
>>> ' '.join(map(str, map(int, [py3round(i * 0.25) for i in range(-20, 20)])))
'-5 -5 -4 -4 -4 -4 -4 -3 -3 -3 -2 -2 -2 -2 -2 -1 -1 -1 0 0 0 0 0 1 1 1 2 2 2 2 2 3 3 3 4 4 4 4 4 5'

让我澄清一下bltinmodule.c中的轮次

if hasattr(args[0], '__round__'):
    return args[0].__round__(*args[1:])
else: 
    raise TypeError("type %.100s doesn't define __round__ method")

所以 round 实际上几乎什么都不做。这取决于传递给它的对象。 这导致floatobject.c函数static PyObject *double_round(double x, int ndigits)

z = round(y);
if (fabs(y-z) == 0.5)
    /* halfway between two integers; use round-half-even */
    z = 2.0*round(y/2.0);

我在上面的函数中使用了这些行的知识。

Python 3 中的 Python 2 轮

我认为你需要编写一个新函数。

def python2round(f):
    if round(f + 1) - round(f) != 1:
        return f + abs(f) / f * 0.5
    return round(f)

if 语句处理i + 0.5i + 1.5 被舍入到不同方向= 到偶数和一半的情况。在这种情况下,舍入是从零开始的。

# in Python 2          apply round to ... -.1 -.75 -.5 -.25 0 .25 .5 .75 ...
>>> ' '.join(map(str, map(int, [round(i * 0.25) for i in range(-20, 20)])))
'-5 -5 -5 -4 -4 -4 -4 -3 -3 -3 -3 -2 -2 -2 -2 -1 -1 -1 -1 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 5 5'
# in Python 3          apply round to ... -.1 -.75 -.5 -.25 0 .25 .5 .75 ...
>>> ' '.join(map(str, map(int, [python2round(i * 0.25) for i in range(-20, 20)])))
'-5 -5 -5 -4 -4 -4 -4 -3 -3 -3 -3 -2 -2 -2 -2 -1 -1 -1 -1 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 5 5'

您是否需要带有第二个参数的解决方案,ndigits

【讨论】:

  • 您的代码似乎在 Python 3 中实现了 Python 2 舍入。我认为提问者想要的是相反的,即 Python 2 中的 Python 3 舍入。
  • Blkknght 是正确的,我正在寻找相反的方式。更重要的是,我正在寻找一种解决方案,可以将确切的 Python 3 行为带入 Python 2,而无需维护新功能(尽管它不会是世界末日)。
  • 哦,对不起。^^我会看看那个..看看是否可以出现更短的内容。不过,您将需要一个新功能。但是一旦功能符合规范,我认为您不需要维护它。
  • 很高兴您挖掘了python源代码以在python中实现完全相同的功能。没有一个能引入真正的 CPython 函数的答案,到目前为止,我最喜欢这个答案。
【解决方案3】:

此回复已阅读原始问题,答案是“不,我无法想出使用 Py3 原始代码的东西。”

但是对于想知道在 Py2 中复制 Py3 行为的代码是什么(包括 int 与 float 行为)的任何人,这里是对上面 User 代码的改编,其中包括 ndigits 和 int vs . 浮动区分ndigits = None vs 0

import sys

def py3round(number, ndigits=None):
    '''
    Simulates Python3 rounding behavior in Python 2

    >>> py3round(2.3)
    2
    >>> py3round(2.7)
    3
    >>> py3round(2.7, 0)
    3.0
    >>> py3round(1.5, 0)
    2.0
    >>> py3round(2.5, 0)
    2.0
    >>> py3round(-1.5)
    -2
    >>> py3round(-2.5)
    -2
    '''
    if sys.version_info[0] >= 3:
        if ndigits is not None:
            return round(number, ndigits)
        else:
            return round(number)        

    intIt = True if ndigits is None else False
    ndigits = ndigits if ndigits is not None else 0

    f = number
    if abs(round(f) - f) == 0.5:
        retAmount = 2.0 * round(f / 2.0, ndigits);
    else:
        retAmount = round(f, ndigits)

    if intIt:
        return int(retAmount)
    else:
        return retAmount

【讨论】:

    【解决方案4】:

    如果您对浮点舍入问题过于偏执,可以查看decimal 库,您可以在其中配置舍入模式(默认为 ROUND_HALF_EVEN)。

    >>> import decimal
    >>> from decimal import Decimal
    >>> Decimal(0.5).quantize(Decimal('1'))
    Decimal('0')
    >>> Decimal(1.5).quantize(Decimal('1'))
    Decimal('2')
    
    >>> decimal.getcontext().rounding = decimal.ROUND_HALF_UP
    >>> Decimal(0.5).quantize(Decimal('1'))
    Decimal('1')
    

    否则,我认为如果您只是编写自己的函数或使用 numpy,而不是希望您可以在 py2 中使用 py3 函数,它会更明确且更易于维护。

    【讨论】:

      【解决方案5】:

      使用decimal.Decimal.to_integral_value

      例如:

      float(decimal.Decimal(1.5).to_integral_value(decimal.ROUND_HALF_EVEN))
      

      舍入选项记录在here。我们关心的两个选项是:

      ROUND_HALF_EVEN (to nearest with ties going to nearest even integer),
      ROUND_HALF_UP (to nearest with ties going away from zero),
      

      【讨论】:

        【解决方案6】:
        def round_to_even(x):
            if x < 0:
                return -round_to_even(-x)
            ipart, fpart = divmod(x, 1)
            ipart = int(ipart)
            fpartx2 = fpart * 2
            if fpartx2 > 1:
                return ipart + 1
            elif fpartx2 == 1:
                return ipart + (ipart & 1)
            else:
                return ipart
        

        【讨论】:

        • 你读过这个问题吗? “显然,我可以编写一个产生所需行为的新函数,但我更好奇是否存在使用实际 Python 3.x 函数的解决方案,以避免增加不必要的复杂性和需要维护的 LOC。”
        【解决方案7】:

        math.ceil函数好用:

        Python 2

        import math
        
        math.ceil(100.5)
        > 101.0
        

        Python 3

        import math
        
        mail.ceil(100.5)
        > 101
        

        【讨论】:

          猜你喜欢
          • 2023-03-24
          • 2020-03-05
          • 1970-01-01
          • 1970-01-01
          • 2016-02-04
          相关资源
          最近更新 更多