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

Question:

I was figuring out how to do floor/ceiling operations without the math module. I solved this by using floor division //, and found out that the negative "gives the ceiling". So this works:

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

I would like the answer to be positive, so first I tried --3//2, but this gives 1. I inferred this is because Python evaluates -- to +. So to solve this, I found out I could use -(-3//2)), problem solved.

But I came over another solution to this, namely (I included the previous example for comparison):

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

I am unable to explain why including the 0 helps. I have read the documentation on division, but I did not find any help there. I thought it might be because of the evaluation order:

If I use --3//2 as an example, from the documentation I have that Positive, negative, bitwise NOT is strictest in this example, and I guess this evaluates -- to +. Next comes Multiplication, division, remainder, so I guess this is +3//2 which evaluates to 1, and we are finished. I am unable to infer it from the documentation why including 0 should change the result.

References:

Asked By: Karl Wilhelm

||

Answers:

Python uses the symbol - as both a unary (-x) and a binary (x-y) operator. These have different operator precedence.

In specific, the ordering wrt // is:

  • unary -
  • binary //
  • binary -

By introducing a 0 as 0--3//2, the first - is a binary - and is applied last. Without a leading 0 as --3//2, both - are unary and applied together.

The corresponding evaluation/syntax tree is roughly like this, evaluating nodes at the bottom first to use them in the parent node:

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

Because the unary - are applied together, they cancel out. In contrast, the unary and binary - are applied before and after the division, respectively.

Answered By: MisterMiyagi

This is a simple matter of order of operations.

--3//2 is the same as (-(-3)) // 2. Since there is nothing on the left-hand side, each - must be unary negation; and this has higher precedence than //; so 3 is negated twice (yielding 3) and then divided by 2.

0--3//2 is the same as 0 - ((-3) // 2). Now that there is something on the left-hand side, the first - must be binary subtraction, which has lower precedence than //. The second - is still unary negation; -3 is divided by 2 yielding -2, and then that value is subtracted from 0.

Answered By: Karl Knechtel

Another way to know how CPython actually calculates is to use the dis module to see what it actually does with its stack machine.

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

Whoops, constants are calculated during compilation, so use a name.

>>> 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
Answered By: Terry Jan Reedy
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.