Level 1 · First Words

Numbers and Maths 🔢

Python is a very good calculator that happens to also be a programming language. Ten minutes here saves you an entire category of confusing bug.

Two kinds of number

crew = 12          # int: a whole number
speed = 9.5        # float: a number with a decimal point

print(type(crew))
print(type(speed))
<class 'int'>
<class 'float'>

int is short for integer, meaning whole. float is short for floating point, which refers to the decimal point being able to move around. That is the whole distinction, and it matters more than it looks.

♾️ Python integers do not overflow

In most languages an integer has a maximum size and quietly wraps around when you exceed it, which has caused real satellites to fail. Python integers grow to fit memory. 2 ** 1000 is a perfectly ordinary thing to ask for, and Python will hand you all 302 digits.

print(2 ** 100)
1267650600228229401496703205376

The operators

You writeIt meansExampleResult
+add7 + 310
-subtract7 - 34
*multiply7 * 321
/divide (always a float)7 / 23.5
//divide and throw away the remainder7 // 23
%the remainder only7 % 21
**to the power of7 ** 249
print(7 / 2)
print(7 // 2)
print(7 % 2)
print(7 ** 2)
print(10 / 5)      # still a float, even though it divides exactly
3.5
3
1
49
2.0

The two division signs are worth your attention

// and % look like curiosities. They are two of the most useful operators in programming.

total_minutes = 137

hours = total_minutes // 60      # how many whole hours fit
minutes = total_minutes % 60     # what is left over

print(f"{total_minutes} minutes is {hours}h {minutes}m")
137 minutes is 2h 17m

That pattern converts seconds to clocks, pennies to pounds, and items to pages.

% also answers "is this divisible by":

for number in [10, 15, 21, 30]:
    if number % 5 == 0:
        print(f"{number} divides by 5 exactly")
    else:
        print(f"{number} does not")
10 divides by 5 exactly
15 divides by 5 exactly
21 does not
30 divides by 5 exactly

"Remainder is zero" is how you test for even numbers (n % 2 == 0), how you make something happen every tenth time round a loop, and how you write FizzBuzz, the most famous interview question there is.

Order of operations

print(2 + 3 * 4)        # multiplication happens first
print((2 + 3) * 4)      # brackets win
print(2 ** 3 ** 2)      # powers go right to left: 2 ** 9
14
20
512

The rules are the ones you learned at school. The advice is simpler than the rules: use brackets whenever there is the slightest doubt. They cost nothing and they are free documentation.

The 0.1 + 0.2 scandal

print(0.1 + 0.2)
0.30000000000000004
HALF LIGHT[Legendary: Failure]

The machine is broken. The foundations are rotten. Nothing can be trusted.

Sit down. Nothing is broken. This is arithmetic working exactly as specified, and every language on this planet does it: JavaScript, Java, C, Rust, your pocket calculator if you push it hard enough.

Computers store floats in binary. In binary, one tenth is a recurring number, exactly as one third is recurring in decimal (0.3333...). You have to stop writing digits somewhere, so the stored value is very slightly off, and the errors add up. The standard that defines this behaviour is IEEE 754, published in 1985 and implemented by essentially every CPU on earth.

What to actually do about it:

# 1. Never compare floats with ==
print(0.1 + 0.2 == 0.3)

# 2. Compare with a tolerance instead
import math
print(math.isclose(0.1 + 0.2, 0.3))

# 3. Round for display
print(round(0.1 + 0.2, 2))

# 4. For money, use exact decimal arithmetic
from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2"))
False
True
0.3
0.3

Rule of thumb: floats for measurements, Decimal for money, ints for counting. Financial software that uses floats for currency is a bug with a launch party.

Converting between types

print(int("42") + 1)        # text to whole number
print(float("3.5") + 1)     # text to decimal
print(str(42) + " crabs")   # number to text
print(int(9.99))            # float to int: chops, does not round
print(round(9.99))          # this is what you usually wanted
43
4.5
42 crabs
9
10
🪤 int() chops, it does not round

int(9.99) is 9. It removes everything after the point rather than rounding to the nearest. If you want nearest, say round(). This mistake has caused real money to go missing in real systems.

The maths module

import math

print(math.sqrt(144))
print(math.pi)
print(math.floor(9.99), math.ceil(9.01))
print(math.factorial(5))
12.0
3.141592653589793
9 10
120

import loads a module: a file of code somebody else already wrote and tested. math ships with Python. Lesson 20 covers imports properly, but you can use them now with no ceremony.

Exercise 1

Split the bill

A meal costs 87.50 and is shared by 4 people, with a 15% tip. Print the total including tip, and what each person pays, both to two decimal places.

Reveal solution
bill = 87.50
people = 4
tip_rate = 0.15

tip = bill * tip_rate
total = bill + tip
each = total / people

print(f"Bill:  {bill:.2f}")
print(f"Tip:   {tip:.2f}")
print(f"Total: {total:.2f}")
print(f"Each:  {each:.2f}")
Bill:  87.50
Tip:   13.12
Total: 100.62
Each:  25.16

Look closely at the tip. The true answer is 13.125, and Python printed 13.12, not 13.13. That is not a bug, it is two of this lesson's ideas colliding: round and :.2f use round-half-to-even (so exact halves go to the even digit, which spreads rounding error out instead of always pushing it up), and floats cannot hold most decimals exactly anyway. For a tip, nobody cares. For a payroll system, this is why you use Decimal.

Exercise 2

Seconds to a clock

Turn 9,384 seconds into hours, minutes and seconds. Use // and % and nothing else clever.

Reveal solution
total = 9384

hours = total // 3600
remainder = total % 3600
minutes = remainder // 60
seconds = remainder % 60

print(f"{hours}h {minutes}m {seconds}s")
print(f"{hours:02d}:{minutes:02d}:{seconds:02d}")
2h 36m 24s
02:36:24

The :02d means 'a whole number, at least two digits, padded with a zero'. That is how you make a clock look like a clock.

Exercise 3

Predict, then run

Work out each answer on paper first.

print(17 // 5)
print(-17 // 5)
print(17 % 5)
print(-17 % 5)
Reveal solution

3, then -4, then 2, then 3.

The negative ones surprise nearly everybody. Python's // rounds down (towards negative infinity), not towards zero, so -17 // 5 is -4 rather than -3. And % always takes the sign of the right-hand side, so -17 % 5 is a positive 3.

This is genuinely different from C, Java and Rust, which round towards zero. Python's choice is more mathematically consistent and occasionally more surprising. Now you know, which puts you ahead of a lot of working programmers.

+100 XP