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, the floating-point rounding problem that every developer meets, formatting numbers for display, and the most useful pieces of the math and random modules.
1. The Three Numeric Types
a = 42 # int: a whole number of any size b = 3.14 # float: a decimal number, about 15-17 significant digits c = 2 + 3j # complex: a real part plus an imaginary part print(type(a), type(b), type(c))
You'll use int and float constantly. Complex numbers are used in engineering and scientific computing. You can ignore them until you need them.
2. Integers Have No Maximum Size
In many other languages, such as C and Java, the usual integer type has a fixed size and cannot hold 2**63 or larger (it overflows). Python integers can grow as large as your computer's memory 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! (50 factorial) has 65 digits, and Python handles it easily
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 is not a bug in Python. It comes from the way computers store decimal numbers in binary. 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 (exactly 0.3)
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 to import anything to use these. They are built into the language.
print(abs(-7.5)) # 7.5 (the absolute value: distance from zero) print(round(3.14159, 2)) # 3.14 (rounded to 2 decimal places) print(round(2.5)) # 2 because Python rounds a .5 to the nearest even number print(pow(2, 10)) # 1024 (the same as 2 ** 10) print(divmod(17, 5)) # (3, 2) (the quotient and the remainder) print(min(4, 9, 2, 7)) # 2 print(max(4, 9, 2, 7)) # 9 print(sum([1, 2, 3, 4])) # 10
divmod() returns the quotient and the remainder together as a tuple. This is useful for tasks like converting seconds to minutes and seconds.
5. The math Module: the Parts You Will Use Most
import math print(math.pi) # 3.141592653589793 print(math.e) # 2.718281828459045 print(math.sqrt(2)) # 1.4142135623730951 print(math.floor(3.9)) # 3 (rounds down to a whole number) print(math.ceil(3.1)) # 4 (rounds up to a whole number) print(math.gcd(12, 18)) # 6 (the 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 degrees is π 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 are the most common format specs:
n = 1234567.891 print(f"{n:.2f}") # 1234567.89 (2 decimal places) print(f"{n:,.2f}") # 1,234,567.89 (with a thousands separator) print(f"{n:.2e}") # 1.23e+06 (scientific notation) print(f"{n:>15.2f}") # " 1234567.89" (right-aligned in a width of 15) print(f"{n:<15.2f}") # "1234567.89 " (left-aligned) # 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)) # two different items random.shuffle(colors) # shuffles the list in place print(colors)
If you need the same "random" numbers on every run (for example, when testing), set a seed:
import random random.seed(42) # the same seed gives the same numbers on 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 gives False (it looks like a bug, but it is not) import math math.isclose(0.1 + 0.2, 0.3) # True (this is the right way to compare floats)
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... (always a float) print(10 // 3) # 3 (floor division gives a whole number)
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 characters, not counting 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")
This program uses the math module and a very large integer to do a real calculation.
🎯 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: the base share for everyone. Use // to 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 the bill exactly)
Hint 1: Use only integers, from start to end
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 key is to never use floats. 100 / 3 in floating point is33.333333333333336, which is 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.
What You Learned
- 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, random choices, shuffles and samples. Set a seed when you need the same results in tests.
Next: Operators & Expressions.
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 did, as well as whether a test passed.
Found this useful? Share it with a friend who’s learning Python.