【问题标题】:Is there a ceiling equivalent of // operator in Python?Python 中是否存在相当于 // 运算符的上限?
【发布时间】:2021-08-06 21:24:41
【问题描述】:

我发现了 Python 中的 // 运算符,它在 Python 3 中使用地板进行除法。

有没有用 ceil 代替的运算符? (我知道/ 运算符在 Python 3 中进行浮点除法。)

【问题讨论】:

  • 重要提示:您想要 int 还是 float 结果?
  • 您应该将接受的答案更改为 dlitz 的。 math.ceil 用于浮点数,它不适用于 Python 的任意精度长整数。
  • @milllimoose 这个问题是有效的,因为 1)“ceil 除法”也基于“模除法”,2)数学并没有真正说明什么是常见的,什么不是,3)对于“连续装箱问题”,您需要此操作,即需要多少个大小为 $k$ 的盒子来包装 $n$ 件物品。

标签: python python-3.x


【解决方案1】:

没有与 ceil 相除的运算符。你需要import math并使用math.ceil

【讨论】:

  • 所以 foobar = math.ceil(foo / bar)?嗯,我可以忍受,不知道我想在哪里使用它,只是好奇,谢谢
  • –1 不要使用,对于非常大的整数,这将开始失败。要么使用多精度算术库,要么使用this 方法留在整数域中。
  • 肯定停留在整数域中。这几乎可以保证性能更高并且不那么令人头疼。
  • 请注意,math.ceil 的精度限制为 53 位。如果您使用的是大整数,则可能无法得到准确的结果。
【解决方案2】:

当将x 除以d 时,您可以使用(x + (d-1)) // d,例如(x + 4) // 5.

【讨论】:

  • 这是我一直使用的经典方法。但不适用于负除数。
  • 它将same result 生成为math.ceil()
  • @Abhijeet 是的,这就是问题所在。除了它对sys.float_info.max 以上的大整数效果更好,而且不需要导入。
【解决方案3】:

你也可以直接内联

((foo - 1) // bar) + 1

在 python3 中,这比强制浮点除法和调用 ceil() 快一个数量级,前提是您关心速度。你不应该这样做,除非你已经通过使用证明你需要这样做。

>>> timeit.timeit("((5 - 1) // 4) + 1", number = 100000000)
1.7249219375662506
>>> timeit.timeit("ceil(5/4)", setup="from math import ceil", number = 100000000)
12.096064013894647

【讨论】:

  • 我自己运行了这些测试,我得到了大约 12.5 秒,嗯,当速度差异如此巨大时,我为什么不关心速度呢?
  • @Cradam 请注意,他正在使用 1 亿次调用 (number=100000000)。每次调用,差异都非常小。
  • 因为代码清晰胜过一切。在这种情况下,清晰度可能是客观的。但是您应该始终首先使可读/可维护。当且仅当您发现了性能检查点时,您才能打破规则。现代机器速度如此之快,因此您的程序正在执行的所有其他操作通常都会使这种差异消失在噪音中。
  • @TravisGriggs 使用整数数学而不是浮点数学不仅仅是为了速度。对于足够大的整数,浮点数学给出了错误的答案
  • 如果foo = -8bar = -4,例如,答案应该是2,而不是3,就像-8 // -4。 Python地板分割定义为"that of mathematical division with the ‘floor’ function applied to the result"和天花板分割是一样的,但ceil()而不是floor()
【解决方案4】:

请注意,math.ceil 的精度限制为 53 位。如果您使用的是大整数,则可能无法得到准确的结果。

gmpy2 库提供了一个 c_div 函数,该函数使用圆角舍入。

免责声明:我维护 gmpy2。

【讨论】:

  • 如果我正在做一些以数学或科学为导向的事情,这个包会很有用,不过我更喜欢使用核心库的答案。我投了赞成票,因为这是一个有用的答案
  • 哇,可以确认了。 python2 -c 'from math import ceil;assert ceil(11520000000000000102.9)==11520000000000000000'(以及替换python3)两者都是True
【解决方案5】:

不,但您可以使用倒置楼层划分:¹

def ceildiv(a, b):
    return -(a // -b)

这是有效的,因为Python's division operator does floor division(与 C 中的整数除法截断小数部分不同)。

这是一个演示:

>>> from __future__ import division     # for Python 2.x compatibility
>>> import math
>>> def ceildiv(a, b):
...     return -(a // -b)
...
>>> b = 3
>>> for a in range(-7, 8):
...     q1 = math.ceil(a / b)   # a/b is float division
...     q2 = ceildiv(a, b)
...     print("%2d/%d %2d %2d" % (a, b, q1, q2))
...
-7/3 -2 -2
-6/3 -2 -2
-5/3 -1 -1
-4/3 -1 -1
-3/3 -1 -1
-2/3  0  0
-1/3  0  0
 0/3  0  0
 1/3  1  1
 2/3  1  1
 3/3  1  1
 4/3  2  2
 5/3  2  2
 6/3  2  2
 7/3  3  3

为什么用这个而不是 math.ceil?

math.ceil(a / b) 可以悄悄地产生不正确的结果,因为它引入了浮点错误。例如:

>>> from __future__ import division     # Python 2.x compat
>>> import math
>>> def ceildiv(a, b):
...     return -(a // -b)
...
>>> x = 2**64
>>> y = 2**48
>>> ceildiv(x, y)
65536
>>> ceildiv(x + 1, y)
65537                       # Correct
>>> math.ceil(x / y)
65536
>>> math.ceil((x + 1) / y)
65536                       # Incorrect!

一般来说,除非您特别需要,否则完全避免使用浮点运算是一种很好的做法。浮点数学有几个棘手的边缘情况,如果您不密切注意,它们往往会引入错误。在没有硬件 FPU 的小型/低功耗设备上,它的计算成本也可能很高。


¹在此答案的先前版本中,ceildiv 被实现为return -(-a // b),但在评论者报告后者在基准测试中表现稍好后,它被更改为return -(a // -b)。这是有道理的,因为被除数 (a) 通常大于除数 (b)。由于 Python 使用任意精度的算法来执行这些计算,计算一元否定 -a 几乎总是比计算 -b 涉及更多的工作。

【讨论】:

  • @apadana 我同意这是非常聪明的,但不是很可读且难以维护!我决定从数学中导入 ceil,这样当我的一位同事阅读我的代码行时,他就会明白它的作用!
  • @apadana 我不同意。问题询问是否“存在”这个“in”Python 的运算符。根据回复,答案似乎是“否”。不过,我赞成 dlitz 的答案是有用的。
  • @SlimCheney 将此方法放入文档化的函数中,您就可以开始了。性能 + 可读性一扫而过。
  • @SamyBencherif:不仅仅是性能+可读性,还有大输入的正确性;浮点有表示限制,而 Python 的 int 没有(嗯,没有有意义的;在 64 位 Python 上,您仅限于 30 * (2**63 - 1) 位数),甚至临时转换为 float 可能会丢失信息。比较 math.ceil((1 << 128) / 10)-(-(1 << 128) // 10)
  • 这应该只包含在标准库中
【解决方案6】:

解决方案 1:使用否定将地板转换为天花板

def ceiling_division(n, d):
    return -(n // -d)

让人想起Penn & Teller levitation trick,这“把世界颠倒(用否定),使用普通的地板分割(天花板和地板已经交换),然后把世界颠倒过来(再次用否定) )"

解决方案 2:让 divmod() 完成工作

def ceiling_division(n, d):
    q, r = divmod(n, d)
    return q + bool(r)

divmod() 函数为整数提供(a // b, a % b)(由于舍入错误,浮点数可能不太可靠)。只要有非零余数,带有bool(r) 的步骤就会将商加一。

方案3:在除法前调整分子

def ceiling_division(n, d):
    return (n + d - 1) // d

将分子向上平移,使地板除法向下舍入到预期的天花板。请注意,这仅适用于整数。

解决方案 4:转换为浮点数以使用 math.ceil()

def ceiling_division(n, d):
    return math.ceil(n / d)

math.ceil() 代码很容易理解,但它会在整数和浮点数之间转换。这不是很快,并且可能存在舍入问题。此外,它依赖于 Python 3 语义,其中“真正的除法”产生一个浮点数,而 ceil() 函数返回一个整数。

【讨论】:

  • 在快速测试中,#1 是最快的,即使与-(-a // b)o_O 相比也是如此
  • 在此确认-(a // -b)-(-a // b) 快,至少在使用python -m timeit ... 计时玩具示例时是这样
  • @Jasha,知道一个怎么可能比另一个更快吗?您的测试是对称的吗,因为对于a=x, b=y 形式的每种情况,都有a=y, b=x 形式的情况(当然不包括b=0)?
  • @Joe 抱歉,我不记得了 :)
【解决方案7】:

简单的解决方案: a // b + 1

【讨论】:

  • 这对于任何均分的东西都是错误的。 a=4、b=2 等
【解决方案8】:

如果您想从一个数字中提取多个。它就像我们在 excel 中有 Math.celling 一样工作。

def excel_celling(number=None, multiple_off=None):
    quotient = number // multiple_off
    reminder = number % multiple_off
    celling_value = quotient * multiple_off + (multiple_off, 0)[reminder==0]
    return int(celling_value)


assert excel_celling(99.99, 100) == 100, "True"
print(excel_celling(99.99, 100) , 100)
assert excel_celling(1, 100) == 100, "True"
print(excel_celling(1, 100),100)
assert excel_celling(99, 100) == 100, "True"
print(excel_celling(99, 100),100)
assert excel_celling(90, 100) == 100, "True"
print(excel_celling(90, 100),100)
assert excel_celling(101, 100) == 200, "True"
print(excel_celling(101, 100),200)
assert excel_celling(199, 100) == 200, "True"
print(excel_celling(199, 100),200)
assert excel_celling(199.99, 100) == 200, "True"
print(excel_celling(199.99, 100),200)
assert excel_celling(200, 100) == 200, "True"
print(excel_celling(200, 100),200)

结果

100 100

100 100

100 100

100 100

200 200

200 200

200 200

200 200

【讨论】:

    猜你喜欢
    • 2020-06-03
    • 2019-06-17
    • 1970-01-01
    • 1970-01-01
    • 2011-06-27
    • 1970-01-01
    • 2012-04-23
    • 2013-11-07
    相关资源
    最近更新 更多