Numbers and Operators
Division splits into three different operators in Python. Get to know them and the surprises they hide.
The three divisions
/ true division, // floor division, % the remainder.
Mixing ints and floats
An operation involving a float answers in floats. Watch the types change as you edit.
The operator set
2 ** 10 is 2 raised to the 10th-7 // 2 is -4, not -3. Floor division always rounds down, toward minus infinity, not toward zero.Overview
The problem it solves
Older languages gave you one division that returned a whole count, quietly throwing away the fraction. Python 3 decided that was a silent bug factory: true division / keeps the fraction and answers in float. Floor division // is the "whole-count division" people were really reaching for — useful for splitting a list into equal chunks. Or turning seconds into minutes.
Python has two numeric types for everyday use: int (whole counts, arbitrary precision, exact) and float (decimals, 64-bit IEEE 754, approximate). Integers have no upper limit — 2 ** 1000 is a 302-digit count and Python computes it just. Floats are fast and fixed-size but cannot represent 0.1 just. It is why 0.1 + 0.2 != 0.3. For money and exact decimals, use the decimal module.
The arithmetic operators are +, -, *, / (true division, always returns float), // (floor division, rounds toward negative infinity), % (modulo) and ** (exponentiation). divmod(a, b) returns both quotient and remainder as a tuple. When an int and a float meet in an step, the int is promoted to float: 3 + 2.0 is 5.0.
Comparison operators (<, >, <=, >=, ==, !=) work across numeric types: 1 == 1.0 is True. Chained comparisons are idiomatic: 0 < x < 10 checks that x is between 0 and 10 exclusive, testing x just once. Boolean operators and, or, not short-circuit: x != 0 and y / x > 1 is safe since the division is skipped when x is zero.
Assignment operators (+=, -=, *=, //=) modify a var in place syntactically, though for immutable types like int and float they rebind the name to a new object. x += 1 is equivalent to x = x + 1, not an in-place mutation.
Division splits into three different operators in Python.
In this guide
- The problem it solves
- The two kinds of count
- What modulo is really for
- Floating point, and where it goes wrong
- Rounding, and the surprise in it
- Guided experiments
- In one line
- The operators that shorten common code
- Common mistakes
- A worked case: divmod and the units it gives you
- Checking floats without lying to yourself
- The operators that surprise people on negatives
- Choosing the right numeric type
- The bitwise operators, briefly
- Questions people ask
- Recap in one screen
Numbers and Operators: A Practical Guide
Python 3 split division into two on purpose — each half is what a different kind of code actually wants.
The problem it solves
Older languages gave you one division that returned a whole count, quietly throwing away the fraction. Python 3 decided that was a silent bug factory: true division / keeps the fraction and answers in float. Floor division // is the "whole-count division" people were really reaching for — useful for splitting a list into equal chunks. Or turning seconds into minutes.
Python has two numeric types for everyday use: int (whole counts, arbitrary precision, exact) and float (decimals, 64-bit IEEE 754, approximate). Integers have no upper limit — 2 ** 1000 is a 302-digit count and Python computes it just. Floats are fast and fixed-size but cannot represent 0.1 just. It is why 0.1 + 0.2 != 0.3. For money and exact decimals, use the decimal module.
The arithmetic operators are +, -, *, / (true division, always returns float), // (floor division, rounds toward negative infinity), % (modulo) and ** (exponentiation). divmod(a, b) returns both quotient and remainder as a tuple. When an int and a float meet in an step, the int is promoted to float: 3 + 2.0 is 5.0.
Comparison operators (<, >, <=, >=, ==, !=) work across numeric types: 1 == 1.0 is True. Chained comparisons are idiomatic: 0 < x < 10 checks that x is between 0 and 10 exclusive, testing x just once. Boolean operators and, or, not short-circuit: x != 0 and y / x > 1 is safe since the division is skipped when x is zero.
Assignment operators (+=, -=, *=, //=) modify a var in place syntactically, though for immutable types like int and float they rebind the name to a new object. x += 1 is equivalent to x = x + 1, not an in-place mutation.
The two kinds of count
count = 42 # int - whole numbers, any size
price = 19.99 # float - decimals, with a small rounding errorPython integers have no size limit — 2 ** 1000 gives an exact 302-digit count — which is unusual among programming languages and at times very convenient.
Floats are the compromise each language makes for decimals: fast, fixed size, and approximate.
The division split is Python 3's signature change. / always returns a float: 7 / 2 is 3.5, 6 / 3 is 2.0. // returns the floor: 7 // 2 is 3, 7.0 // 2 is 3.0. The modulo operator % gives the remainder: 7 % 2 is 1. Together, // and % partition any division into quotient and remainder. And divmod(7, 2) returns both as a tuple.
Exponentiation is **: 2 ** 10 is 1024. On integers the result is exact. And 2 ** 1000 gives an exact 302-digit count — Python integers have arbitrary precision. On floats, ** is a shorthand for math.pow() and is subject to the usual floating-point rounding.
The floating-point trap is 0.1 + 0.2 != 0.3. Binary floating point cannot represent 0.1 just, so arithmetic accumulates tiny errors. For display, rounding hides it: round(0.1 + 0.2, 1) == 0.3 is True. For money or exact decimal arithmetic, use the decimal module. For comparisons, use math.isclose(a, b) instead of ==.
Type mixing follows one rule: when an int and a float meet in an step, the int is promoted to float. 3 + 2.0 is 5.0, not 5. This is implicit and almost always what you want. But it means the result type can surprise you when you expected an int.
The arithmetic operators:
7 + 3 # 10
7 - 3 # 4
7 * 3 # 21
7 / 3 # 2.3333... - true division, ALWAYS returns a float
7 // 3 # 2 - floor division, discards the remainder
7 % 3 # 1 - modulo, the remainder
7 ** 3 # 343 - powerThe two to watch are / and //. In Python 3, 6 / 3 gives 2.0, not 2 — a float, still when it divides just. Use // when you want a whole count, such as an index or a count of complete groups.
What modulo is really for
% returns the remainder, and it answers three questions that come up constantly:
n % 2 == 0 # is n even?
n % 15 == 0 # is n divisible by 15?
seconds % 60 # seconds left over after whole minutes
index % len(items) # wrap around to the start of a listThe wrap-around use is the neatest: (i + 1) % len(colours) cycles through a list for ever without ever going out of bounds — the standard way to rotate through colours, players or servers.
Modulo with negative counts follows the sign of the right operand in Python: -7 % 3 is 2, not -1. That differs from C and JavaScript and is worth remembering when porting code.
Floating point, and where it goes wrong
0.1 + 0.2 # 0.30000000000000004
0.1 + 0.2 == 0.3 # FalseThis is not a Python bug. Floats are stored in binary. And 0.1 has no exact binary form, in the same way that 1/3 has no exact decimal one. The error is around 10⁻¹⁶ and it compounds across long math.
Two consequences to build into your habits:
Never check floats with ==. Use a tolerance:
import math
math.isclose(0.1 + 0.2, 0.3) # TrueNever use floats for money. Use decimal.Decimal, or work in whole pence:
from decimal import Decimal
Decimal("0.1") + Decimal("0.2") # Decimal('0.3') - exactNote the strings in that case: Decimal(0.1) from a float would inherit the error you were trying to escape.
Rounding, and the surprise in it
round(2.5) # 2 -- not 3
round(3.5) # 4
round(2.675, 2) # 2.67 -- not 2.68The first two use banker's rounding: exact halves round to the nearest still count. Over many vals this avoids the upward bias that always-round-up adds. It is why it is the standard in finance and statistics.
The third is the float problem again — 2.675 is really 2.67499999... in binary, so it rounds down. Decimal gives the answer you expected.
Tied tools: math.floor and math.ceil always go down and up, int() truncates towards zero (so int(-2.7) is -2, not -3). And abs() drops the sign.
Guided experiments
- Read the first four lines. Line by line: 3.5, then 3 (fraction thrown away), then 1 (the leftover), then 1024. Note that
7 // 2gives 3 while7 / 2gives 3.5 — same inputs, other promises. - Check the types. In the second editor,
4 + 2.0returns6.0, a float, since one operand was a float. Each operator follows this rule: the float wins. - Break on zero. Add
print(1 / 0)to either editor and run.ZeroDivisionErroris Python telling you the step is undefined — the message names the exact line that caused it. - Explore modulo yourself. Replace the counts with a few of your own, one pair at a time. And check the pattern:
x % yis always between0andy - 1. That boundedness is why it is so useful for wrapping.
In one line
Python has three division-shaped operators for three other jobs: / when you want the exact answer, // when you want whole-count chunks. And % when you want the remainder that chunking throws away. Getting the right one wrong is a classic source of bugs that give no error at all — the program just silently rounds. When counts misbehave, check your operators before your logic.
The operators that shorten common code
x = 5
x += 3 # x = x + 3, and the same for -= *= /= //= %= **=
a, b = b, a # swap, with no temporary variable
7 < x < 20 # chained comparison, evaluated once
divmod(17, 5) # (3, 2) - quotient and remainder together
sum([1, 2, 3]) # 6
min(values), max(values)
abs(-4) # 4The math module carries the rest of the standard toolkit:
import math
math.sqrt(16) # 4.0
math.floor(3.7) # 3
math.ceil(3.2) # 4
math.pi # 3.141592653589793
math.log(100, 10) # 2.0
math.inf # infinity, useful as a starting "worst value"math.inf deserves a mention: initialising best = math.inf before a loop looking for a min is cleaner and safer than picking an arbitrary large count.
Common mistakes
- Expecting
/to give a whole count. It returns a float in Python 3. Use//. - Checking floats with
==. Usemath.isclose. - With floats for money. Use
Decimalor integer pence. - Assuming
roundalways rounds halves up. It rounds to still. - Forgetting
input()returns a string.input("n: ") * 3repeats the text. Wrap it inint(). - Integer division on negatives.
-7 // 2is-4, since it floors rather than truncating.
A worked case: divmod and the units it gives you
// and % are almost always wanted together, and divmod returns both in one call:
minutes, seconds = divmod(3725, 60)
hours, minutes = divmod(minutes, 60)
print(f"{hours}h {minutes}m {seconds}s")1h 2m 5sEach divmod peels off one unit: how many whole minutes. And what is left over. Applying it again to the minutes gives hours. The same shape turns bytes to kilobytes, pence to pounds. Or an item count to pages and a remainder.
Doing it with two operators means writing the division twice. And the two can drift apart when someone edits one and not the other. divmod computes them together, so they cannot disagree.
The general pattern is worth recognising: any time you have a total and a unit size, // is "how many whole units" and % is "what did not fit". Chunking a list, paginating results, laying out a grid and formatting a duration are all the same arithmetic.
Checking floats without lying to yourself
"Never check floats with ==" is easy to say and needs one more sentence to be useful, since math.isclose has two tolerances and the default just covers one case.
import math
print(math.isclose(0.1 + 0.2, 0.3))
print(math.isclose(1e-9, 0.0))
print(math.isclose(1e-9, 0.0, abs_tol=1e-6))True False TrueBy default isclose uses a relative tolerance: the two vals must be within a small proportion of each other. That is the right test for ordinary magnitudes, and it is why the first line works.
It fails against zero, and the second line shows it. Nothing is within a proportion of zero except zero it. So a comparison to zero always needs an absolute tolerance, chosen from what "negligible" means in your problem. That is what abs_tol is for. And forgetting it is the most common way this function surprises people.
The tied habit is to check rounded vals just when rounding is the question. round(a, 2) == round(b, 2) is a legitimate test for "do these agree to the penny", and a poor substitute for a tolerance when the question is "are these the same count".
The operators that surprise people on negatives
Three steps disagree with C, JavaScript and most people's intuition, all for the same underlying reason: Python floors rather than truncating.
print(-7 // 2, -7 % 3, int(-2.7))-4 2 -2-7 // 2 is -4, not -3, since floor division rounds towards negative infinity rather than towards zero. -7 % 3 is 2, not -1, since the result takes the sign of the right-hand operand. And int(-2.7) is -2 since int truncates, which is the one that does *not* floor — making // and int() disagree on negatives.
The modulo behaviour is the useful one. Since the result always has the sign of the divisor, i % len(items) is a valid index for any i, positive or negative, which is just what makes the wrap-around idiom safe. In languages where modulo can give a negative, that idiom needs a correction term.
The rule to carry: // and % are set so that a == (a // b) * b + a % b holds for each pair, and all else follows from keeping that identity true.
Choosing the right numeric type
Four types cover numeric work in Python. And picking the wrong one is the cause of most "the arithmetic is wrong" reports.
**int** for anything countable: quantities, indices, ids, pence. It is exact and unbounded, so there is no overflow to plan around and no rounding to worry about. When a value will just ever be whole, an int removes an whole class of problem.
**float** for measurement and anything scientific: lengths, temperatures, probabilities, timings. It is fast, fixed-size and approximate, and the approximation is acceptable since the input was approximate too. A sensor reading exact to three decimal places loses nothing to a form error at the sixteenth.
**decimal.Decimal** for money and anything a person will check by hand. It works in base ten, so the vals you write are the vals it holds, and it carries an explicit precision and rounding mode. The cost is speed, which almost never matters next to giving a total that matches the invoice.
**fractions.Fraction** for exact ratios, where a third really must be a third rather than 0.333…. Rare, and the right answer when it applies.
The decision is easier than it looks: if the count counts something, int. If it measures something, float. If somebody will audit it, Decimal. Mixing a Decimal with a float in one expression turns back to float and discards the exactness you paid for. So the choice has to hold across the whole math rather than at the end of it.
The bitwise operators, briefly
Six operators work on the binary form of integers, and while most Python never touches them, they turn up often enough to be worth recognising.
&, | and ^ are and, or and exclusive-or applied bit by bit. ~ inverts each bit. << and >> shift left and right, which multiply and divide by powers of two — n << 3 is n * 8. And n >> 1 is n // 2 for positive counts.
Where you will meet them is flags. A single integer can carry many independent on-off settings, one per bit, which is how file permissions, regular-expression options and many C-derived APIs are expressed. re.IGNORECASE | re.MULTILINE is just this: two flags mixed into one integer with |. And the library tests each with &.
Two cautions. These are the same characters as the set operators, and the meaning is fully other — & on two sets is intersection, on two integers it is bitwise and. And and/or are not the same as &/|: the word forms short-circuit and give one of their operands, the symbols always test both sides and work bit by bit. With & where and was meant is a real bug that often gives the right answer on the vals you tested.
For readable flags in your own code, enum.Flag gives the same mixing behaviour with names attached, which is easier to debug than an integer whose meaning is spread across five bit positions.
Questions people ask
Is there a max integer? No. Python integers grow to whatever memory lets.
How do I round to two decimal places for display? f"{value:.2f}". It formats without changing the stored value.
What is the gap between // and int()? // floors towards negative infinity. int() truncates towards zero. They differ on negative counts.
How do I generate a random count? random.randint(1, 6) for integers, random.random() for a float between 0 and 1. For anything security-tied use the secrets module instead.
Why is 0.1 + 0.2 not 0.3? Binary floating point cannot represent 0.1 just. Each language with IEEE 754 floats behaves the same way.
Is ** the same as math.pow? Nearly — ** keeps integers exact where it can, while math.pow always returns a float.
Why does 2 ** 0.5 give a float? Since the result is not a whole count. ** keeps integers exact just when both operands are integers and the exponent is not negative.
How do I check whether a float is a whole count? x.is_integer(), which is a way on floats and reads better than checking against int(x).
Recap in one screen
intis unbounded and exact.floatis fast and approximate./always returns a float.//floors;%gives the remainder and is how you wrap around a list.- Floats cannot represent decimals just — check with
math.isclose. And useDecimalfor money. rounduses banker's rounding, soround(2.5)is 2.input()returns text; turn it before doing arithmetic.