为什么 `0--3//2` 和 `--3//2` 有区别?

Why is there a difference between `0--3//2` and `--3//2`?

我想知道如何在没有 math 模块的情况下进行 floor/ceiling 操作。我通过使用 floor division // 解决了这个问题,发现负数“给出了上限”。所以这有效:

>>> 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 会改变结果。

参考文献:

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

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

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

通过将 0 作为 0--3//2 引入,第一个 -binary - 并最后应用。没有前导 0 作为 --3//2,两个 - 都是 一元 并一起应用。

对应的evaluation/syntax树大致是这样的,先评估最底层的节点,然后在父节点中使用:

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

因为一元 - 一起应用,所以它们抵消了。相反,一元和二进制 - 分别在 之前 除法之后应用。

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

--3//2 等同于 (-(-3)) // 2。由于左侧没有任何内容,因此每个 - 必须是一元否定;这比 // 具有更高的优先级;所以 3 被否定两次(产生 3)然后除以 2.

0--3//2 等同于 0 - ((-3) // 2)。既然左边有东西,第一个-肯定是二元减法,比//的优先级。第二个-仍然是一元否定; -3 除以 2 得到 -2,然后从 0 中减去该值。

另一种了解 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