Chapter 05

Arithmetic — division, remainders, and the decimal trap

The seven arithmetic operators, which one runs first, where integer division and remainders earn their keep, and why decimals sometimes answer strangely.

30 minPython 3.12
  1. 1Encounter
  2. 2Understand
  3. 3Worked
  4. 4Predict
  5. 5Apply
  6. 6Stretch

The problem we are solving

There are 17 books and a box holds 5. How many boxes, and how many books are left over?

The answer is not 17 / 5. That gives 3.4, and there is no such thing as 3.4 boxes. You need two separate answers: how many whole ones and how much is left. Python has an operator for each, and they turn out to be two of the most useful ones you will learn — pagination, splitting seconds into hours and minutes, testing whether a number is even, all come from here.

There is a second thing that stops everyone the first time they see it: in Python, 0.1 + 0.2 does not give 0.3. Why it does not, and what to do about it, is also in this mission.

By the end of this mission you can

  • Use the seven arithmetic operators
  • Say which calculation runs first, and when parentheses are needed
  • Recognise where // and % are the right tool
  • Explain why / always produces a decimal, even when the division is exact
  • Write calculations that respect the precision limits of decimal numbers

Prerequisites: Variables and types.


The seven operators

python
print(10 + 3)
print(10 - 3)
print(10 * 3)
print(10 / 3)
print(10 // 3)
print(10 % 3)
print(10 ** 3)
text
13
7
30
3.3333333333333335
3
1
1000

The last three deserve a word:

  • // — floor division. Divides and keeps the whole part, discarding the rest
  • % — modulo, the remainder. What is left after the division
  • ** — power. 10 ** 3 is ten cubed
/ always produces a decimal. Even 10 / 5 answers 2.0, not 2. If the type depended on whether the division came out exact, a program's behaviour would be unpredictable, so Python keeps one rule here: / means float. When you need a whole number, use //.

Solving the box problem

python
books = 17
per_box = 5

full_boxes = books // per_box
left_over = books % per_box

print("Full boxes:", full_boxes)
print("Left over :", left_over)
text
Full boxes: 3
Left over : 2

Three boxes fill up and 2 books stay outside. // and % come as a pair — one says "how many whole ones", the other "how much is left".

The same pair shows up in plenty of other places:

python
total_seconds = 500

minutes = total_seconds // 60
seconds = total_seconds % 60

print(minutes, "min", seconds, "sec")
text
8 min 20 sec

And % is how you test even and odd. Divide any number by 2 and the remainder is 0 (even) or 1 (odd):

python
print(14 % 2)
print(15 % 2)
text
0
1

That remainder is exactly what the mission on conditions will build on.

Which calculation runs first

Python follows the ordinary rules of arithmetic — multiplication and division before addition and subtraction:

python
print(10 + 5 * 2)
print((10 + 5) * 2)
text
20
30

On the first line 5 * 2 happened first, then 10 was added. On the second, the parentheses changed the order.

Highest to lowest:

  1. **
  2. *, /, //, %
  3. +, -
python
print(2 + 3 * 4 ** 2)
text
50

4 ** 2 is 16, then 3 * 16 is 48, then 2 + 48 is 50.

When in doubt, use parentheses. Even knowing the rules, (a + b) / 2 is better written than a + b / 2 — the next person to read it does not have to recall any rule. Extra parentheses do not slow the code down; they only make it clear.

This is the single most common beginner mistake, and it shows up when averaging:

python
a = 10
b = 20
c = 30

wrong = a + b + c / 3
right = (a + b + c) / 3

print("Wrong:", wrong)
print("Right:", right)
text
Wrong: 40.0
Right: 20.0

The program raised no error. Nothing in red, nothing broken — just a wrong answer. That is the most dangerous class of bug there is, because Python will not warn you. Only checking the arithmetic yourself catches it.

The decimal trap

python
print(0.1 + 0.2)
text
0.30000000000000004

This is not Python's fault. Computers store numbers in base two, and 0.1 cannot be written exactly in base two — just as 1/3 cannot be written exactly in decimal, where 0.3333... runs on forever. So the computer stores something extremely close, and adding two of them makes the tiny difference visible. JavaScript, Java and C all behave the same way.

Two practical consequences:

One — round when you display:

python
total = 0.1 + 0.2
print(round(total, 2))
text
0.3

round(total, 2) means keep two places after the decimal point.

Two — never compare two decimals with ==:

python
print(0.1 + 0.2 == 0.3)
text
False

This is the trap that produces wrong answers in financial calculations. Serious projects do not hold money in a float — they store the smallest unit as a whole number (49.99 becomes 4999), or they use the separate decimal module. For now just know where the limit is; modules come later.


A complete example

packing.py:

python
# Work out packing and shipping for one order
total_items = 47
items_per_box = 6
box_cost = 35.5

full_boxes = total_items // items_per_box
loose_items = total_items % items_per_box

# A partly filled box still costs a full box
boxes_needed = full_boxes
if loose_items > 0:
    boxes_needed = full_boxes + 1

shipping = boxes_needed * box_cost

print("Items        :", total_items)
print("Full boxes   :", full_boxes)
print("Loose items  :", loose_items)
print("Boxes needed :", boxes_needed)
print("Shipping     :", round(shipping, 2))
text
Items        : 47
Full boxes   : 7
Loose items  : 5
Boxes needed : 8
Shipping     : 284.0

The if line has not been taught yet — that is what the next few missions are for. For now just read it: if any items are left loose, one more box is needed.


When it breaks

ZeroDivisionError: division by zero Something was divided by zero. It is undefined in mathematics too, so Python stops. You have to check the divisor before dividing — which gets easy once you have conditions.

10 / 5 gave 2.0 and I need 2 Use //, or write int(10 / 5). The first is better: the intent is visible in the code.

// answers oddly on negative numbers -7 // 2 gives -4, not -3. Python rounds down, not towards zero. Worth remembering if you work with negatives.

The calculation looks right but the answer is wrong Count the parentheses, and break each step onto its own line and print it. The mistake is almost always operator precedence.

A long tail of digits after the decimal point That is not an error, it is how decimals behave. Shorten it with round() when you display it.