Numbers: integers, floats, and the floating point trap
Python has two number types you will use constantly, and one piece of behaviour that looks like a bug in Python but is actually a fact about computers. Meeting it deliberately here is much better than meeting it in a program that handles money.
Two types
count = 7 # int — a whole number
price = 19.99 # float — a number with a decimal point
An int is a whole number: 7, 0, -340. A float is a number with a
fractional part: 19.99, 0.5, -2.0. Note that 2.0 is a float even though
its value is whole — the decimal point is what decides it.
Ask Python which is which:
print(type(7))
print(type(19.99))
<class 'int'>
<class 'float'>
type() is genuinely useful when something is not behaving. Before guessing why
a calculation is wrong, check that the values are the types you assumed.
One pleasant thing about Python: integers have no size limit.
print(2 ** 1000)
That prints a 302-digit number, exactly. In Java or C that overflows and gives a wrong answer silently. Python just grows the number.
Arithmetic
print(10 + 3) # 13
print(10 - 3) # 7
print(10 * 3) # 30
print(10 / 3) # 3.3333333333333335
print(10 // 3) # 3
print(10 % 3) # 1
print(10 ** 3) # 1000
Three of those deserve attention.
/ always gives a float. Even when it divides evenly:
print(10 / 2)
5.0
Not 5. If you need a whole number, say so.
// is floor division — divide and throw away the fraction:
print(10 // 3) # 3
print(-10 // 3) # -4
"Floor" means it rounds down, towards negative infinity, not towards zero. That second result surprises people. It is consistent, not arbitrary.
% is the remainder, and it is more useful than it first appears:
print(17 % 5) # 2
print(10 % 2) # 0
print(7 % 2) # 1
n % 2 == 0 is how you test whether a number is even. n % 15 == 0 is how you
test divisibility by fifteen. You will reach for % constantly once you have
loops.
Together, // and % split a number cleanly:
total_minutes = 137
hours = total_minutes // 60
minutes = total_minutes % 60
print(hours, "hours", minutes, "minutes")
2 hours 17 minutes
Order of operations
Python follows normal mathematical precedence: ** first, then * / //
%, then + -.
print(2 + 3 * 4) # 14, not 20
print((2 + 3) * 4) # 20
Use brackets even when they are not strictly needed. (a * b) + c costs you two
characters and removes any doubt for whoever reads it next.
The floating point trap
Try this:
print(0.1 + 0.2)
0.30000000000000004
That is not a Python bug. It happens in JavaScript, Java, C and every other language using standard floating point, and it is worth understanding rather than memorising.
Computers store numbers in binary. Some fractions that are tidy in base ten are
infinitely repeating in base two — 0.1 is one of them, in the same way that
one third is 0.3333... forever in base ten. The computer stores the closest
value it can fit, which is very slightly off. Add two slightly-off numbers and
the error becomes visible.
The consequence that will actually bite you:
print(0.1 + 0.2 == 0.3)
False
Never compare floats with ==. Compare the difference against a small
tolerance instead:
result = 0.1 + 0.2
print(abs(result - 0.3) < 0.000001)
True
When it is money, do not use floats
This is the rule that matters commercially. Never store currency as a float. Rounding errors accumulate, and an accounting system whose totals drift by paise is a serious problem.
Two correct approaches:
Work in the smallest unit as integers. Store paise, not rupees. ₹19.99
becomes 1999. All arithmetic is exact because integers are exact, and you
divide by 100 only when displaying.
Or use Decimal:
from decimal import Decimal
price = Decimal("19.99")
tax = Decimal("0.18")
print(price + (price * tax))
23.5882
Note the quotes: Decimal("19.99"), not Decimal(19.99). Passing a float hands
it a value that is already slightly wrong, and it faithfully preserves the error.
Decimal is slower and wordier, which is why it is not the default. For money
it is worth it.
Rounding
print(round(3.7)) # 4
print(round(3.2)) # 3
print(round(2.675, 2)) # 2.67
That last one is the REPL example from module 1. Two things are happening.
The obvious one is floating point again: 2.675 is stored as very slightly less
than 2.675, so rounding it down is correct given what is actually stored.
The less obvious one is that Python uses banker's rounding — exact halves go to the nearest even number:
print(round(0.5)) # 0
print(round(1.5)) # 2
print(round(2.5)) # 2
This is deliberate. Always rounding halves upward introduces a small upward bias across many values; alternating removes it. It is the correct default for statistics and surprising everywhere else.
Two other useful functions:
print(abs(-7)) # 7 — distance from zero
print(divmod(17, 5)) # (3, 2) — floor division and remainder together
Practice
- Print the result of
7 / 2,7 // 2and7 % 2. Say in your own words what each one does before running it. - Write code that turns
total_seconds = 9045into "2 hours, 30 minutes, 45 seconds" using only//and%. - Verify for yourself that
0.1 + 0.2 != 0.3, then write a comparison using a tolerance that correctly reportsTrue. - Calculate 18% tax on ₹1,250 twice — once with floats, once with
Decimal. Print both. Are they the same? - Write a check for whether a year is a leap year using only
%. The rule: divisible by 4, except years divisible by 100, unless also divisible by 400. Test it with 2024 (yes), 1900 (no) and 2000 (yes).
Question 5 is harder than it looks and is worth the struggle. If you get stuck, write the three conditions out in English first, then translate them one at a time.
Next: strings, and the single most useful piece of syntax in modern Python.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship