使用整数除法进行四舍五入

27 投票
7 回答
58514 浏览
提问于 2025-04-16 05:36

有没有一种简单、符合Python风格的方法,可以在不使用浮点数的情况下,四舍五入到最接近的整数?我想用整数运算来实现以下功能:

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

==============

@John:浮点数在不同平台上可能会有不一样的表现。如果你希望你的代码在不同的平台上都能通过测试,就需要避免使用浮点数(或者在测试中加一些复杂的处理,期望它能正常工作)。上面的代码可能在大多数或所有平台上都能正常工作,但我觉得还是避免使用浮点数更简单。这样做有什么“违背Python精神”的呢?

7 个回答

7

简而言之:

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

这个解决方案:

  • 按照提问者的要求,四舍五入到最近的整数
  • 适用于正整数和负整数
  • 将0.5的分数部分四舍五入到最近的偶数(例如,rnd(0.5)=0rnd(1.5)=2),这和提问者使用的Python的round函数的行为一致
  • 遵循整数运算,符合提问者的要求(可以查看divmod文档

详细内容

受到zhmyh的回答的启发,我想出了以下解决方案(更新:这个方案只适用于非负的总数和剩余量,评论中有提到)

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

因为提问者要求四舍五入到最近的整数,所以zhmyh的方案实际上有点不对,因为它总是向下一个更大的整数四舍五入,而我的方案符合要求只适用于非负的总数和剩余量。一个正确的方案,能够处理负数的是:

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

不过请注意,如果2 * r == surplus,它基本上会对正负结果都进行向上取整,比如ceil(-1.5) == -1,而ceil(1.5) == 2。在四舍五入到最近整数的情况下,这种行为是正确的(因为到下一个更低和更高整数的距离相等),但在零的参考上是不对称的。为了修正这个问题,也就是将0.5向远离零的方向四舍五入,我们可以添加一个布尔条件:

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

更进一步,为了像提问者使用的Python的round函数那样,将0.5四舍五入到最近的偶数

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

如果你想知道divmod是如何定义的:根据它的文档

对于整数,结果与(a // b, a % b)相同。

因此,我们坚持使用整数运算,符合提问者的要求。

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

把数字向左移动一位,相当于把它乘以2;把数字向右移动一位,则是把它除以2,并且结果会向下取整。如果在中间加上1,那么如果结果的小数部分超过0.5,就会向上取整。

这其实和你写的...

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

是一样的,只不过所有的数先乘以2,然后再除以2。这种操作可以用整数运算来完成,因为位移操作不需要浮点数。

44

你可以很简单地做到这一点:

(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 次整数运算。整数运算的耗时可能会根据数字的大小而变化。访问函数内部的局部变量不需要“查找”。

如果你真的很急着追求速度,可以做一些基准测试。否则,保持简单就好。

撰写回答