Skip to content
Advertisement

What’s the mathematical reason behind Python choosing to round integer division toward negative infinity?

I know Python // rounds towards negative infinity and in C++ / is truncating, rounding towards 0.

And here’s what I know so far:

               |remainder|
-12 / 10  = -1,   - 2      // C++
-12 // 10 = -2,   + 8      # Python

12 / -10  = -1,     2      // C++
12 // -10 = -2,   - 8      # Python

12 / 10  = 1,      2       // Both
12 // 10 = 1,      2

-12 / -10 = 1,    - 2      // Both
          = 2,    + 8

C++:
1. m%(-n) == m%n
2. -m%n == -(m%n)
3. (m/n)*n + m%n == m

Python:
1. m%(-n) == -8 == -(-m%n)
2. (m//n)*n + m%n == m

But why Python // choose to round towards negative infinity? I didn’t find any resources explain that, but only find and hear people say vaguely: “for mathematics reasons”.

For example, in Why is -1/2 evaluated to 0 in C++, but -1 in Python?:

People dealing with these things in the abstract tend to feel that rounding toward negative infinity makes more sense (that means it’s compatible with the modulo function as defined in mathematics, rather than % having a somewhat funny meaning).

But I don’t see C++ ‘s / not being compatible with the modulo function. In C++, (m/n)*n + m%n == m also applies.

So what’s the (mathematical) reason behind Python choosing rounding towards negative infinity?


See also Guido van Rossum’s old blog post on the topic.

Advertisement

Answer

But why Python // choose to round towards negative infinity?

I’m not sure if the reason why this choice was originally made is documented anywhere (although, for all I know, it could be explained in great length in some PEP somewhere), but we can certainly come up with various reasons why it makes sense.

One reason is simply that rounding towards negative (or positive!) infinity means that all numbers get rounded the same way, whereas rounding towards zero makes zero special. The mathematical way of saying this is that rounding down towards −∞ is translation invariant, i.e. it satisfies the equation:

round_down(x + k) == round_down(x) + k

for all real numbers x and all integers k. Rounding towards zero does not, since, for example:

round_to_zero(0.5 - 1) != round_to_zero(0.5) - 1

Of course, other arguments exist too, such as the argument you quote based on compatibility with (how we would like) the % operator (to behave) — more on that below.

Indeed, I would say the real question here is why Python’s int() function is not defined to round floating point arguments towards negative infinity, so that m // n would equal int(m / n). (I suspect “historical reasons”.) Then again, it’s not that big of a deal, since Python does at least have math.floor() that does satisfy m // n == math.floor(m / n).


But I don’t see C++ ‘s / not being compatible with the modulo function. In C++, (m/n)*n + m%n == m also applies.

True, but retaining that identity while having / round towards zero requires defining % in an awkward way for negative numbers. In particular, we lose both of the following useful mathematical properties of Python’s %:

  1. 0 <= m % n < n for all m and all positive n; and
  2. (m + k * n) % n == m % n for all integers m, n and k.

These properties are useful because one of the main uses of % is “wrapping around” a number m to a limited range of length n.


For example, let’s say we’re trying to calculate directions: let’s say heading is our current compass heading in degrees (counted clockwise from due north, with 0 <= heading < 360) and that we want to calculate our new heading after turning angle degrees (where angle > 0 if we turn clockwise, or angle < 0 if we turn counterclockwise). Using Python’s % operator, we can calculate our new heading simply as:

heading = (heading + angle) % 360

and this will simply work in all cases.

However, if we try to to use this formula in C++, with its different rounding rules and correspondingly different % operator, we’ll find that the wrap-around doesn’t always work as expected! For example, if we start facing northwest (heading = 315) and turn 90° clockwise (angle = 90), we’ll indeed end up facing northeast (heading = 45). But if then try to turn back 90° counterclockwise (angle = -90), with C++’s % operator we won’t end up back at heading = 315 as expected, but instead at heading = -45!

To get the correct wrap-around behavior using the C++ % operator, we’ll instead need to write the formula as something like:

heading = (heading + angle) % 360;
if (heading < 0) heading += 360;

or as:

heading = ((heading + angle) % 360) + 360) % 360;

(The simpler formula heading = (heading + angle + 360) % 360 will only work if we can always guarantee that heading + angle >= -360.)

This is the price you pay for having a non-translation-invariant rounding rule for division, and consequently a non-translation-invariant % operator.

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement