← Back to Core Python
Lesson 4 · Python Foundations

Numbers and Arithmetic

Arithmetic, floor division, modulo, exponentiation, and operator precedence.

Beginner30 min

What you will be able to do

  • Use the seven arithmetic operators and predict the type each one returns
  • Explain the difference between /, //, and %, including on negative numbers
  • Apply operator precedence, and use parentheses where it matters
  • Use abs, round, pow, divmod, min, max, and sum
  • Say why round(2.5) is 2 and not 3
  • Write the calculations applications actually need: percentages, discounts, tax, averages, pagination

The idea, in plain English

Almost every program counts something. Prices and quantities, marks and averages, lessons completed out of a total, users this week against users last week - the arithmetic itself is school-level, and the part that catches people out is which operator to reach for and what type comes back.

Python has seven arithmetic operators. Four behave exactly as you would expect. The three that do not - `/`, `//`, and `%` - are worth the time this lesson spends on them, because between them they cause most beginner arithmetic bugs.

The first surprise is that `/` always gives you a float. `10 / 2` is 5.0, not 5, even though the answer is a whole number. If you want an integer back, `//` is the operator you meant.

The second is that arithmetic is not evaluated left to right. `10 + 5 * 2` is 20, because multiplication binds tighter than addition. Python follows ordinary mathematical precedence, which is convenient right up until you assume it does not.

Worked example: Turning 7384 seconds into 2 hours, 3 minutes, 4 seconds.

The three ways to divide

Given 10 and 3, Python can answer three different questions, and they are genuinely different questions rather than three formats of one answer.

The one to be careful with is `//` on negative numbers. It does not chop off the decimal part - it rounds down, towards negative infinity. So -10 // 3 is -4, not -3, and that is a different result from int(-10 / 3), which truncates towards zero and gives -3.

Given 10 and 3
/10 / 3 is 3.3333333333333335. True division. Always returns a float, even when the result is whole - 10 / 2 is 5.0.
//10 // 3 is 3. Floor division - how many whole 3s fit. Returns an int when both operands are ints.
%10 % 3 is 1. The remainder left over. Nothing to do with percentages.
divmod()divmod(10, 3) is (3, 1). Both answers at once, which is exactly what time and pagination need.
// on negatives-10 // 3 is -4, because floor rounds down rather than towards zero. int(-10 / 3) gives -3 instead.
% on negatives-10 % 3 is 2, not -1. The result takes the sign of the divisor, which keeps a % b always in range for indexing.

Tip: A good habit: use // when the answer should be a whole number of things - pages, rows, complete groups. Use / when you want a measurement, like an average or a rate.

Why round(2.5) is 2

round(2.5) gives 2, and round(3.5) gives 4. That is not a bug and it is not inconsistent - Python rounds a half to the nearest even number, which is often called banker’s rounding.

The reason is bias. Always rounding halves up makes a long column of numbers drift upward; rounding to even sends half of them up and half down, so the errors cancel over many values. Statistics and accounting both care about that.

It surprises people mostly when they test with a single number. If you genuinely need "always round halves up" - and invoices sometimes specify it - use Decimal with an explicit rounding mode rather than fighting round().

Watch out: round() on a float can also surprise for a second reason: round(2.675, 2) is 2.67, because 2.675 is not stored as exactly 2.675. Decimal avoids both problems at once.

Precedence, in the order Python applies it

Parentheses first, then exponentiation, then unary minus, then the multiplying group, then the adding group. Within a group, evaluation runs left to right - except `**`, which runs right to left, so 2 ** 3 ** 2 is 2 ** 9 and not 8 ** 2.

You do not need to memorise this. You need to know it exists, and to add parentheses the moment an expression stops being obvious at a glance - which is usually the second operator.

Highest binding first
( )Parentheses. Always evaluated first, and the only tool you need to override everything below.
**Exponentiation. Binds right to left: 2 ** 3 ** 2 is 512, not 64.
-xUnary minus. Note that -2 ** 2 is -4, because ** binds tighter than the minus sign.
* / // %Multiplication, division, floor division, remainder. Left to right among themselves.
+ -Addition and subtraction. Last, which is why 10 + 5 * 2 is 20.

Syntax and examples

All seven operators, on the same two numbers
a, b = 10, 3 print(a + b) # 13 print(a - b) # 7 print(a * b) # 30 print(a / b) # 3.3333333333333335 <- float, always print(a // b) # 3 <- int, floored print(a % b) # 1 <- remainder print(a ** b) # 1000
Floor division is not truncation
print(10 // 3) # 3 print(-10 // 3) # -4 floors towards negative infinity print(int(-10 / 3)) # -3 truncates towards zero print(10 % 3) # 1 print(-10 % 3) # 2 takes the sign of the divisor
Precedence, and the parentheses that fix it
print(10 + 5 * 2) # 20 not 30 print((10 + 5) * 2) # 30 print(10 + 5 * 2 ** 2) # 30 ** first: 5 * 4 = 20, then 10 + 20 print(2 ** 3 ** 2) # 512 ** is right to left: 2 ** 9 print(-2 ** 2) # -4 ** binds tighter than unary minus print((-2) ** 2) # 4
The numeric built-ins
print(abs(-250)) # 250 print(round(10.6789, 2)) # 10.68 print(pow(2, 10)) # 1024 print(pow(2, 10, 1000)) # 24 (2**10) % 1000, done efficiently print(divmod(10, 3)) # (3, 1) marks = [85, 92, 78, 90, 88] print(min(marks), max(marks), sum(marks)) # 78 92 433 print(sum(marks) / len(marks)) # 86.6
divmod earns its place: seconds into h:m:s
total_seconds = 7384 hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) print(f"{hours}h {minutes}m {seconds}s") # 2h 3m 4s
The calculations applications actually do
# Percentage complete completed, total = 24, 80 print(completed / total * 100) # 30.0 # Discount price, discount_pct = 2000, 10 final = price * (1 - discount_pct / 100) print(final) # 1800.0 # Tax on top subtotal, tax_pct = 1000, 18 print(subtotal * (1 + tax_pct / 100)) # 1180.0 # Pagination - // and % answer both halves total_items, per_page = 127, 10 print(total_items // per_page, total_items % per_page) # 12 7 print(-(-total_items // per_page)) # 13 pages needed

Tip: To round a division up rather than down, -(-a // b) is the standard trick - two negations turn a floor into a ceiling without importing math. It is how you work out how many pages 127 items need.

Arithmetic operators

+

Addition. Also joins strings and lists, which is why "1" + 1 is an error rather than 2.

10 + 3   # 13
-

Subtraction.

10 - 3   # 7
*

Multiplication. On a string it repeats: "ab" * 3 is "ababab".

10 * 3   # 30
/

True division. Always a float, even for exact results.

10 / 3   # 3.333...
//

Floor division. Rounds down, towards negative infinity.

10 // 3  # 3
%

Remainder. The workhorse behind even/odd, cycling, and pagination.

10 % 3   # 1
**

Exponentiation. Right-associative, and accepts negative and fractional exponents.

10 ** 3  # 1000

Numeric built-in functions

abs(x)

Distance from zero, sign removed. Useful for "how far apart are these".

abs(-250)  # 250
round(x, n)

Rounds to n decimal places, or to a whole number when n is omitted. Halves go to even.

round(10.6789, 2)  # 10.68
pow(x, y, m)

x to the power y. With a third argument, does it modulo m efficiently - used in cryptography.

pow(2, 10, 1000)  # 24
divmod(x, y)

Quotient and remainder together, as a tuple. Unpacks straight into two names.

h, m = divmod(185, 60)
min() / max()

Smallest and largest. Accept either several arguments or one iterable.

min(10, 5, 20)  # 5
sum()

Adds an iterable of numbers. Pair with len() for an average.

sum(marks) / len(marks)

Try it yourself

The code does not change. Swap the content string and the program does something else entirely.

The negative floor

“print(-10 // 3, int(-10 / 3))”

Banker’s rounding

“print(round(0.5), round(1.5), round(2.5), round(3.5))”

Right-associative power

“print(2 ** 3 ** 2, (2 ** 3) ** 2)”

Round up with floor division

“print(-(-127 // 10))”

What usually goes wrong

Reaching for / when you wanted a whole number

/ always returns a float. Using it for a count gives you 12.0 pages or 5.0 items, which then prints with a decimal point and fails an equality check against an int.

✗ pages = total_items / per_page     # 12.7
✓ pages = total_items // per_page    # 12
Reading % as "percent"

It is the remainder operator. 10 % 3 is 1, not 333%. A percentage is ordinary division and multiplication: part / whole * 100.

✗ percentage = completed % total
✓ percentage = completed / total * 100
Assuming left-to-right evaluation

10 + 5 * 2 is 20. Multiplication, division, floor division, and remainder all bind tighter than addition and subtraction, and ** binds tighter still.

✗ total = price + price * tax_rate / 100      # works, but only by luck
✓ total = price + (price * tax_rate / 100)   # says what you meant
Using floats for money

Covered in the data types lesson and worth repeating here, because this is the lesson where you start writing totals. Binary floats cannot hold 0.1 exactly, so a bill can come out a paisa short.

✗ total = 19.99 + 1.50
✓ from decimal import Decimal
total = Decimal("19.99") + Decimal("1.50")
Expecting round() to round halves up

round(2.5) is 2. Python rounds halves to the nearest even value to avoid upward bias across many numbers. Test with more than one value before concluding it is broken.

✗ round(2.5)      # expected 3, got 2
✓ from decimal import Decimal, ROUND_HALF_UP
Decimal("2.5").quantize(Decimal("1"), ROUND_HALF_UP)

Best practices

  • Choose // when the result counts things and / when it measures something.
  • Add parentheses as soon as an expression has two different operators, even where precedence already agrees with you.
  • Use divmod() when you need both the quotient and the remainder - it says what you mean and computes once.
  • Keep money in Decimal or in the smallest integer unit. Never in float.
  • Round only at the point of display, never during a calculation, so errors cannot accumulate.
  • Use sum(xs) / len(xs) for an average, and guard against len(xs) being zero.

Practice

Write these yourself before opening anything. Getting them wrong first is most of how this sticks.

1.

With a = 25 and b = 7, print the result of all seven arithmetic operators.

Show solution
a, b = 25, 7 print(a + b) # 32 print(a - b) # 18 print(a * b) # 175 print(a / b) # 3.5714285714285716 print(a // b) # 3 print(a % b) # 4 print(a ** b) # 6103515625
2.

Given number = 27, use % to print "Even" or "Odd".

Show hint

A number is even when the remainder after dividing by 2 is 0.

Show solution
number = 27 if number % 2 == 0: print("Even") else: print("Odd") # Odd
3.

From marks = [85, 90, 76, 88, 92], print the total, the number of subjects, the average, the highest and the lowest.

Show solution
marks = [85, 90, 76, 88, 92] print("Total: ", sum(marks)) # 431 print("Count: ", len(marks)) # 5 print("Average:", sum(marks) / len(marks)) # 86.2 print("Highest:", max(marks)) # 92 print("Lowest: ", min(marks)) # 76
4.

A 2500 item has a 15% discount. Print the discount amount and the final price.

Show solution
price = 2500 discount_percentage = 15 discount = price * discount_percentage / 100 final_price = price - discount print("Discount: ", discount) # 375.0 print("Final price:", final_price) # 2125.0
5.

A student has completed 32 of 80 lessons. Print the completion percentage with a % sign after it.

Show hint

An f-string will place the number inside the text for you.

Show solution
completed = 32 total = 80 progress = completed / total * 100 print(f"{progress}%") # 40.0%
6.

Convert 367 minutes into hours and minutes using divmod().

Show solution
total_minutes = 367 hours, minutes = divmod(total_minutes, 60) print(f"{hours} hours {minutes} minutes") # 6 hours 7 minutes
7.

Of 1200 students, 840 have finished. Print the completion percentage, how many remain, and the ratio of completed to remaining.

Show hint

The ratio is just one divided by the other.

Show solution
total_students = 1200 completed_students = 840 remaining = total_students - completed_students print(f"Completed: {completed_students / total_students * 100}%") # 70.0% print(f"Remaining: {remaining}") # 360 print(f"Ratio: {completed_students / remaining:.2f} to 1") # 2.33 to 1
Coding challenge

Shopping bill calculator

Work a price and quantity through a discount and then tax, printing each stage. The order matters: discount comes off first, tax goes on what is left.

It should
  • Start from a product price, a quantity, a discount percentage, and a tax percentage
  • Print the subtotal, the discount amount, the price after discount, the tax amount, and the final bill
  • Apply tax to the discounted price, not to the subtotal
  • Round every money value to two decimal places for display only
  • Lay the output out so the numbers line up and can be read as a bill
Start here
product_price = 2500 quantity = 3 discount_percentage = 10 tax_percentage = 18 # subtotal -> discount -> after discount -> tax -> final
Show one solution
One solution
product_price = 2500 quantity = 3 discount_percentage = 10 tax_percentage = 18 subtotal = product_price * quantity discount = subtotal * discount_percentage / 100 after_discount = subtotal - discount # Tax applies to what is actually being paid, so it comes after the discount. tax = after_discount * tax_percentage / 100 final_bill = after_discount + tax print(f"Product price {product_price:>12,.2f}") print(f"Quantity {quantity:>12}") print(f"{'-' * 28}") print(f"Subtotal {subtotal:>12,.2f}") print(f"Discount {discount_percentage}% {-discount:>12,.2f}") print(f"After discount {after_discount:>12,.2f}") print(f"Tax {tax_percentage}% {tax:>12,.2f}") print(f"{'-' * 28}") print(f"Final bill {final_bill:>12,.2f}") # Subtotal 7,500.00 # Discount 10% -750.00 # After discount 6,750.00 # Tax 18% 1,215.00 # Final bill 7,965.00

Key points

  • / is true division and always returns a float, even when the answer is whole.
  • // is floor division: it rounds down towards negative infinity, so -10 // 3 is -4.
  • % is the remainder, not a percentage. -10 % 3 is 2, taking the sign of the divisor.
  • divmod(a, b) gives the quotient and remainder together, and unpacks into two names.
  • ** is exponentiation, binds right to left, and binds tighter than unary minus - so -2 ** 2 is -4.
  • Precedence runs: parentheses, **, unary minus, then * / // %, then + -.
  • round() rounds halves to the nearest even number, so round(2.5) is 2.
  • abs, round, pow, divmod, min, max, and sum cover nearly all everyday numeric work.
  • An average is sum(xs) / len(xs), and len(xs) being zero will raise.
  • Use Decimal for money, and round only when displaying.

Quick check before you move on

With a = 10 and b = 3, what do a / b, a // b, a % b, and a ** b print?
3.3333333333333335, then 3, then 1, then 1000. Four different questions about the same pair of numbers.
What is 10 + 5 * 2 ** 2?
30. Exponentiation first gives 2 ** 2 = 4, then multiplication gives 5 * 4 = 20, then addition gives 10 + 20 = 30.
Why is -10 // 3 equal to -4 rather than -3?
Floor division rounds down towards negative infinity, and -3.33 rounds down to -4. Truncating towards zero - which is what int(-10 / 3) does - would give -3 instead.
What does -2 ** 2 evaluate to?
-4. ** binds tighter than the unary minus, so Python computes 2 ** 2 and then negates it. Write (-2) ** 2 if you want 4.

Interview questions

What is the difference between // and int() on a negative number?

// floors - it rounds towards negative infinity - so -10 // 3 is -4. int() truncates towards zero, so int(-10 / 3) is -3. They agree on positive numbers and diverge on negative ones, which is exactly the kind of difference that survives testing and then fails in production.

Why is -10 % 3 equal to 2 rather than -1?

Python defines the remainder to take the sign of the divisor, so a % b is always in [0, b) for positive b. That makes it safe for indexing and cycling - i % len(xs) is always a valid index. C and Java take the sign of the dividend instead, which is why the same expression gives -1 there.

Why does / return a float even for 10 / 2?

Consistency of type. If / returned int sometimes and float others, the return type of an expression would depend on its runtime values, and every caller would have to handle both. Python 3 made / always true division and gave the integer behaviour its own operator, //.

What is the third argument to pow() for?

Modular exponentiation - pow(base, exp, mod) computes (base ** exp) % mod without ever building the enormous intermediate number. It is what makes RSA and Diffie-Hellman practical, where the exponents have hundreds of digits.

What is floating-point precision, in one sentence?

Floats are stored in binary, and most decimal fractions have no exact binary representation, so values like 0.1 are held as very close approximations and the tiny errors become visible when you combine them.

Why is Decimal preferred over float for money?

Decimal stores base-10 digits exactly, so 0.1 + 0.2 really is 0.3 and a total matches the figure on the invoice. It also lets you set precision and a rounding mode explicitly, which accounting rules usually specify. The cost is speed, which almost never matters in a billing path.

Python integers have no maximum. What does that cost?

Arbitrary precision means no overflow, which removes a whole class of bug. The price is that an int is a heap object rather than a machine word, so arithmetic is slower and memory larger than a fixed-width type - which is why NumPy offers int32 and int64 for numeric work at scale.

How would you calculate a percentage safely?

part / whole * 100, with a guard for whole being zero, since that raises ZeroDivisionError rather than returning infinity. Multiply after dividing rather than before when the numbers are large, and round only for display.

Quiz

  1. 1.

    What is the difference between / and //?

  2. 2.

    What does % return, and what is it commonly used for?

  3. 3.

    What does divmod(10, 3) return?

  4. 4.

    What is 2 ** 3 ** 2, and why?

  5. 5.

    Why does round(2.5) return 2?

  6. 6.

    How do you round a division up rather than down?

Comments

Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.

Loading comments...