PythonMastery
beginner 14 min read · lesson 3 of 19 in Python Fundamentals

Numbers in Depth: Integers, Floats, and the math Module

1 · The lesson

read

You 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

python
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.

python
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):

python
million = 1_000_000             # easier to scan than 1000000
print(million == 1000000)       # True

3. The Floating-Point Trap

0.1 + 0.2 does not equal 0.3 in any language that uses binary floating-point.

python
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.

python
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.

python
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

python
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):

python
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:

python
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

python
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:

python
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 ==

python
# 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

python
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

python
print(10 / 3)                   # 3.3333... — a float
print(10 // 3)                  # 3         — floor division for integer result


Mini-Program — Random Password Strength Meter

python
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.

python
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:

python
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
python
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 is
33.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 ==, use math.isclose().
  • abs, round, divmod, pow, min, max, sum are built in.
  • The math module has square roots, logarithms, trigonometry, constants.
  • Format numbers with f"{n:.2f}", f"{n:,}", f"{n:.1%}".
  • random gives 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.in

Short exercises that run in your browser and tell you what your code actually did, not just whether a test passed.