Numbers in Depth: Integers, Floats, and the math Module
1 · The lesson
readYou met int and float in the Variables lesson. This lesson goes deeper: the three numeric types, integer division behaviour, the floating-point gotcha every developer hits, formatting numbers for display, and the most useful pieces of the math and random modules.
1. The Three Numeric Types
a = 42 # int — whole number, any size b = 3.14 # float — decimal, ~15-17 significant digits c = 2 + 3j # complex — real + imaginary parts print(type(a), type(b), type(c))
You'll use int and float constantly. Complex numbers exist for engineering and scientific computing — you can ignore them until you need them.
2. Integers Have No Maximum Size
This is one of Python's quiet superpowers. Other languages overflow at 2**63. Python integers grow as large as your RAM allows.
big = 2 ** 100 print(big) # 1267650600228229401496703205376 # Even bigger factorial_50 = 1 for i in range(1, 51): factorial_50 *= i print(factorial_50) # 50! — a 65-digit number, no problem
Underscores make long literals readable (the underscores are ignored):
million = 1_000_000 # easier to scan than 1000000 print(million == 1000000) # True
3. The Floating-Point Trap
0.1 + 0.2does not equal0.3in any language that uses binary floating-point.
print(0.1 + 0.2) # 0.30000000000000004 print(0.1 + 0.2 == 0.3) # False — this is correct behaviour, not a bug
This isn't Python's fault — it's how binary computers represent decimals. The number 0.1 can't be stored exactly in binary, the same way 1/3 can't be stored exactly in base 10.
The fix: use math.isclose() to compare floats safely.
import math print(math.isclose(0.1 + 0.2, 0.3)) # True # For money or anything needing exact decimals, use the `decimal` module: from decimal import Decimal result = Decimal("0.1") + Decimal("0.2") print(result) # 0.3 — exact
Rule of thumb: never compare floats with ==. Always use math.isclose() or work in fixed precision with Decimal.
4. Built-in Number Functions
You don't need imports for these — they come with the language.
print(abs(-7.5)) # 7.5 — absolute value print(round(3.14159, 2)) # 3.14 — round to N decimal places print(round(2.5)) # 2 — banker's rounding (rounds to even) print(pow(2, 10)) # 1024 — same as 2 ** 10 print(divmod(17, 5)) # (3, 2) — (quotient, remainder) print(min(4, 9, 2, 7)) # 2 print(max(4, 9, 2, 7)) # 9 print(sum([1, 2, 3, 4])) # 10
The divmod() returning a tuple is a nice combo for things like converting seconds to minutes-and-seconds.
5. The math Module — Things You'll Actually Use
import math print(math.pi) # 3.141592653589793 print(math.e) # 2.718281828459045 print(math.sqrt(2)) # 1.4142135623730951 print(math.floor(3.9)) # 3 — round down to integer print(math.ceil(3.1)) # 4 — round up to integer print(math.gcd(12, 18)) # 6 — greatest common divisor # Logarithms print(math.log(100, 10)) # 2.0 — log base 10 print(math.log2(8)) # 3.0 — log base 2 # Degrees / radians for trig (sin, cos, tan all expect radians) print(math.radians(180)) # 3.14... — 180° = π radians print(math.sin(math.pi / 2)) # 1.0
A quick "is this finite?" check (useful when dividing by user input):
import math result = 1 / 0.0001 print(math.isfinite(result)) # True print(math.isinf(float("inf"))) # True print(math.isnan(float("nan"))) # True
6. Formatting Numbers for Display
You've seen f"{x:.2f}" in the Input/Output lesson. Here's the full toolkit:
n = 1234567.891 print(f"{n:.2f}") # 1234567.89 — 2 decimal places print(f"{n:,.2f}") # 1,234,567.89 — thousands separator print(f"{n:.2e}") # 1.23e+06 — scientific notation print(f"{n:>15.2f}") # " 1234567.89" — right-align width 15 print(f"{n:<15.2f}") # "1234567.89 " — left-align # Percent print(f"{0.847:.1%}") # 84.7% # Integer with leading zeros print(f"{7:04d}") # 0007 — useful for IDs
These format specs work the same way in format() calls outside f-strings.
7. Random Numbers
import random # Integers print(random.randint(1, 6)) # a six-sided die roll: 1-6 inclusive print(random.randrange(0, 100)) # 0-99 # Floats print(random.random()) # 0.0 ≤ x < 1.0 print(random.uniform(0, 10)) # 0.0 ≤ x < 10.0 # Pick from a collection colors = ["red", "green", "blue", "yellow"] print(random.choice(colors)) # one item print(random.sample(colors, 2)) # k unique items random.shuffle(colors) # in-place shuffle print(colors)
For reproducible "randomness" (essential when testing), seed it:
import random random.seed(42) # same seed → same sequence every run print(random.randint(1, 100)) # 82 print(random.randint(1, 100)) # 15
8. Mistakes You'll Hit
1. Comparing floats with ==
# 0.1 + 0.2 == 0.3 → False (looks like a bug, isn't) import math math.isclose(0.1 + 0.2, 0.3) # True — the right way
2. Forgetting that round() uses banker's rounding
print(round(0.5)) # 0 — not 1! print(round(2.5)) # 2 — not 3!
For traditional "0.5 rounds up", use
math.floor(x + 0.5) or int(x + 0.5) for positive numbers.
3. Dividing integers and expecting an integer
print(10 / 3) # 3.3333... — a float print(10 // 3) # 3 — floor division for integer result
Mini-Program — Random Password Strength Meter
import math, random # Make up a "password" length for this demo password_length = 12 charset_size = 94 # printable ASCII minus space # Entropy in bits: log2(charset_size^length) entropy = password_length * math.log2(charset_size) print(f"Estimated entropy: {entropy:.1f} bits") # Time to crack at 1 billion guesses per second seconds = (charset_size ** password_length) / 2 / 1_000_000_000 years = seconds / (60 * 60 * 24 * 365) print(f"Average crack time: {years:.2e} years")
That's math and int-with-big-numbers doing real work.
🎯 Your Turn — Split a Bill Without Losing a Paisa
Three friends split a ₹100 bill. 100 / 3 is 33.333..., and if you round each
share to 33.33 the total comes to 99.99 — you have lost a paisa. Banks and
billing systems never lose that paisa.
Write split_bill(total_paise, people) that divides a whole number of paise
into people shares that add up to exactly the original amount. Give the
leftover paise to the earliest shares, one each.
split_bill(10000, 3) → [3334, 3333, 3333] # sums to 10000 split_bill(10000, 4) → [2500, 2500, 2500, 2500] split_bill(7, 2) → [4, 3] split_bill(5, 5) → [1, 1, 1, 1, 1]
Skeleton:
def split_bill(total_paise, people): # TODO 1: base share for everyone — use // so you stay in whole paise # TODO 2: how many paise are left over? use % # TODO 3: build the list, adding 1 paisa to the first `leftover` shares ... shares = split_bill(10000, 3) print(shares) # [3334, 3333, 3333] print(sum(shares)) # 10000 — must match exactly
Hint 1 — Stay in integers the whole way
Money in paise is a whole number, so never let a float in.// gives
you the floor of the division and % gives you the remainder:
10000 // 3 is 3333 and 10000 % 3 is
1. That 1 is the paisa you must not lose.
Hint 2 — The leftover is always smaller than people
total % people can never be larger than people - 1, so
you are always handing out at most one extra paisa per share. Build the list
with a loop or a comprehension and ask, for each index, whether it is below the
leftover count.
Show full solution
def split_bill(total_paise, people): base = total_paise // people leftover = total_paise % people return [base + (1 if i < leftover else 0) for i in range(people)] # Sanity checks print(split_bill(10000, 3)) # [3334, 3333, 3333] print(sum(split_bill(10000, 3))) # 10000 print(split_bill(7, 2)) # [4, 3] print(split_bill(5, 5)) # [1, 1, 1, 1, 1]
The whole trick is refusing to use floats. 100 / 3 in floating point is33.333333333333336 — already wrong in the last digit before you round it. By
working in paise with // and %, every value stays an exact integer and the
sum is guaranteed to match. This is why real payment systems store amounts as
integer minor units, not as float rupees.
Recap
- Three numeric types:
int(unlimited size),float(~15-17 digit precision),complex(rare). - Floats are inexact. Never compare with
==, usemath.isclose(). abs,round,divmod,pow,min,max,sumare built in.- The
mathmodule has square roots, logarithms, trigonometry, constants. - Format numbers with
f"{n:.2f}",f"{n:,}",f"{n:.1%}". randomgives you dice rolls, picks, shuffles, samples — seed it for reproducible tests.
Next up: writing readable code with comments and PEP 8 style.
Source: adapted from Python official documentation Section 3.1.1 (Numbers) and the math module reference. PSF License.
Practice this
on practicepython.inShort exercises that run in your browser and tell you what your code actually did, not just whether a test passed.