【问题标题】:Why is there a difference between `0--3//2` and `--3//2`?为什么`0--3//2`和`--3//2`之间有区别?
【发布时间】:2021-12-03 13:33:11
【问题描述】:

我正在研究如何在没有math 模块的情况下进行地板/天花板操作。我通过使用地板除法// 解决了这个问题,并发现负数“给出了天花板”。所以这行得通:

>>> 3//2
1
>>> -3//2
-2

我希望答案是肯定的,所以首先我尝试了--3//2,但这给出了 1。我推断这是因为 Python 将 -- 评估为 +。所以为了解决这个问题,我发现我可以使用-(-3//2)),问题解决了。

但是我找到了另一个解决方案,即(我将前面的示例用于比较):

>>> --3//2  # Does not give ceiling
1
>>> 0--3//2  # Does give ceiling
2

我无法解释为什么包含 0 会有所帮助。我已阅读有关部门的文档,但在那里没有找到任何帮助。我认为可能是因为评估顺序:

如果我以--3//2 为例,从文档中我知道Positive, negative, bitwise NOT 在此示例中是最严格的,我猜这会将-- 评估为+。接下来是Multiplication, division, remainder,所以我猜这是+3//2,其计算结果为1,我们就完成了。我无法从文档中推断出为什么包含 0 会改变结果。

参考资料:

【问题讨论】:

  • -3//2给-2 是仍然给你发言权; floor(x)是小于x的最大整数,-2是小于-1.5的最大整数。
  • @Hearth 你是对的,但问题的“精神”是寻求 1.5 的上限,而不是 -1.5。但我知道这是不精确的。
  • @chepner 我相信他们的意思是他们想用这个操作作为一种迂回的方式来做正数的上限。所以他们不期望-3//2 是积极的,他们希望以一种可以有效使用math.ceil(3/2) 的方式将结果始终转换为积极的。
  • @chepner -3//2 的点只是他们获得3/2 的“ceil”的第一步。第二步是将其转换回正/绝对值,这引发了这个问题,因为它没有达到预期的效果。
  • @ttbek:与在地板上添加一个相比,它的优势在于它给出了正确的答案! (例如,如果 a=4,b=2 则 (a // b) +1 给出 3 但 ceil(4/2) 为 2。

标签: python operator-precedence


【解决方案1】:

Python 使用符号 - 作为 unary (-x) 和 binary (x-y) 运算符。这些有不同的operator precedence。

具体来说,// 的顺序是:

  • 一元-
  • 二进制//
  • 二进制-

通过将0 引入为0--3//2,第一个- 是一个二进制 -,并在最后应用。如果没有前导 0 作为 --3//2,则 - 都是一元并一起应用。

对应的求值/语法树大致是这样的,先求值最底层的节点在父节点中使用:

 ---------------- ---------------- 
|     --3//2     |    0--3//2     |
|================|================|
|                |    -------     |
|                |   | 0 - z |    |
|                |    -----+-     |
|                |         |      |
|     --------   |     ----+---   |
|    | x // y |  |    | x // y |  |
|     -+----+-   |     -+----+-   |
|      |    |    |      |    |    |
|  ----+    +--  |   ---+    +--  |
| | --3 |  | 2 | |  | -3 |  | 2 | |
|  -----    ---  |   ----    ---  |
 ---------------- ---------------- 

因为一元 - 一起应用,所以它们抵消了。相比之下,一元和二元 - 分别应用于 before 和 after 除法。

【讨论】:

  • 啊,我还是个新手,所以 unary 和 binary 之间的区别让我迷失了方向。在您回答后,我尝试了这个:0+--3//2 == 1,因此0 右侧的第一个运算符被视为binary 运算符是有道理的。
  • @KarlWilhelm 在代码中添加空格和括号可以更清楚地了解发生了什么。除非您正在为 CodeGolf.SE 编写代码,并尝试保存字节,否则最好像这样更清晰地格式化内容......
  • @KarlWilhelm 一元运算是一个接受一个参数的函数。二元运算是一个需要两个的函数。我发现在解释发生的事情时画括号很有帮助。 0--3//2 是 0 - (-3 // 2),而 --3//2 是 (--3) // 2
  • 现在我想知道在 Python 中“-3”中的“-”是一元运算符还是整数文字的一部分。
  • @CarstenS 从语法上讲,它不是文字的一部分——AST 基本上将其表示为UnaryOp("-", 3)(只是不那么漂亮)。 An integer literal is just the absolute part。但是,它在生成字节码时进行评估,并在运行时直接加载为值-3。
【解决方案2】:

这是一个简单的操作顺序问题。

--3//2 与(-(-3)) // 2 相同。由于左边没有任何东西,每个- 必须是一元否定;这比//具有更高的优先级;所以3 被否定两次(产生 3)然后除以 2。

0--3//2 与0 - ((-3) // 2) 相同。现在左边有东西,第一个- 必须是二进制减法,它的优先级低于//。第二个-仍然是一元否定; -3 除以2 得到-2,然后从0 中减去该值。

【讨论】:

  • 修正了错字。毕竟那里有很多小数字。
【解决方案3】:

了解 CPython 实际计算方式的另一种方法是使用 dis 模块查看它对其堆栈机器的实际操作。

>>> import dis
>>> dis.dis('0--3//2')
  1           0 LOAD_CONST               0 (2)
              2 RETURN_VALUE

哎呀,常量是在编译过程中计算出来的,所以用个名字吧。

>>> t=3
>>> dis.dis('0--t//2')
  1           0 LOAD_CONST               0 (0)
              2 LOAD_NAME                0 (t)
              4 UNARY_NEGATIVE
              6 LOAD_CONST               1 (2)
              8 BINARY_FLOOR_DIVIDE
             10 BINARY_SUBTRACT
             12 RETURN_VALUE

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-06
    • 1970-01-01
    相关资源
    最近更新 更多