Why this matters
Many programs split numbers into groups. A game puts scores into bands of ten, a clock turns a count of seconds into days, and a list wraps around when you step past its end. With positive numbers, the // and % operators give the answer you expect. With negative numbers, a different rule may apply, and the program may put a value in the wrong group or read the wrong list slot.
The arithmetic lesson showed how // and % handle negative numbers. This lesson covers the rule behind those answers, and the other rule that you must ask for when you need it. If you know another language, check its rule before you port code. Some languages round division toward zero, and Python does not.
Two words: truncation and floor
Truncation means cutting off the decimal part of a number. Floor means rounding down to the next whole number on the number line, which is the line of all whole numbers from left to right.
For a positive number, the two give the same answer. Seven divided by two is 3.5. Truncation cuts off the 0.5 and gives 3. Rounding down also gives 3.
For a negative number, they differ. Negative seven divided by two is -3.5. Truncation cuts off the 0.5 and gives -3, which is closer to zero. Rounding down moves to the left on the number line and gives -4.
A number line makes this clear. Mark -4, -3 and -2 in order from left to right. The value -3.5 sits halfway between -4 and -3. Truncation moves toward zero and lands on -3. Floor moves left and lands on -4.
Python's // operator rounds down, and its partner % gives the remainder that goes with that rule. You do not need to learn the rules again here. Keep one idea in mind: // answers "how many whole groups, counting down on the number line?"
Python's floor rule
Here is a program that prints the floor answers and the truncated answers for the same inputs. The truncated answers use int, which cuts the decimal part off toward zero. The math.trunc function from the standard math module does the same.
The output is:
-4 1
-4 -1
-2 0
-1 4
truncated -7 / 2: -3
floor -7 / 2: -4
truncated with math.trunc: -3The first line is -7 // 2, which gives -4, because floor division rounds down. The remainder -7 % 2 is 1. The sign of the remainder follows the divisor, which is 2 here, so the remainder is positive.
The second line shows the other sign. 7 // -2 is -4, and 7 % -2 is -1. The divisor is negative, so the remainder is negative.
The third line has no remainder, because -8 divides evenly by 4. The fourth line shows that -1 % 5 is 4, which is the position five steps around from zero, one step back from the start.
The lines with int and math.trunc show truncation. Both give -3, which is the answer Java's / gives. In Python you must write truncation on purpose.
The rule that ties the two operators together is the same as before: a == (a // b) * b + a % b. For -7 and 2, that is (-4) * 2 + 1, which is -7.
The truncated remainder, by hand
The truncated quotient and the truncated remainder can both be built from int. The remainder is then the number minus the truncated quotient times the divisor. The next program does that for several pairs, and prints the library's divmod next to it for comparison:
The output is:
-9 4 truncated: (-2, -1) floor: (-3, 3)
7 -2 truncated: (-3, 1) floor: (-4, -1)
-7 2 truncated: (-3, -1) floor: (-4, 1)
-8 4 truncated: (-2, 0) floor: (-2, 0)Read the first line. The truncated quotient of -9 divided by 4 is -2, because -2.25 moves toward zero. The truncated remainder is -1. The floor quotient is -3, and the floor remainder is 3. Both pairs satisfy the same identity, with different quotients.
The function int(a / b) is fine for small numbers. It uses a float (Python's type for numbers with a decimal point), which cannot hold every large integer exactly. For large values, a truncated integer division without floats is -(-a // b) when a is negative and b is positive. The next program shows the difference:
The output is:
via float: 9007199254740992
via floor: 9007199254740993
negated floor: 9007199254740993The float version loses the last digit, because a float has about 16 significant decimal digits. The integer forms are exact. Use the integer form when the numbers can be large.
Example 1: score bands
A game shows a number from -20 to 19 in bands of ten. The bands are -20 to -11, -10 to -1, 0 to 9 and 10 to 19. For a value t, the start of its band is t divided by 10, then multiplied by 10. The question is which division to use.
The output is:
-3 truncated band 0 floor band -10
-10 truncated band -10 floor band -10
-11 truncated band -10 floor band -20
0 truncated band 0 floor band 0
9 truncated band 0 floor band 0
10 truncated band 10 floor band 10
27 truncated band 20 floor band 20The value -3 belongs in the band -10 to -1. The truncated version gives 0, which is the band for 0 to 9, so -3 is in the wrong band. The floor version gives -10, which is correct.
The value -11 shows the other side. Truncation gives -10 because it moves toward zero. That puts -11 in the band -10 to -1, which is wrong, since -11 is below -10. The floor version gives -20, which is the correct band.
For positive values, both versions agree. The bug only appears for negative values, which is why it can stay hidden until a test uses a negative number. In Python, the plain // form is the correct one for bands.
Example 2: moving backwards around a circle
Five players sit in a circle. They are stored in a list with the indexes 0 to 4. The current player is at index 0. The game needs the player one step backwards, which is index -1. In a circle, one step back from the first slot is the last slot, index 4.
The next program uses the remainder operator, which gives the right answer here, and then the wrong one from the truncated form:
The output is:
floor remainder: Eli
truncated remainder: -1
list at truncated index: EliThe first line is correct. (0 - 1) % 5 gives 4, and the player at index 4 is Eli.
The truncated remainder is -1. Python lists accept negative indexes, and players[-1] is the last item, so the program still prints Eli. The result is right only by luck. Try a larger step back and the list rejects the index:
The output is:
Eli
Traceback (most recent call last):
File "/path/to/main.py", line 3, in <module>
print(players[-6])
~~~~~~~^^^^
IndexError: list index out of rangeThe first line works because -1 counts from the end. The second line fails, because the list has five slots and -6 is one past the start. The floor remainder would have wrapped -6 to slot 4 of a five-slot list. The floor version is the safe choice for any wrap-around index that can go below zero.
Example 3: seconds before 1970 and an hour offset
Many computer clocks count seconds from a fixed start. That start is 1 January 1970 at midnight in UTC, which is a common time standard. A time before the start has a negative count. The count -1 means one second before the start, which is 23:59:59 on 31 December 1969.
Splitting that count into days and seconds of the day needs the floor rule.
The output is:
truncated day 0
floor day -1 second of day 86399
hour 21There are 86,400 seconds in a day, which is why 86400 appears in the code. The truncated division gives day 0, which is the first day of 1970, and that is one day too late. The floor version gives day -1, the day before. The second of the day is 86399, which is 23 hours, 59 minutes and 59 seconds, the last second of the day.
The last line uses the same idea for hours. Two o'clock minus five hours is nine o'clock the previous evening. (2 - 5) % 24 gives 21, which is 9 p.m. on a 24-hour clock.
The example works the same way whatever the offset is. A negative sum needs the floor remainder to land inside the range from 0 to 23.
Common mistakes
Mistake 1: a negative index that Python accepts
Python accepts negative list indexes, so a truncated remainder of -1 does not crash. It reads from the end of the list. The bug is hidden until the index goes below the length of the list, as the example above showed. The fix is to use % with a non-negative divisor, which always gives a value from 0 up to the size minus one.
Mistake 2: a divisor of zero
Both operators still divide. A divisor of zero stops the program, and int(a / b) also stops it, because the division runs first. Check the divisor before the call, or make sure it can never be zero.
The output is:
Traceback (most recent call last):
File "/path/to/main.py", line 1, in <module>
print(5 // 0)
~~^^~~
ZeroDivisionError: division by zeroAll three raise ZeroDivisionError, though the wording of the message can differ between Python versions.
Mistake 3: a float that loses digits
A float cannot hold every integer above about 9 quadrillion. The int(a / b) form can give a wrong answer for large inputs, as the earlier example showed. Use // for integers, and keep the value out of floats.
Mistake 4: expecting Python to behave like the language you came from
The rule in Python is the floor rule. If the code you are porting uses truncation, write the truncation on purpose with int(a / b), math.trunc(a / b) or -(-a // b) for positive divisors. Read each division in the old code and decide which rule it needs.
Practice
Exercise 1. Predict the output of print(-9 // 4, -9 % 4, int(-9 / 4), -9 - int(-9 / 4) * 4).
Show the answer
The output is -3 3 -2 -1. The floor quotient of -9 divided by 4 is -2.25 rounded down, which is -3. The floor remainder is -9 minus -3 times 4, which is 3. The truncated quotient is -2, and the remainder that goes with it is -1.
Exercise 2. A ring has seven slots numbered 0 to 6. You stand in slot 0 and move back three slots. Write the expression that gives the slot number.
Show the answer
Use (0 - 3) % 7. It gives 4, which is the slot three steps back from slot 0 in a ring of seven slots.
Exercise 3. A score of -15 is placed in bands of ten, the same as Example 1. What is the first number of its band?
Show the answer
-15 // 10 gives -2. Multiply by 10 to get -20. The band is -20 to -11.
Exercise 4. Write a function wrap_index(index, size) that returns a valid slot number from 0 to size - 1 for any index, including negative ones. Print wrap_index(-1, 5) and wrap_index(5, 5).
Show the answer
The output is 4 0. Index -1 wraps to the last slot, and index 5 wraps back to slot 0.
What you now know
- Python's
//rounds down, and its%gives the remainder the sign of the divisor. - Truncation toward zero is a different rule. Ask for it with
int(a / b),math.truncor-(-a // b). - Score bands, wrap-around indexes and clock arithmetic need the floor rule when the numbers can be negative.
int(a / b)uses afloat, which loses digits for large integers.- A negative list index counts from the end, so a wrong negative index can read the wrong item without an error.
- A divisor of zero raises
ZeroDivisionErrorfor//and%alike.