python 带整数除法的舍入

jexiocij  于 2022-12-10  发布在  Python
关注(0)|答案(7)|浏览(217)

有没有一种不使用浮点数而四舍五入到最接近整数的简单的Python方法?我想用整数运算来做下面的事情:

skip = int(round(1.0 * total / surplus))

==============
@约翰:浮点不能跨平台复制。如果你想让你的代码通过不同平台的测试,那么你需要避免浮点(或者在你的测试中添加一些蹩脚的Espilon东西,希望它能起作用)上面的内容可能很简单,在大多数/所有平台上都是一样的,但我宁愿不做这样的决定,因为完全避免浮点更容易。这怎么会“不符合Python的精神”呢?

yizd12fk

yizd12fk1#

您可以非常简单地执行此操作:
(n + d // 2) // d,其中n是被除数,d是除数。
(((n << 1) // d) + 1) >> 1或等效的(((n * 2) // d) + 1) // 2这样的替代方法在最近的CPython中可能会比较慢,其中int的实现方式与旧的long类似。
简单方法执行3次变量访问、1次常量加载和3次整数运算。复杂方法执行2次变量访问、3次常量加载和4次整数运算。整数运算可能需要花费时间,这取决于所涉及数字的大小。函数局部变量的变量访问不涉及“查找”。
如果你真的对速度很绝望,做基准测试。否则,吻。

egdjgwm8

egdjgwm82#

skip = (((total << 1) // surplus) + 1) >> 1

左移一位等于乘2,右移一位等于除2,中间加1使得如果结果大于0.5的小数部分,“向下舍入”实际上就是向上舍入。
这基本上是一样的,如果你写...

skip = int((1.0*total/surplus) + 0.5)

除了将所有值乘以2,然后再除以2,这是可以用整数运算来实现的(因为移位不需要浮点)。

eqqqjvef

eqqqjvef3#

TL;DR:

q, r = divmod(total, surplus)
skip = q if q % 2 == 0 and 2*r == surplus else q + (2*r // surplus) # rounds to nearest integer; half to nearest even

此解决方案:

  • 按OP要求舍入到最接近的整数
  • 适用于正整数和负整数
  • 将0.5个小数部分舍入为最接近的偶数(rnd(0.5)=0rnd(1.5)=2),这与OP使用的python的round函数的行为相匹配
  • 按照OP的要求坚持整数运算(参见divmod文档)

完整的故事
灵感源自zhmyh's answer答案,即

q, r = divmod(total, surplus)
skip = q + int(bool(r)) # rounds to next greater integer (always ceiling)

,我想出了以下解决方案**(更新:这仅适用于非负的总计和盈余,如注解中所指出的)**:

q, r = divmod(total, surplus)
skip = q + int(2 * r >= surplus) # rounds to nearest integer (floor or ceiling) if total and surplus are non-negative

由于OP要求四舍五入到最近的整数,zhmhs的解决方案实际上有点不正确,因为它总是四舍五入到下一个更大的整数,而我的解决方案只对非负总数和盈余有效。一个对负数也有效的正确解决方案是:

q, r = divmod(total, surplus)
skip = q + (2 * r // surplus) # rounds to nearest integer (positive: half away from zero, negative: half toward zero)

请注意,如果2 * r == surplus,则基本上会对正负结果执行取上限,例如ceil(-1.5) == -1,而ceil(1.5) == 2。就舍入到最接近的整数而言,这仍然是正确的行为(因为到下一个更小整数和下一个更大整数的距离相等),但是它关于零是不对称的。离零取整一半,我们可以添加一个布尔条件:

q, r = divmod(total, surplus)
skip = q if q < 0 and 2*r == surplus else q + (2*r // surplus) # rounds to nearest integer; half away from zero

更妙的是,将取整到最接近的偶数,就像OP使用的python的round函数一样:

q, r = divmod(total, surplus)
skip = q if q % 2 == 0 and 2*r == surplus else q + (2*r // surplus) # rounds to nearest integer; half to nearest even

如果您想知道divmod是如何定义的:根据文件记载
对于整数,结果与(a // b, a % b)相同。
因此,我们坚持整数运算,如OP所要求的。

smtd7mpg

smtd7mpg4#

又一个好笑道:

q, r = divmod(total, surplus)
skip = q + int(bool(r))
daupos2t

daupos2t5#

在除法之前,只需注意四舍五入规则。对于最简单的四舍五入:

if total % surplus < surplus / 2:
    return total / surplus
else:
    return (total / surplus) + 1

如果你需要做一个适当的四舍五入,稍微调整一下。

jbose2ul

jbose2ul6#

问题是你所追求的舍入策略。
下面是我提出的几个例子-注意整数floor和ceiling是case,但是在“舍入到最接近的”中有很多策略。IEEE 754和最专业、工业甚至优雅的方法是舍入到最接近的偶数。我从来没有在任何地方看到过一个在整数运算中这样做的例子。你不能像53那样通过浮点数-位尾数可能会导致精度问题,如果需要执行不同的舍入策略,它已经应用了舍入到最近偶数模式。正确的技术应该始终停留在整数域中

def roundNegInf(n, d): #floor
    return n // d
def roundPosInf(n, d): #ceil
    return (n + d + -1*(d >= 0)) // d
def roundTowardsZero(n, d):
    return (n + ((d + -1*(d >=0)) if (n < 0) ^ (d < 0) else 0)) // d
def roundAwayFromZero(n, d):
    return (n + (0 if (n < 0) ^ (d < 0) else (d + -1*(d >= 0)))) // d
def roundNearestPosInf(n, d):
    #return (((n << 1) // d) + 1) >> 1
    #return (((n * 2) // d) + 1) // 2
    q, r = divmod(n, d)
    return q + (2 * r <= d if d < 0 else 2 * r >= d)
def roundNearestNegInf(n, d):
    q, r = divmod(n, d)
    return q + (2 * r < d if d < 0 else 2 * r > d)
def roundNearestEven(n, d): #round only when for positive numbers guard, round, sticky are 11X or 1X1
    q, r = divmod(n, d)
    return q + (q & 1) * (2 * r == d) + ((2 * r < d) if d < 0 else (2 * r > d))
def roundNearestToZero(n, d):
    q, r = divmod(n, d)
    return q + (q < 0) * (2 * r == d) + ((2 * r < d) if d < 0 else (2 * r > d))
def roundNearestAwayZero(n, d):
    q, r = divmod(n, d)
    return q + (q >= 0) * (2 * r == d) + ((2 * r < d) if d < 0 else (2 * r > d))
def testRounding():
    pairs = ((1, 2), (-1, 2), (1, -2), (-1, -2), (3, 2), (-3, 2), (3, -2), (-3, -2),
             (1, 3), (-1, 3), (1, -3), (-1, -3), (2, 3), (-2, 3), (2, -3), (-2, -3))
    funcs = (roundNegInf, roundPosInf, roundTowardsZero, roundAwayFromZero,
             roundNearestPosInf, roundNearestNegInf,
             roundNearestToZero, roundNearestAwayZero, roundNearestEven)
    res = [[f(*p) for p in pairs] for f in funcs]
    expected = [[0, -1, -1, 0, 1, -2, -2, 1, 0, -1, -1, 0, 0, -1, -1, 0],
                   [1, 0, 0, 1, 2, -1, -1, 2, 1, 0, 0, 1, 1, 0, 0, 1],
                   [0, 0, 0, 0, 1, -1, -1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
                   [1, -1, -1, 1, 2, -2, -2, 2, 1, -1, -1, 1, 1, -1, -1, 1],
                   [1, 0, 0, 1, 2, -1, -1, 2, 0, 0, 0, 0, 1, -1, -1, 1],
                   [0, -1, -1, 0, 1, -2, -2, 1, 0, 0, 0, 0, 1, -1, -1, 1],
                   [0, 0, 0, 0, 1, -1, -1, 1, 0, 0, 0, 0, 1, -1, -1, 1],
                   [1, -1, -1, 1, 2, -2, -2, 2, 0, 0, 0, 0, 1, -1, -1, 1],
                   [0, 0, 0, 0, 2, -2, -2, 2, 0, 0, 0, 0, 1, -1, -1, 1]
                   ]
    assert(all([x == y for x, y in zip(res, expected)]))
dw1jzc5e

dw1jzc5e7#

这也应该可以:

def rint(n):
    return (int(n+.5) if n > 0 else int(n-.5))

相关问题