Single slash is true division and always returns a float. Double slash is floor division and rounds toward negative infinity. Percent is the modulo. The surprise is that -7 // 2 is -4, not -3, because flooring is not truncation.
Three operators, one of which behaves differently from the equivalent in C, Java, JavaScript and Go. That difference is consistent and defensible, and it still catches people, because most of us learned integer division somewhere else first.
Table of contents
- The three operators
- Negative numbers, and why -7 // 2 is -4
- Division by zero, and floats that do not raise
- Float division is inexact, and when that matters
- How this fits the rest of the stack
- FAQ
The three operators
7 / 2 # 3.5 -- true division, always a float
7 // 2 # 3 -- floor division
7 % 2 # 1 -- remainder
8 / 2 # 4.0 -- still a float, even when it divides evenly
8 // 2 # 4 -- int
divmod(7, 2) # (3, 1) -- quotient and remainder in one call
The key detail in that first block is 8 / 2 returning 4.0. True division always produces a float, regardless of whether the result is whole. That matters where the type is load-bearing:
items = [1, 2, 3, 4, 5, 6]
mid = len(items) / 2 # 3.0 -- a float
items[:mid] # TypeError: slice indices must be integers
mid = len(items) // 2 # 3 -- an int
items[:mid] # works
This is the single most common division error in Python, and it is a Python 3 change: in Python 2, / between two integers was floor division. Old tutorials and old Stack Overflow answers still reflect that, which is why the mistake persists.
For an index, a count, or anything used as a repeat quantity, use //.
Negative numbers, and why -7 // 2 is -4
Here is the behaviour that differs from most other languages:
# Python
-7 // 2 # -4
-7 % 2 # 1
# C, Java, JavaScript, Go
# -7 / 2 -> -3 (truncation toward zero)
# -7 % 2 -> -1
Python floors — rounds toward negative infinity — rather than truncating toward zero. So -3.5 becomes -4, not -3.
The payoff is that the modulo always has the sign of the divisor, which is what you almost always want:
# Wrapping an index around a list of 5, in Python
[-7 % 5, -1 % 5, 3 % 5] # [3, 4, 3] -- all valid indices
# The same in a truncating language gives -2 and -1: not valid indices
That property makes circular buffers, clock arithmetic and hue calculations work without a guard clause. In a truncating language you write ((n % m) + m) % m to get the same result, which is a well-known workaround precisely because the default is inconvenient.
The invariant holds throughout: (a // b) * b + (a % b) == a for any integers with b non-zero.
When you genuinely want truncation toward zero, be explicit:
import math
math.trunc(-7 / 2) # -3
int(-7 / 2) # -3 -- int() truncates
-7 // 2 # -4
Note that int() on a float truncates rather than rounds, which is its own source of off-by-one bugs when applied to a value like 2.9999999999999996.
Division by zero, and floats that do not raise
Integer and float division by zero raises:
1 / 0 # ZeroDivisionError: division by zero
1 // 0 # ZeroDivisionError: integer division or modulo by zero
1.0 / 0.0 # ZeroDivisionError: float division by zero
Which is good — a loud failure at the point of the mistake. The catch is that this is not universal. NumPy follows IEEE 754 and produces infinity with a warning instead:
import numpy as np
np.array([1.0]) / np.array([0.0])
# RuntimeWarning: divide by zero encountered
# array([inf])
So a calculation that would have raised on plain Python floats propagates inf or nan silently through a NumPy pipeline, and you find out much later when a result is meaningless. If that matters, make it raise:
np.seterr(divide='raise', invalid='raise')
The general guard is a check rather than an exception handler, since the check documents the intent:
rate = successes / total if total else 0.0
Float division is inexact, and when that matters
The familiar demonstration:
0.1 + 0.2 # 0.30000000000000004
0.1 + 0.2 == 0.3 # False
1 / 3 * 3 # 1.0 -- happens to work
0.1 * 3 # 0.30000000000000004
This is binary floating point, not a Python quirk — the same arithmetic in JavaScript, Java or C gives identical results. Some decimal fractions have no exact binary representation, so a small error accumulates.
For measurements and statistics, that error is irrelevant. For money, it is a defect. Use Decimal:
from decimal import Decimal, getcontext, ROUND_HALF_UP
getcontext().rounding = ROUND_HALF_UP
price = Decimal("19.99")
qty = Decimal("3")
total = price * qty # Decimal('59.97') -- exact
share = (total / Decimal("7")).quantize(Decimal("0.01"))
# Decimal('8.57')
Construct from strings, not floats. Decimal(0.1) inherits the float’s error and gives you a very long inexact decimal; Decimal("0.1") is exactly one tenth.
And for exact fractions where you are not working in a fixed number of decimal places, Fraction keeps a numerator and denominator:
from fractions import Fraction
Fraction(1, 3) + Fraction(1, 6) # Fraction(1, 2) -- exact
Comparing floats deserves a rule of its own: never use ==. Use math.isclose(a, b), which handles both relative and absolute tolerance sensibly.
How this fits the rest of the stack
Division is one of those topics where the surprises are small individually and expensive in aggregate — a float where an integer was needed, a negative floor where truncation was expected, a rounding error in a total that has to balance.
The expensive version is the last one, because it usually surfaces as a reconciliation problem long after the code shipped, and answering it means seeing exactly what the running service computed. On RunxBuild, a Python service deploys from your GitHub repository with build and runtime logs in the same place, environment variables as service settings, and rollback to the previous deploy when a release goes wrong. Managed MySQL and Postgres sit beside it on private networking with backups, so the numbers a calculation produced and the numbers stored can be compared rather than guessed at. To see what a service and its database come to, the RunxBuild hosting calculator lists them as separate line items.
Useful related references:
- Python Not Equal: != vs is not, and Why the Difference Bites
- Python Integer Division: Why // Floors and Why -7 // 2 Is -4
- Python for Websites: Where It Fits and Where It Does Not
- Python services on RunxBuild
FAQ
What is the difference between / and // in Python?
A single slash is true division and always returns a float, even when the division is exact — 8 / 2 is 4.0. A double slash is floor division and returns an integer when both operands are integers. Use // for indices, counts and anything that must be an int.
Why is -7 // 2 equal to -4 in Python?
Because floor division rounds toward negative infinity rather than truncating toward zero, so -3.5 becomes -4. Most other languages truncate and give -3. The benefit is that the modulo always takes the sign of the divisor, so negative indices wrap correctly without a guard.
How do I truncate toward zero instead?
Use math.trunc(a / b) or int(a / b), both of which cut toward zero rather than flooring. Be aware that going through a float loses precision on very large integers, so for exact integer arithmetic prefer explicit handling of the sign over a float round trip.
Why does 0.1 + 0.2 not equal 0.3?
Because binary floating point cannot represent every decimal fraction exactly, so a tiny error accumulates. This is not specific to Python — the same arithmetic gives the same result in JavaScript, Java and C. Use math.isclose to compare floats, and Decimal for money.
What should I use for currency calculations?
Decimal, constructed from strings rather than floats — Decimal("19.99"), not Decimal(19.99), since the latter inherits the float’s rounding error. Set the rounding mode explicitly and use quantize to fix the number of decimal places at the point where the value is finalised.