Numbers: math, random, statistics, decimal and fractions
Five standard library modules cover every numeric need core Python has: exact maths, randomness, summary statistics, exact decimals and exact fractions.
- What the standard library is
- math
- Rounding
- Powers, roots and logarithms
- Number theory
- Checks and comparisons
- Trigonometry and distance
- random
- Choosing from a sequence
- Reproducible randomness
- Never use random for security
- statistics
- decimal
- fractions
- A worked comparison
- Common mistakes
- Best practices
- Practice
- Conclusion
- Basics
- Data Types
- Operators
- Strings
- Control Flow
- Lists
- Tuples
- Sets
- Dictionaries
- Comprehensions
- Functions
- Advanced Functions
- Recursion
- Exception Handling
- File Handling
- Modules
- Standard Library
- OOP
- Advanced OOP
- Iterators and Generators
- Decorators
- Context Managers
- Descriptors and Dataclasses
- Python Internals
- Concurrency
- Regular Expressions
- Serialization
- Command Line Python
- Testing and Debugging
- Type Hints
- Performance
- Python Security
- DSA with Python
What the standard library is
Python ships with a large collection of modules that are always available, with no installation step. This is the "batteries included" principle, and everything in this path stays inside it - there are no third party packages anywhere in these notes.
A standard library module still needs importing. It is not built in the way len and print are; it simply comes with Python.
math
import math
print(math.pi) # 3.141592653589793
print(math.e) # 2.718281828459045
print(math.inf, -math.inf)
print(math.tau) # 2 * piRounding
print(math.floor(3.7), math.floor(-3.2)) # 3 -4 always down
print(math.ceil(3.2), math.ceil(-3.7)) # 4 -3 always up
print(math.trunc(3.7), math.trunc(-3.7)) # 3 -3 towards zero
print(round(3.7), round(-3.7)) # 4 -4 to nearest, half to even| Function | 3.7 | -3.7 | Direction |
|---|---|---|---|
floor | 3 | -4 | Down |
ceil | 4 | -3 | Up |
trunc | 3 | -3 | Towards zero |
round | 4 | -4 | Nearest, ties to even |
Powers, roots and logarithms
print(math.sqrt(16)) # 4.0
print(math.isqrt(17)) # 4 - integer square root, exact
print(math.pow(2, 10)) # 1024.0 - always a float
print(2 ** 10) # 1024 - stays an int
print(math.log(math.e)) # 1.0 - natural log
print(math.log(1000, 10)) # 2.9999999999999996 - floating point
print(math.log10(1000)) # 3.0 - use the dedicated function
print(math.log2(1024)) # 10.0
print(math.exp(1)) # eNumber theory
print(math.gcd(48, 18)) # 6
print(math.lcm(4, 6)) # 12 (Python 3.9+)
print(math.factorial(5)) # 120
print(math.comb(5, 2)) # 10 - combinations
print(math.perm(5, 2)) # 20 - permutationsChecks and comparisons
print(math.isclose(0.1 + 0.2, 0.3)) # True
print(math.isclose(1000, 1001, rel_tol=0.01)) # True - within 1 percent
print(math.isnan(float("nan"))) # True
print(math.isfinite(math.inf)) # False
print(math.fsum([0.1] * 10)) # 1.0 exactly
print(sum([0.1] * 10)) # 0.9999999999999999math.fsum adds floats without accumulating rounding error. Use it when summing a long list of decimals.
Trigonometry and distance
print(math.degrees(math.pi)) # 180.0
print(math.radians(180)) # 3.141592653589793
print(round(math.sin(math.radians(30)), 4)) # 0.5
print(math.hypot(3, 4)) # 5.0
print(math.dist((0, 0), (3, 4))) # 5.0 - any number of dimensionsrandom
import random
print(random.random()) # a float in [0.0, 1.0)
print(random.uniform(1, 10)) # a float in [1, 10]
print(random.randint(1, 6)) # an int in [1, 6] - BOTH ends included
print(random.randrange(1, 7)) # an int in [1, 7) - like range
print(random.randrange(0, 100, 5)) # a multiple of 5 below 100randint(a, b)includesb;randrange(a, b)excludes it. This is the one inconsistency in the module and it is a reliable source of off by one errors.
Choosing from a sequence
colours = ["red", "green", "blue", "yellow"]
print(random.choice(colours)) # one item
print(random.choices(colours, k=3)) # 3 items, WITH replacement
print(random.sample(colours, k=3)) # 3 items, WITHOUT replacement
print(random.choices(colours, weights=[10, 1, 1, 1], k=5)) # red is likelier
deck = list(range(1, 11))
random.shuffle(deck) # shuffles IN PLACE, returns None
print(deck)Reproducible randomness
random.seed(42)
print([random.randint(1, 100) for _ in range(5)])
random.seed(42)
print([random.randint(1, 100) for _ in range(5)]) # exactly the sameSeeding makes a sequence repeatable, which is essential for tests and simulations you need to reproduce.
Never use random for security
import secrets
print(secrets.token_hex(16)) # a secure random token
print(secrets.token_urlsafe(16))
print(secrets.randbelow(100))
print(secrets.choice(["a", "b", "c"]))random uses a fast generator whose output is predictable if enough values are observed. For passwords, tokens, session ids or anything an attacker should not guess, use secrets.
statistics
import statistics
scores = [72, 85, 90, 85, 64, 78]
print(statistics.mean(scores)) # 79.0
print(statistics.median(scores)) # 81.5
print(statistics.mode(scores)) # 85 - the most common value
print(statistics.stdev(scores)) # sample standard deviation
print(statistics.pstdev(scores)) # population standard deviation
print(statistics.variance(scores))
print(statistics.quantiles(scores, n=4)) # the quartiles
print(statistics.fmean(scores)) # faster, always returns a float
print(statistics.median_low([1, 2, 3, 4])) # 2
print(statistics.median_high([1, 2, 3, 4])) # 3from statistics import multimode
print(multimode([1, 1, 2, 2, 3])) # [1, 2] - mode() would raise on a tie
# statistics.mean([]) # StatisticsError on empty inputdecimal
from decimal import Decimal, getcontext, ROUND_HALF_UP
print(0.1 + 0.2) # 0.30000000000000004
print(Decimal("0.1") + Decimal("0.2")) # 0.3
print(Decimal("0.1") + Decimal("0.2") == Decimal("0.3")) # Trueprint(Decimal(0.1)) # 0.1000000000000000055511151231257827... - the float's real value
print(Decimal("0.1")) # 0.1 - exactly what you wroteAlways construct a Decimal from a string. Passing a float hands it a value that was already inexact.
price = Decimal("19.99")
quantity = 3
tax_rate = Decimal("0.18")
subtotal = price * quantity
tax = (subtotal * tax_rate).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
total = subtotal + tax
print(f"{subtotal} {tax} {total}") # 59.97 10.79 70.76
getcontext().prec = 28 # significant digits, default 28
print(Decimal(1) / Decimal(7))| Use | For |
|---|---|
float | Measurements, science, graphics, anything approximate |
Decimal | Money, tax, invoices, anything an accountant will check |
int of the smallest unit | Money, when you would rather avoid Decimal entirely |
fractions
from fractions import Fraction
print(Fraction(1, 3)) # 1/3
print(Fraction(1, 3) + Fraction(1, 6)) # 1/2 - exact
print(0.1 + 0.2 == 0.3) # False
print(Fraction(1, 10) + Fraction(2, 10) == Fraction(3, 10)) # True
print(Fraction("0.25")) # 1/4
print(Fraction(6, 8)) # 3/4 - reduced automatically
print(float(Fraction(1, 3))) # 0.3333333333333333
print(Fraction(1, 3).numerator, Fraction(1, 3).denominator)Fractions are exact for any rational number, including thirds, which decimals cannot represent. Use them for exact ratios, probability and anything where 1/3 + 1/3 + 1/3 must equal exactly 1.
A worked comparison
from decimal import Decimal
from fractions import Fraction
third_float = 1 / 3
third_decimal = Decimal(1) / Decimal(3)
third_fraction = Fraction(1, 3)
print(third_float * 3) # 1.0 (by luck of rounding)
print(third_decimal * 3) # 0.9999999999999999999999999999
print(third_fraction * 3) # 1 exactly
total = sum([Decimal("0.01")] * 100)
print(total, total == Decimal("1.00")) # 1.00 TrueCommon mistakes
- Constructing
Decimalfrom a float instead of a string. - Confusing
randint(inclusive) withrandrange(exclusive). - Using
randomfor tokens or passwords. - Expecting
random.shuffleto return the shuffled list; it returnsNone. - Calling
statistics.modeon data with a tie, and meetingStatisticsError. - Using
math.powwhen**would have kept the result an integer. - Using
floatfor money.
Best practices
- Use
math.isclosefor every float comparison. - Use
Decimalfor currency, built from strings, and quantize before displaying. - Seed
randomin tests so failures reproduce. - Use
secretswhenever unpredictability matters. - Use
math.fsumwhen summing many floats.
Practice
- Compute compound interest with
floatand withDecimalover 120 months and compare the totals. - Simulate 10 000 dice rolls and report the distribution, seeded so it reproduces.
- Write a function returning the mean, median and standard deviation of a list, handling the empty case.
- Explain why
Decimal(0.1)andDecimal("0.1")differ, showing the output of each. - Add one tenth ten times with
float,DecimalandFraction, and compare each to 1.
Conclusion
math for exact integer maths and float helpers, random for simulations, secrets for anything security related, statistics for summaries, and Decimal or Fraction when the answer has to be exact. Money is never a float.