Python's math operators are the seven arithmetic symbols +, -, *, /, //, %, and **, used for addition, subtraction, multiplication, true division, floor division, modulo (remainder), and exponentiation. The three behaviors that catch people out are that / always returns a float in Python 3 (6 / 3 is 2.0), that % takes the sign of the divisor (-7 % 2 is 1), and that round() uses banker's rounding (round(2.5) is 2). Operator precedence follows PEMDAS, with ** binding tightest and being right-associative.
That is the summary an AI Overview would hand you. What it can't show you is why those rules bite — the diagrams below trace how precedence actually resolves an expression and how floor division and modulo split a negative number, and the symptom-to-fix table maps the exact error messages and wrong answers you'll hit to their causes. This guide covers all of it with correct, runnable examples.
The Arithmetic Operators
Python has seven core arithmetic operators:
| Operator | Name | Example | Result |
|---|---|---|---|
+ | Addition | 1 + 2 | 3 |
- | Subtraction | 3 - 2 | 1 |
* | Multiplication | 2 * 3 | 6 |
/ | True division | 6 / 3 | 2.0 |
// | Floor division | 5 // 2 | 2 |
% | Modulo (remainder) | 5 % 2 | 1 |
** | Exponentiation | 2 ** 3 | 8 |
Which operator should I use?
The seven operators overlap in ways that cause real bugs. This table maps a goal to the right tool and the trap to avoid:
| Your goal | Use | Not this | Why |
|---|---|---|---|
| Exact decimal result (e.g. average) | / | // | / keeps the fraction as a float |
| Whole-number quotient (pages, rows) | // | int(a / b) | // avoids float rounding on huge ints |
| Remainder / "every Nth" / wrap-around | % | manual subtraction | % handles negatives predictably |
| Both quotient and remainder | divmod(a, b) | two separate ops | one call, guaranteed consistent |
| Powers and roots | ** | math.pow | ** keeps int results; math.pow is always float |
| Money / precise decimals | decimal.Decimal | float | floats can't represent 0.1 exactly |
The first surprise: in Python 3, the / operator always returns a float, even when the division is exact.
print(6 / 3) # 2.0 (a float, not 2)
print(7 / 2) # 3.5
print(type(6 / 3)) # <class 'float'>
Floor Division and Modulo
Floor division (//) divides and rounds the result down toward negative infinity, returning an int when both operands are integers. The modulo operator (%) gives the remainder.
print(7 // 2) # 3
print(7 % 2) # 1
The catch is how these behave with negative numbers. Floor division rounds toward negative infinity (not toward zero), and in Python the result of % always takes the sign of the divisor:
print(-7 // 2) # -4 (rounds down, not -3)
print(-7 % 2) # 1 (sign matches the divisor, 2)
print(7 % -2) # -1 (sign matches the divisor, -2)
A handy related built-in is divmod(), which returns both the quotient and remainder at once: divmod(17, 5) gives (3, 2).
The rule that keeps // and % consistent is the identity (a // b) * b + (a % b) == a. Because Python rounds the quotient down and forces the remainder to match the divisor's sign, the two operators always reconstruct the original number. The diagram below traces it for -7:
Exponentiation and Roots
Use ** to raise a number to a power. To compute roots, raise to a fractional exponent:
print(2 ** 3) # 8
print(5 ** 2) # 25
print(9 ** 0.5) # 3.0 (square root)
print(27 ** (1/3)) # 3.0 (cube root)
In Python 3, 1/3 is already a float, so 27 ** (1/3) works as expected — you no longer need the old 1/3.0 workaround that was required in Python 2.
Operator Precedence and Parentheses
Python evaluates operators in a fixed order, similar to the PEMDAS rule from math class: parentheses first, then exponents, then multiplication/division/floor-division/modulo, then addition/subtraction.
print(1 + 2 * 3) # 7 (multiplication happens first)
print((1 + 2) * 3) # 9 (parentheses force addition first)
print(2 ** 3 ** 2) # 512 (** is right-associative: 2 ** 9)
Note that ** is right-associative, so 2 ** 3 ** 2 is 2 ** (3 ** 2), which equals 512, not 64. When precedence isn't obvious at a glance, add parentheses — they cost nothing and make intent clear to the next reader.
Augmented Assignment
Augmented assignment operators combine an operation with assignment, updating a variable in place. They are shorthand and improve readability.
x = 10
x += 5 # same as x = x + 5
print(x) # 15
x -= 3 # x = x - 3
print(x) # 12
x *= 2 # x = x * 2
print(x) # 24
x //= 5 # x = x // 5
print(x) # 4
x **= 2 # x = x ** 2
print(x) # 16
Every arithmetic operator has an augmented form: +=, -=, *=, /=, //=, %=, and **=.
Integers vs. Floats
Python automatically picks a numeric type. Whole numbers are int, and numbers with a decimal point are float. Operators promote to float when needed:
print(4 + 2) # 6 (int + int -> int)
print(4 + 2.0) # 6.0 (int + float -> float)
print(10 / 2) # 5.0 (/ always produces a float)
print(10 // 3) # 3 (int floor division stays int)
Python integers have arbitrary precision — they grow as large as memory allows, with no overflow:
print(2 ** 100) # 1267650600228229401496703205376
Common Pitfalls
Most math-operator bugs come from a small set of surprises. Use this symptom-to-fix table as a fast lookup before reading the detail below:
| Symptom you see | Cause | Fix |
|---|---|---|
6 / 3 gives 2.0 where you wanted 2 | / is always float division | Use // for an int quotient |
0.1 + 0.2 == 0.3 is False | Binary float can't store 0.1 exactly | math.isclose(a, b) or the decimal module |
-7 // 2 is -4, expected -3 | // rounds toward negative infinity | Expected behavior; use int(-7 / 2) to truncate toward zero |
-7 % 2 is 1, expected -1 | % takes the sign of the divisor | Expected in Python; use math.fmod for C-style sign |
round(2.5) is 2, expected 3 | Banker's rounding (round-half-to-even) | Use math.ceil, or decimal with ROUND_HALF_UP |
2 ** 3 ** 2 is 512, expected 64 | ** is right-associative | Add parentheses: (2 ** 3) ** 2 |
ZeroDivisionError | Divisor evaluated to 0 | Guard with if divisor != 0 before dividing |
Floating-point imprecision
Floats are stored in binary and cannot represent every decimal exactly, which leads to tiny rounding errors:
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False
This is not a Python bug — it affects nearly every programming language. When comparing floats, test that they're close rather than exactly equal:
import math
print(math.isclose(0.1 + 0.2, 0.3)) # True
For exact decimal math (money, for example), use the decimal module instead of floats.
Division by zero
Dividing by zero raises a ZeroDivisionError rather than returning infinity:
print(10 / 0) # ZeroDivisionError: division by zero
print(10 % 0) # ZeroDivisionError: integer modulo by zero
Guard against it when the divisor comes from user input or external data:
divisor = 0
if divisor != 0:
result = 10 / divisor
else:
result = None
round() uses banker's rounding
Python's built-in round() uses round-half-to-even (banker's rounding), so a value exactly halfway between two integers rounds toward the nearest even number:
print(round(0.5)) # 0 (not 1)
print(round(1.5)) # 2
print(round(2.5)) # 2 (not 3)
print(round(2.675, 2)) # 2.67 (float imprecision, not 2.68)
This is intentional and reduces cumulative bias across many roundings, but it surprises people expecting 2.5 to become 3.
Useful math Module Functions
For anything beyond the basic operators, import the math module:
import math
print(math.sqrt(16)) # 4.0 (square root, always a float)
print(math.floor(3.7)) # 3 (round down to int)
print(math.ceil(3.2)) # 4 (round up to int)
print(math.pow(2, 3)) # 8.0 (returns a float)
print(2 ** 3) # 8 (operator returns an int here)
print(abs(-5)) # 5 (built-in, no import needed)
print(math.pi) # 3.141592653589793
Note the difference between math.pow() and **: math.pow() always returns a float, while the ** operator preserves int results when both operands are integers. For integer exponentiation, prefer **.
Wrapping Up
The arithmetic operators are quick to learn, but division behavior, signed modulo, floating-point limits, and banker's rounding are the details that separate correct code from subtle bugs. Keep these rules in mind:
/always returns a float; use//for integer floor division.%takes the sign of the divisor, and//rounds toward negative infinity.- Floats are approximate — use
math.isclose()or thedecimalmodule when precision matters. - Guard against
ZeroDivisionErrorwhenever a divisor isn't guaranteed nonzero.
For more programming tutorials and developer tools, browse the InventiveHQ blog.