Python with AI Tutorial · Chapter 27 of 48
The Python math module gives you the functions a calculator has and a spreadsheet takes for granted: square roots, logarithms, ceiling and floor, factorials, constants such as pi and e, and precise summation. Combined with the built-in round() and the decimal module for money, it covers almost every number you will meet in business scripts.
Python already handles + - * / ** // % without any import, so the math module is for the next layer up: the operations Excel exposes as SQRT, LOG, CEILING, FLOOR, COMBIN and FACT. Everything below is standard library, so there is nothing to install.
Rounding with round()
round() is a built-in, not part of the math module, and it has a surprise: Python rounds half to even (“banker’s rounding”), so 2.5 becomes 2 and 3.5 becomes 4. The second argument sets the number of decimals, and a negative value rounds to tens, hundreds and so on.
print(round(2.5), round(3.5), round(-2.5))
print(round(1234.5678, 2))
print(round(1234.5678, -2))
print(round(2.675, 2)) # binary float surprise
Output: 2 4 -2 1234.57 1200.0 2.67
The last line is not a Python bug: 2.675 cannot be stored exactly in binary and is really 2.67499999…, so it rounds down. For prices and invoices, use Decimal as shown later in this chapter.
Floor, ceiling and truncation
math.floor always goes down, math.ceil always goes up and math.trunc chops toward zero. They differ only for negative numbers, and all three return an int. Ceiling is the one you reach for when working out how many boxes, pages or shifts are needed.
import math
units_ordered = 1250
units_per_carton = 500
print("Cartons needed:", math.ceil(units_ordered / units_per_carton))
print("Full cartons:", math.floor(units_ordered / units_per_carton))
print(math.floor(-2.5), math.ceil(-2.5), math.trunc(-2.5))
Output: Cartons needed: 3 Full cartons: 2 -3 -2 -2
A quick way to remember the three: floor is the number on your left on a number line, ceil is the number on your right, and trunc simply drops the decimals. For positive values floor and trunc agree, which is why the difference only shows up with refunds, losses and other negative figures.
Integer division, modulo and divmod
Two operators do most of the packing and time-splitting work: // gives the whole number of times one value fits in another and % gives what is left over. divmod() returns both at once.
print(1250 // 500, 1250 % 500)
hours, minutes = divmod(535, 60)
print(f"535 minutes = {hours} h {minutes} min")
total_seconds = 98765
h, rem = divmod(total_seconds, 3600)
m, s = divmod(rem, 60)
print(f"{h:02d}:{m:02d}:{s:02d}")
Output: 2 250 535 minutes = 8 h 55 min 27:26:05
Powers, roots and compound growth
Python’s ** operator handles powers natively; math.sqrt and math.pow are the module versions. Compound interest and growth projections are the classic business use.
import math
principal = 100_000
rate = 0.08
years = 5
future_value = principal * (1 + rate) ** years
print("Future value:", round(future_value, 2))
print("Square root of 144:", math.sqrt(144))
print("Cube root of 1000:", round(1000 ** (1/3), 6))
print("2 to the 10th:", 2 ** 10)
Output: Future value: 146932.81 Square root of 144: 12.0 Cube root of 1000: 10.0 2 to the 10th: 1024
Logarithms and exponentials
math.log(x) is the natural log; pass a second argument for another base, or use log10 and log2 directly. Logs answer “how many periods until” questions, such as how long money takes to double.
import math
print(math.log10(1000), math.log2(1024))
print(round(math.log(100, 10), 6))
rate = 0.08
years_to_double = math.log(2) / math.log(1 + rate)
print("Years to double at 8%:", round(years_to_double, 2))
print("e:", math.exp(1))
Output: 3.0 10.0 2.0 Years to double at 8%: 9.01 e: 2.718281828459045
Constants and a few geometry helpers
The module defines pi, e, tau (2 pi), inf and nan. Infinity is handy as a starting value when searching for a minimum; nan marks a missing number and is never equal to anything, including itself.
import math
radius = 5
print("Circle area:", round(math.pi * radius ** 2, 2))
print("90 degrees in radians:", round(math.radians(90), 4))
print("Hypotenuse 3-4:", math.hypot(3, 4))
lowest = math.inf
for price in [4500, 3999, 4250]:
lowest = min(lowest, price)
print("Lowest quote:", lowest)
print(math.nan == math.nan, math.isnan(math.nan))
Output: Circle area: 78.54 90 degrees in radians: 1.5708 Hypotenuse 3-4: 5.0 Lowest quote: 3999 False True
Precise sums, products and combinatorics
Adding many floats accumulates tiny errors; math.fsum corrects for them. math.prod multiplies a sequence, which is how you chain monthly growth factors, and comb, perm and factorial cover counting problems.
import math
print(sum([0.1] * 10))
print(math.fsum([0.1] * 10))
growth = [1.05, 1.08, 0.97]
print("3-month factor:", round(math.prod(growth), 5))
print("Teams of 3 from 10 staff:", math.comb(10, 3))
print("Ways to rank 2 of 5 vendors:", math.perm(5, 2))
print("5!:", math.factorial(5))
print("GCD / LCM:", math.gcd(48, 36), math.lcm(4, 6))
Output: 0.9999999999999999 1.0 3-month factor: 1.09998 Teams of 3 from 10 staff: 120 Ways to rank 2 of 5 vendors: 20 5!: 120 GCD / LCM: 12 12
| Function | Returns | Excel equivalent |
|---|---|---|
math.ceil(x) |
Smallest integer >= x | CEILING / ROUNDUP |
math.floor(x) |
Largest integer <= x | FLOOR / ROUNDDOWN |
math.trunc(x) |
Integer part toward zero | TRUNC |
math.sqrt(x) |
Square root | SQRT |
math.pow(x, y) |
x to the power y as float | POWER |
math.log(x, base) |
Logarithm (natural by default) | LN / LOG |
math.exp(x) |
e to the power x | EXP |
math.fsum(seq) |
Accurate float sum | SUM |
math.prod(seq) |
Product of all items | PRODUCT |
math.comb(n, k) |
Combinations | COMBIN |
math.factorial(n) |
n! | FACT |
math.isclose(a, b) |
True if nearly equal | – |
Reach for this table when translating a spreadsheet into Python. Most Excel maths functions have a one-to-one match, and the few that do not (such as MROUND) are a single line of arithmetic with round or Decimal.
Money: Decimal and ROUND_HALF_UP
Accountants round 2.675 to 2.68, and they expect 0.1 + 0.2 to equal 0.3. Binary floats fail both tests. The decimal module stores numbers exactly as written, so use it for prices, tax and totals and keep floats for measurements and statistics.
from decimal import Decimal, ROUND_HALF_UP
import math
print(0.1 + 0.2 == 0.3, math.isclose(0.1 + 0.2, 0.3))
price = Decimal("2.675")
print(price.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
line_total = Decimal("1299.99") * 3
gst = (line_total * Decimal("0.18")).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
print("Subtotal:", line_total, "GST:", gst, "Total:", line_total + gst)
Output: False True 2.68 Subtotal: 3899.97 GST: 701.99 Total: 4601.96
Decimal from a float, as in Decimal(2.675), copies the float’s binary error into the Decimal and defeats the purpose. Always pass a string: Decimal("2.675").Get an EMI calculator and a check on the formula in one go.
Write a Python function emi(principal, annual_rate_percent, months) that returns the monthly instalment for a reducing-balance loan using the standard EMI formula, rounded half-up to 2 decimals with the decimal module. Show the result for 500000 at 9.5% over 60 months, then print a 12-row amortisation table with columns Month, EMI, Interest, Principal, Balance.
Ask for an explanation of banker’s rounding with a practical fix.
Explain in plain English why Python's round(2.5) returns 2 and round(2.675, 2) returns 2.67. Then give me a small helper function round_money(value) that always rounds half-up to 2 decimals the way an accountant expects, and show 6 test cases proving it.
Common mistakes
- Expecting
round(2.5)to give 3. Python rounds half to even; useDecimalwithROUND_HALF_UPfor money. - Comparing floats with
==. Usemath.iscloseor work inDecimal. - Calling
math.sqrt(-1), which raisesValueError; usecmathif you truly need complex results. - Forgetting
import mathand gettingNameError: name 'math' is not defined. - Using
math.floor(x / y)whenx // yalready does the job for integers.
Exercise
A warehouse ships 1,780 units in cartons of 24, and each carton costs 3.75 to ship. Print the number of cartons, the shipping cost rounded half-up to two decimals with Decimal, and how many units the last carton contains.
Show answer
import math
from decimal import Decimal, ROUND_HALF_UP
units, per_carton = 1780, 24
cartons = math.ceil(units / per_carton)
cost = (Decimal(cartons) * Decimal("3.75")).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
last = units % per_carton or per_carton
print(cartons, cost, last) # 75 281.25 4
Related chapters
FAQ
Why does Python round 2.5 to 2?
round() uses round-half-to-even, which avoids bias when summing many rounded values. For accounting-style half-up rounding use Decimal.quantize with ROUND_HALF_UP.
What is the difference between math.pow and the ** operator?
** works on ints and floats and keeps ints exact, so 2 ** 100 is precise. math.pow converts both arguments to float and always returns a float.
Should I use float or Decimal for prices?
Use Decimal built from strings for prices, tax and totals so 0.1 + 0.2 equals 0.3 exactly. Use float for measurements, ratios and statistics where speed matters more.
Working with spreadsheets too? Ready-made Excel, Google Sheets and Power BI templates are at NextGenTemplates.com.
Chapter 27 of 48 · Python with AI: all 48 chapters



