如何在Python中实现向上取整的除法?

8

我希望3/2等于2而不是1.5。

我知道有一个数学术语可以描述这个操作(不是四舍五入),但我现在想不起来了。无论如何,我怎样才能在不使用两个函数的情况下完成这个操作?

以下是我不想要的示例:

answer = 3/2 then math.ceil(answer)=2 (why does math.ceil(3/2)=1?)  

ex of what I DO want:

 "function"(3/2) = 2

2
如果你想让3 <operation> 2 = 2,那绝对不是除法。PS math.ceil(3/2) = 1 因为 3/2 = 1(整数除法),所以你将1进行了向上取整,而不是1.5。 - NullUserException
8
这是一种合法的四舍五入方式,称为“向正无穷取整”(或“远离零取整”)。你们大多数人对这个提问者进行了不公平的攻击。是的,在原生Python中没有一种内置的函数可以实现这个功能。 - smci
4
@Null,你混淆了术语。如果你编写低级别的C宏或汇编代码在DSP芯片上,人们每天都会这样做(参见下面的引用)。如果你的意思是这在“高级语言规范”中看不到,那是对的。有大量的数学操作没有内置的直接高级语言算术运算,比如2^N点的快速傅里叶变换、卷积、相关、拉普拉斯变换等等...重点是OP的问题是合法的(虽然很少见),而且反对票是错误的。 - smci
2
没问题。一些参考资料:EETimes“不同舍入算法介绍”,或阅读大多数DSP芯片的参考手册,例如德州仪器C6xxx系列、ARM Cortex等。我们正在说话时,踩票似乎正在消失;-) - smci
3
@Null,我想指出你上面的第一句话是不正确的:“如果你希望 3 <operation> 2 = 2,那肯定不是除法。”实际上它是除法(向远离零的方向取整的除法),只是不是你熟悉的那种。话说到此为止。 - smci
显示剩余10条评论
8个回答

16
< p > < em >给出简短的答案...

Python只提供了两种类型的除法本地运算符:"真"除法和"向下舍入"除法。因此,你想要的不是一个单独的函数。然而,使用一些简短的表达式可以轻松实现多种不同类型的带有舍入的除法。

根据标题的要求:给定严格的整数输入,“向上舍入”除法可以使用(a+(-a%b))//b来实现,“远离零”除法可以使用更复杂的a//b if a*b<0 else (a+(-a%b))//b来实现。其中之一可能是你想要的。至于为什么...


为了给出更详细的答案...

首先,让我回答一下有关为什么 3/2==1math.ceil(3/2)==1.0 的子问题,以解释 Python 除法运算符的工作方式。这里有两个主要问题...

float vs int division: 在 Python 2 中,除法的行为取决于输入的类型。如果 ab 都是整数,则 a/b 执行“向下取整”或“地板整数”除法(例如 3/2==1,但 -3/2==-2)。这等效于 int(math.floor(float(a)/b))

但是,如果 ab 中至少有一个是浮点数,则 Python 执行“真实”除法,并给出一个 float 结果(例如 3.0/2==1.5,且 -3.0/2==-1.5)。这就是为什么有时会看到构造 float(a)/b:它被用来强制执行真实除法,即使两个输入都是整数(例如 float(3)/2==1.5)。这就是为什么你的示例 math.ceil(3/2) 返回 1.0,而 math.ceil(float(3)/2) 返回 2.0。结果在到达 math.ceil() 之前已经被向下舍入。

默认情况下进行"真实除法":在2001年,决定(PEP 238)将Python的除法运算符更改为始终执行"真实"除法,无论输入是浮点数还是整数(例如,这将使3/2==1.5)。为了不破坏现有脚本,改变默认行为的更改被推迟到Python 3.0; 为了在Python 2.x下获得此行为,您必须通过在文件顶部添加from __future__ import division来启用它。否则,将使用旧的类型相关行为。

但是,“向下取整”除法仍然经常需要,因此PEP并没有完全摆脱它。相反,它引入了一个新的除法运算符:a//b,即使输入包括浮点数,也始终执行向下取整除法。这可以在Python 2.2+和3.x下都不需要特殊处理地使用。


排除这种情况,带有四舍五入的除法:

为了简化事情,在处理整数时,以下表达式都使用a//b运算符,因为它在所有Python版本中的行为相同。 同样,我假设如果b是正数,则0<=a%b<b,如果b是负数,则b<=a%b<=0。 这是Python的行为,但其他语言可能具有略有不同的模数运算符。

带有四舍五入的四种基本整数除法类型:

  • "round down" 也称为 "向下取整" 或 "负无穷大取整" 的除法: Python 可以通过 a//b 实现这一功能。

  • "round up" 也称为 "向上取整" 或 "正无穷大取整" 的除法: 可以通过 int(math.ceil(float(a)/b))(a+(-a%b))//b 实现。后者的原因是,如果 ab 的倍数,则 -a%b 为 0,否则它是我们需要添加到 a 中的数量,以达到下一个最高的倍数。

  • "round towards zero" 也称为 "截断取整" 的除法 - 可以通过 int(float(a)/b) 实现。如果不使用浮点数进行计算,则更加棘手...因为 Python 仅提供向下取整的整数除法,而 % 运算符具有类似的向下取整偏差,我们没有任何非浮点运算符可以在 0 周围对称地进行取整。因此,我能想到的唯一方法就是将向下取整和向上取整构造成分段表达式:a//b if a*b>0 else (a+(-a%b))//b

  • "round away from zero" 也称为 "远离零取整" 的除法 - 不幸的是,这比截断取整还要棘手。我们无法利用 int 运算符的截断行为,因此即使包括浮点运算符,我也想不出一个简单的表达式。因此,我必须使用截断取整表达式的反向操作,并使用 a//b if a*b<0 else (a+(-a%b))//b

请注意,如果您仅使用正整数,(a+b-1)//b提供了比上述任何解决方案更有效的向上/远离零的四舍五入,但对于负数则无效。
希望这有所帮助...如果有人能提出更好的四舍五入公式,我很乐意进行修改。我发现我目前使用的不太令人满意。

1
那些末尾的整数 -- 那才是正确的方法。 - Owen
谢谢您的建议。我重构了我的答案,以提供更多细节,并强调顶部的表达式。希望我没有破坏答案 :) - Eli Collins
仅适用于正整数,(a // b)+1 怎么样? - Devy
1
@Devy 对于“向上取整”除法,(a // b)+1对于b的倍数返回错误的值--例如,(10//5)+1返回3,而不是2。 - Eli Collins

4

该帖子的意图是"如何在Python中实现向正无穷取整的除法"(建议您更改标题)。

根据IEEE-754标准(请阅读这个概述),这是一种完全合法的舍入模式,称为"向正无穷取整"(或"远离零取整")。大多数9个反对票都是不公平地攻击楼主。是的,在本地Python中没有单函数方法来实现这一点,但我们可以使用round(float(a)/b),或者子类化numbers.Number并重写__div__()

楼主需要澄清他们是否希望-3/2向-2或-1取整(或者对于负操作数不关心)。由于他们已经说过他们不想向上取整,我们可以推断-3/2应该向-2取整。

足够的理论。对于实现:

  • 如果你只想要一个快速且简单的向上取整的解决方案,可以使用 round(float(a)/b)
  • math.ceil(float(a)/b) 会向上取整,而你说你不想要这个

  • 但是,如果这是你默认的除法操作,或者你经常需要这样做,那么可以像下面的伪代码一样做:numbers.Number的子类Real、Rational或Integral中继承(2.6版中新增),重新定义 __div__() 或者定义一个非默认的替代 __divra__() 操作。你可以定义一个类成员或者类方法 rounding_mode 并在除法运算中查找它。但是要注意 __rdiv__() 和普通浮点数混合使用的问题。

.

import numbers

class NumberWithRounding(numbers.Integral):
    # Here you could implement a classmethod setRoundingMode() or member rounding_mode
    def __div__(self,other):
        # here you could consider value of rounding_mode, or else hardwire it like:
        return round(float(self)/other)
    # You also have to raise ImplementationError/ pass/ or implement the other 31
    # methods for Float: __abs__(),...,__xor__() Just shortcut that for now...

4

Python 3 中的整数除法:

3 // 2 == 1

Python 3 中的非整数除法:

3 / 2 == 1.5

你所说的并不是一种完全的除法。


虽然也许你可以指出Python 3默认执行浮点数除法,但Python 2.6默认执行整数除法。 - Mr. Shickadance
1
Python 2.6.1(不是3):3/2 -> 13.0/2 -> 1.53.0//2 -> 1.0 - user166390

2
当你将两个整数相除时,结果是一个整数。
3 / 2 等于 1,而不是 1.5
请参阅文档的注释1:

对于(普通或长)整数除法,结果是一个整数。结果总是向负无穷大舍入:1/2 是 0,(-1)/2 是 -1,1/(-2) 是 -1,(-1)/(-2) 是 0。请注意,如果任何操作数是长整型,则结果是长整型,无论其数值如何。

当你从除法中得到 1 后,就没有办法将其变成 2
要得到 1.5,你需要使用浮点数除法:3.0 / 2
然后,你可以调用 math.ceil 来获得 2
你错了;不存在将数字进行除法运算,然后向上舍入的数学函数。
你能做的最好的事情就是编写自己的函数,它接受两个浮点数并调用 math.ceil

但是如果您仔细阅读问题,原帖的作者明确要求不要进行四舍五入或向上取整。 - NullUserException
1
@Null:你说得对,我没有注意到。这是他能够接近的最近距离了。 - SLaks
我认为这个问题没有有效的答案;我不知道有哪种算术运算能够得到3和2之间等于2的结果。 - NullUserException
@Null:就像我之前评论的那样,这被称为“向正无穷舍入”(或“远离零舍入”)。确实,使用“向正无穷舍入”的除法将是一种合法的数学运算,可以得到这个结果(这在FP硬件上很少实现,这是你的意思)。OP可以子类化numbers.Number或其子类,重新定义__div__()或定义__divra__()。 - smci
@Franklin:他想要始终向上取整。 - SLaks
显示剩余4条评论

1
你可能想要的是类似这样的东西:
math.ceil(3.0/2.0)
# or
math.ceil(float(3)/float(2))

你也可以使用“from future”进行导入:

from __future__ import division
math.ceil(3/2) # == 2

但如果你这样做,想要得到整数除法的当前行为,你需要使用双斜杠:

3 // 2 == 1 # True

1

在gmpy2中,可以进行向上取整(+Inf),向下取整(-Inf)和截断(到0)的整数除法。

>>> gmpy2.c_div(3,2)
mpz(2)
>>> help(gmpy2.c_div)
Help on built-in function c_div in module gmpy2:
c_div(...)
    c_div(x,y): returns the quotient of x divided by y. The quotient
    is rounded towards +Inf (ceiling rounding). x and y must be integers.
>>> help(gmpy2.f_div)
Help on built-in function f_div in module gmpy2:
f_div(...)
    f_div(x,y): returns the quotient of x divided by y. The quotient
    is rounded towards -Inf (floor rounding). x and y must be integers.
>>> help(gmpy2.t_div)
Help on built-in function t_div in module gmpy2:
t_div(...)
    t_div(x,y): returns the quotient of x divided by y. The quotient
    is rounded towards 0. x and y must be integers.
>>>

gmpy2 可在 http://code.google.com/p/gmpy/ 获取。

(免责声明:我是 gmpy 和 gmpy2 的当前维护者。)


1
我认为你要找的是这个:
假设你有x(3)和y(2),
result =(x + y-1)// y;
这相当于没有使用浮点数的上取整。
当然,y不能为0。

0
首先,您希望在参数中使用浮点除法。使用以下代码:
from __future__ import division

如果您想要始终向上取整,使得 f(3/2)==2f(1.4)==2,那么您需要让 fmath.trunc(math.ceil(x))
如果您想要获取最接近的整数,但是在遇到平局时向上取整,那么您需要使用 math.trunc(x + 0.5)。这样,f(3/2)==2 并且 f(1.4)==1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接