Python lesson 3 of 9
Python Arithmetic and Numbers
How Python does maths - why division always gives a float, what // and % really answer, powers, int versus float, and why 0.1 + 0.2 is not 0.3.
Published · Every example on this page was run before it was published.
Printing text is how a program talks. Arithmetic is how it works something out before it speaks. A checkout page totals a bill, a timer turns a pile of seconds into a clock reading, a scoreboard adds one more point — all of that is numbers being combined, with a handful of operators you can learn in a single sitting.
Python's arithmetic looks like the sums you did on paper, and most of the time it behaves that way. Two plus three really is five. But a few details are genuinely different, for good reasons rather than out of awkwardness. Division hands you a decimal even when it did not have to. Two extra division operators answer a question plain division cannot. And numbers with a decimal point carry a small permanent inaccuracy that will turn up in your output whether you expect it or not.
This lesson takes the operators in the order you meet them, then spends real time on the three things
that catch people out: what / actually gives you, what // and % are for, and why 0.1 + 0.2
refuses to equal 0.3. Nothing here goes beyond the variables, types and printing from the two
lessons before it.
The Four Operators You Write Every Day
Addition, subtraction, multiplication and division are written +, -, * and /. Python works the
answer out the instant it reads the line, and variables serve just as well as plain numbers:
apples = 12
oranges = 5
print(apples + oranges)
print(apples - oranges)
print(apples * oranges)
print(apples / oranges)17
7
60
2.4Multiplication is the one that looks unfamiliar. On paper you would write a small x, or a dot, or nothing at all beside a bracket. Python accepts none of those: the letter x is a perfectly good variable name, so it cannot double as an operator. Multiplication is always a star:
width = 3
height = 4
print(width * height)
print(2 * (width + height))12
14That second line is the perimeter of a rectangle, and the star sits before the bracket even though a textbook would leave it out. There is no implied multiplication in Python.
Division Always Hands Back a Decimal
Here is the most surprising rule in Python arithmetic, and the one worth committing to memory today:
/ always produces a number with a decimal point, even when the division comes out perfectly even.
print(10 / 2)
print(9 / 3)
print(100 / 4)
print(type(10 / 2))5.0
3.0
25.0
<class 'float'>Ten divided by two is five, and Python prints 5.0. Not a mistake, not a rounding artefact — a
deliberate rule. A number with a decimal point is called a float, short for "floating point", and
/ is defined to produce one every single time. The .0 tells you what kind of number you are holding,
not that something went wrong.
The rule exists so that / is predictable. If division sometimes gave a whole number and sometimes a
decimal, you could never be sure what type your result was without knowing the exact values involved.
Where it bites is the moment you print a result for a human. "You have 5.0 messages" reads badly. Once
a stray .0 appears in your output, a / upstream is almost always the cause, and the fix is either a
different operator or a conversion — both are below.
Whole Groups and What Is Left Over
Plain division is not the only useful answer to "how does this number split up". Picture a hundred eggs going into cartons that hold a dozen. Two answers are true at once here, and neither of them is 8.33. The first is how many cartons you can close and stack: eight. The second is how many eggs are still sitting on the counter with nowhere to go: four.
These are two halves of one question, and Python gives each half its own operator. // is floor
division, which reports how many whole groups fit. % is the modulo operator, which reports what
is left over:
print(100 // 12)
print(100 % 12)8
4The % symbol will look familiar from percentages, and that reading is no help at all here. Python
borrowed the symbol for something unrelated: the remainder, the part that did not fit. Feed both
operators plain whole numbers and plain whole numbers come back, which is what counting needs when the
things being counted cannot be cut in half.
The pair earns its keep as soon as the numbers are too big to hold in your head. A school trip takes a hundred and thirty-seven students, and each coach seats forty-five:
students = 137
seats_per_coach = 45
full_coaches = students // seats_per_coach
still_waiting = students % seats_per_coach
print("Students:", students)
print("Full coaches:", full_coaches)
print("Still waiting:", still_waiting)
print("Back to the total:", full_coaches * seats_per_coach + still_waiting)Students: 137
Full coaches: 3
Still waiting: 2
Back to the total: 137That last line is the reason to trust the pair. Multiply the whole groups back up, add the remainder, and you land exactly on the number you started with. The relationship always holds, so it is the quickest way to check you have the operators the right way round.
One more property turns % from a curiosity into a reliable tool: divide by a positive number and
the remainder is always smaller than it. Split anything into groups of eight and what is left over can
only be 0 through 7, because 8 would be another whole group:
print(15 // 8, 15 % 8)
print(16 // 8, 16 % 8)
print(23 // 8, 23 % 8)
print(7 // 8, 7 % 8)1 7
2 0
2 7
0 7A remainder of 0, as on the second line, means the division was exact. And when the total is smaller
than the group size, as on the last line, you get zero whole groups and the entire amount left over.
Neither case needs special handling — the operators simply tell the truth.
That zero-remainder fact gives you the standard way to ask whether a number divides evenly, which in practice usually means asking whether it is even:
print(10 % 2)
print(11 % 2)
print(2506 % 10)0
1
6An even number leaves nothing when divided by two, so number % 2 is 0 for evens and 1 for odds.
The third line applies the same idea to digits: % 10 peels off the final digit of a number.
Powers with the ** Operator
Two stars mean "raised to the power of". 2 ** 3 is three twos multiplied together:
print(2 ** 3)
print(5 ** 2)
print(10 ** 6)
print(9 ** 0.5)
print(2 ** 0.5)8
25
1000000
3.0
1.414213562373095110 ** 6 is a million, which is tidier than counting zeros by hand. A fractional power is a root:
raising to the power of 0.5 is the square root, so 9 ** 0.5 is three. It comes back as 3.0 rather
than 3 because a float went into the calculation, and once a float is involved the answer stays a
float. The square root of two on the last line is an endless decimal, and 1.4142135623730951 is as
close as Python can store — the first sight of a limit two sections below.
int and float: Two Kinds of Number
Python keeps whole numbers and decimal numbers as two separate types. An int is a whole number with
no decimal point: 7, 0, -40. A float is a number that has one: 7.0, 2.5, -0.001. The
built-in type() function tells you which one you are holding:
whole = 7
decimal = 7.0
print(whole, decimal)
print(type(whole))
print(type(decimal))
print(whole == decimal)
print(3 + 0.5)
print(type(4 * 2.0))7 7.0
<class 'int'>
<class 'float'>
True
3.5
<class 'float'>Two results deserve attention. whole == decimal is True: 7 and 7.0 are different types but the
same quantity, and Python compares them by value. Yet type(4 * 2.0) is a float, which shows the rule
when the types meet in one sum: mix an int with a float and the answer is a float. Python widens the
int rather than chopping the float, because widening loses nothing.
To move between the types deliberately, int() and float() build a new value of the type you name:
print(int(4.9))
print(int(-4.9))
print(round(4.9))
print(round(-4.9))
print(round(2.5))
print(round(3.5))
print(float(7))
print(int("25") + 5)4
-4
5
-5
2
4
7.0
30int() does not round. It truncates — it throws away everything after the decimal point and moves
toward zero, which is why 4.9 becomes 4 and -4.9 becomes -4. When you want rounding, round()
is the function for it, and it gives 5 and -5 instead. Reaching for int() when you meant round()
is a quiet way to be wrong by one.
round() has one habit of its own that surprises almost everybody. On an exact half it does not always
go upwards; it goes to the even neighbour, which is why round(2.5) is 2 while round(3.5) is 4.
That keeps a long column of rounded numbers from creeping upwards overall, and it is worth knowing about
before you meet it in a total that is a penny off.
The last line is the conversion you will use most often. Text that looks like a number is not a number,
and int("25") builds the int 25 from the text "25" so that arithmetic can happen — the bridge
between the outside world, which hands you text, and your sums.
Why 0.1 + 0.2 Is Not 0.3
This one deserves to be shown before it is explained:
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
print(0.1 + 0.2 - 0.3)0.30000000000000004
False
5.551115123125783e-17That is not a typo, a bug, or something wrong with your machine. It is how floats work, and those same three lines give the same strange answer in JavaScript, Java, C, Ruby and essentially every other mainstream language, because they nearly all store decimal numbers the same way.
The reason, in plain terms: a computer stores numbers in binary, as sums of halves, quarters, eighths
and so on, and some decimal fractions have no exact form in that system. One tenth is one of them, in
exactly the way one third has no exact form in ordinary decimal notation — writing 0.3333 and adding
threes forever never quite reaches a third. So Python stores the closest value to 0.1 that it can,
which is a hair off, and adding two hairs-off numbers can produce an error big enough to see. That last
line, 5.551115123125783e-17, is shorthand for a number with sixteen zeros after the decimal point
before the digits begin: a vanishingly small gap, but not zero, and == does not forgive gaps.
The trouble is that it only shows up sometimes, which is what makes it a trap:
print(0.1 + 0.2 + 0.3)
print(1.1 * 3)
print(2.675 * 100)0.6000000000000001
3.3000000000000003
267.5Two of those are visibly off and the third is exact. Since you cannot tell by looking which sums will
drift, the practical rule has to apply everywhere: never compare two floats with ==. Ask instead
whether they are close enough:
import math
total = 0.1 + 0.2
print(abs(total - 0.3) < 0.000001)
print(math.isclose(total, 0.3))
print(round(total, 2) == 0.3)True
True
TrueThe first line subtracts one number from the other, takes the size of the difference with abs() so the
sign cannot matter, and checks that it is tiny. The second does the same job properly with
math.isclose() from the standard library, which chooses a sensible tolerance for you. The third rounds
before comparing, which is often right when the numbers have a natural precision.
A second strategy is stronger, and it is the one to reach for with money: work in whole units. Store paise rather than rupees, or cents rather than dollars, keep the arithmetic in ints where nothing can drift, and split the result only for display:
price_paise = 1075
quantity = 3
total_paise = price_paise * quantity
print("Total in paise:", total_paise)
print("Rupees:", total_paise // 100)
print("Paise:", total_paise % 100)Total in paise: 3225
Rupees: 32
Paise: 25Notice // and % reappearing to do the same job as before: how many whole rupees, and what is left
over. This arithmetic is exact because ints are exact. Python also ships a decimal module built for
exact decimal arithmetic, which is what serious financial code uses — worth knowing the name now and
reading about when you need it.
The Order Python Works In
When several operators appear in one expression, Python does not simply work left to right. It follows precedence rules, the same ones you were taught for paper arithmetic: powers first, then multiplication and division, then addition and subtraction, with brackets overriding everything.
print(2 + 3 * 4)
print((2 + 3) * 4)
print(10 - 2 - 3)
print(2 ** 3 ** 2)
print((2 ** 3) ** 2)14
20
5
512
64The multiplication on the first line happens before the addition, giving 14 rather than 20. Operators
of equal standing do run left to right, so 10 - 2 - 3 is 8 minus 3. Powers are the exception: they
group right to left, so 2 ** 3 ** 2 is two raised to the ninth, not eight squared. That is the
mathematical convention, and also a good argument for brackets — nobody should have to remember that
rule to read your code.
Precedence mistakes are dangerous precisely because nothing goes wrong. The program runs, prints a number, and the number is simply not the one you wanted:
maths = 78
science = 85
english = 91
print((maths + science + english) / 3)
print(maths + science + english / 3)84.66666666666667
193.33333333333334The second line divides only the English mark by three, then adds it to the other two. No error, no warning, just an average of 193 for three subjects that are all under 100. The brackets on the first line are not decoration; they are the entire calculation.
Unary minus carries a trap of its own:
print(-3 ** 2)
print((-3) ** 2)
print(-(3 ** 2))-9
9
-9** binds more tightly than the minus sign, so -3 ** 2 squares the three and then negates the result.
If you meant minus three, squared, the brackets are compulsory. The habit worth forming is to add
brackets whenever an expression would make a reader pause, even where Python does not require them:
(base * rate) + fee and base * rate + fee give the same answer, and only the first cannot be
misread.
Negative Numbers Divide Downwards
// is often described as "divide and throw away the decimal part", and for positive numbers that
description works fine. For negative numbers it is wrong, and if you have used another language you may
have learned the wrong rule without knowing it:
print(7 // 2)
print(-7 // 2)
print(7 % 2)
print(-7 % 2)
print(-7 / 2)3
-4
1
1
-3.5Minus seven divided by two is -3.5. Throwing away the decimal part would give -3, but Python gives
-4. That is what floor means in "floor division": round down toward the smaller number, not
toward zero. Down from -3.5 is -4.
The remainder follows from that choice, because Python keeps the promise that multiplying back up and adding the remainder returns the original number:
print((-7 // 2) * 2 + (-7 % 2))
print(-13 % 5)
print(13 % -5)
print(int(-7 / 2))-7
2
-2
-3-4 times 2 is -8, and a remainder of 1 brings it back to -7. For that identity to hold the
remainder had to come out positive, which is why -7 % 2 is 1 and not -1. In general the remainder
takes the sign of the right-hand number: -13 % 5 is positive because 5 is, and 13 % -5 is negative
because -5 is. That is also the qualifier on the earlier rule about leftovers being small. With a
negative number on the right the remainder is negative too, so what always holds is that its size
stays under the size of the number you divided by.
The final line shows the alternative when you genuinely want truncation toward zero: divide with / and
convert with int(), which gives -3. Both behaviours are available; they answer different questions.
In everyday counting your numbers are positive and the distinction never arises — but when it does,
guessing will cost you an hour.
Updating a Number You Already Have
Adding to a running total is common enough that Python has shorthand for it. total += 5 means exactly
total = total + 5: work the sum out from the current value, then rebind the name to the result. The
same shorthand exists for the other operators, and the whole family is called augmented assignment.
score = 10
print(score)
score += 5
print(score)
score -= 3
print(score)
score *= 2
print(score)
score /= 4
print(score)10
15
12
24
6.0Follow the value down: 10, add 5 to get 15, subtract 3 to get 12, double it to 24, then divide by 4.
That last result is 6.0 rather than 6, because /= is ordinary division underneath and division
always produces a float. //= exists too, and would have kept it an int.
A Worked Example
Here is a small complete program that turns a count of study sessions into a readable summary, using
multiplication for the total, // and % to split minutes into hours, plain / for an average, and an
f-string to keep the decimal under control.
minutes_in_hour = 60
minutes_per_session = 25
sessions = 20
total_minutes = minutes_per_session * sessions
hours = total_minutes // minutes_in_hour
spare_minutes = total_minutes % minutes_in_hour
rebuilt = hours * minutes_in_hour + spare_minutes
average_per_day = total_minutes / 7
print("==============================")
print(" STUDY SUMMARY")
print("==============================")
print("Sessions .........", sessions)
print("Minutes each .....", minutes_per_session)
print("Total minutes ....", total_minutes)
print("------------------------------")
print("Whole hours ......", hours)
print("Spare minutes ....", spare_minutes)
print("Checks back to ...", rebuilt)
print(f"Average per day ... {average_per_day:.1f} minutes")
print("==============================")==============================
STUDY SUMMARY
==============================
Sessions ......... 20
Minutes each ..... 25
Total minutes .... 500
------------------------------
Whole hours ...... 8
Spare minutes .... 20
Checks back to ... 500
Average per day ... 71.4 minutes
==============================The first three lines name the facts the program is built on, so changing the number of sessions means
editing one line rather than hunting through the calculations. minutes_in_hour is a fixed 60, written
as a name because total_minutes // 60 would tell a reader nothing about why 60.
Then the arithmetic. minutes_per_session * sessions gives 500 total minutes. 500 // 60 is 8, the
whole hours, because eight sixties fit into 500 with room to spare. 500 % 60 is 20, the minutes that
did not make up a ninth hour. rebuilt is the check from earlier: 8 hours of 60 minutes is 480, plus
the 20 spare, is 500 again. average_per_day uses plain / because a daily average genuinely can be
fractional.
The printing block is presentation only. The rows of = and - are text inside quotes doing the job of
ruled lines on a paper form, the dots pad each label so the numbers line up, and the commas inside
print() place a single space between label and value. The last calculated line is the only f-string:
500 / 7 is 71.42857142857143, which would be noise in a summary, so :.1f displays it as 71.4.
The stored value is untouched — keep full precision in the number, and choose the precision at the point
a human reads it.
Common Mistakes
Using / when you wanted whole groups. This is the most common arithmetic slip, and the give-away
is a decimal point turning up where a count belongs:
eggs = 100
per_carton = 12
print(eggs / per_carton)
print(eggs // per_carton)8.333333333333334
8The first answer is a correct piece of division and a useless answer to the question that was asked, because a carton is either full or it is not. A decimal point sitting where a plain count belongs is nearly always a missing second slash.
Comparing floats with ==. Whole-number arithmetic is exact, so == is safe there. Float
arithmetic is not:
print(1 + 2 == 3)
print(0.1 + 0.2 == 0.3)
print(1 / 3 * 3 == 1)True
False
TrueThe middle line is false and the last is true, and there is no pattern you can rely on. Use
math.isclose(), round both sides before comparing, or keep the values in whole units.
Reading % as "percent". The operator has nothing to do with percentages, and using it that way
produces a number plausible enough to slip through:
correct = 18
questions = 24
print(correct % questions)
print(correct / questions * 100)18
75.018 % 24 is 18, because 24 does not fit into 18 even once, so the whole 18 is left over. A real
percentage needs a division and a multiplication, and comes out at 75.
Dividing by zero. Every division operator refuses, because there is no answer to give: Python raises
a ZeroDivisionError and stops the program unless you catch it.
students = 0
try:
print(120 / students)
except ZeroDivisionError as error:
print("ZeroDivisionError:", error)
print("The program carried on")ZeroDivisionError: division by zero
The program carried onThis turns up whenever you average something that might be empty. Check for zero before dividing, or catch the error as above.
Expecting a tidy number on screen. Division prints everything it has, which is often more than you meant to show:
bill = 100
people = 3
print(bill / people)
print(round(bill / people, 2))
print(f"Each pays {bill / people:.2f}")33.333333333333336
33.33
Each pays 33.33The first line is the honest full answer. round(value, 2) produces a genuinely shorter number, while
the f-string specification :.2f formats for display and leaves the underlying value alone. Prefer the
f-string when the goal is presentation, and keep the full-precision value for further arithmetic.
Next Steps
Try the Party Planner exercise if you have not already — it hands you a guest list, an appetite and
a pizza that arrives cut into eight, then leaves the two // and % lines for you to write.
Of the linked practice problems, two-number-sum puts plain addition to work while you keep track of
which values are ints. binary-search is where floor division earns its keep: finding the middle of a
range is (low + high) // 2, and it has to be // rather than / because a list position must be a
whole number — a float index is an error, not a rounded guess.
Before either, spend a few minutes in the Python playground. Print 10 / 2 and
10 // 2 side by side until the difference is boring rather than surprising. Try % with a few totals
and group sizes and confirm for yourself that the leftover is never as large as the group. Then type
0.1 + 0.2 once more, so that when it appears in your own output six months from now you recognise it
immediately instead of losing an afternoon to it.
Write it yourself
Reading about code and writing it are different skills. These exercises practise exactly what this lesson covered; they run in this tab and need no account.
When you want a harder one
Interview-style problems graded against hidden tests — a big step up from the exercises. Come back to these when the ideas in this lesson feel comfortable rather than new.