PYTHON / VARIABLES AND DATA TYPES
Numbers: int, float, and complex
Tell int, float, and complex apart, predict which type an expression returns, and know why float results are approximate while int results are exact.
What you will learn
- Write 5, 5.0, or 5j to choose int, float, or complex for the same digit
- Explain why 0.1 + 0.2 prints 0.30000000000000004 and use math.isclose instead of ==
- Predict result types: / always floats, // and % keep ints, int + float promotes
- Read .real, .imag and .conjugate() off a complex, and know it has no < ordering
Understanding Numbers: int, float, and complex
Python has exactly three built-in numeric types, and the literal you type decides which one you get: 42 is an int, 42.0 is a float, and 42j is a complex. An int stores a whole number exactly, with as many digits as memory allows, so there is no 32-bit or 64-bit ceiling and 2 ** 70 is an ordinary value rather than an overflow. A float is a 64-bit IEEE 754 double: fixed size, roughly 15 to 17 significant decimal digits, and therefore only an approximation of most real numbers. A complex is two floats glued together, a real part and an imaginary part, each carrying exactly the same limits as any other float.
Float arithmetic surprises people because a double stores a binary fraction, a sum of powers of two. One tenth has no finite binary expansion, the same way one third has no finite decimal expansion, so 0.1 is stored as the nearest double, which is very slightly more than a tenth. Adding two such approximations produces a value that Python rounds again to the nearest double, which is why 0.1 + 0.2 prints 0.30000000000000004. The comparison itself is not fuzzy: == compares the stored values faithfully, and those stored values genuinely differ from the double stored for 0.3.
When types mix, Python widens in one direction only: int becomes float, float becomes complex, and never the reverse. That is why true division with / always yields a float, even for 8 / 4, while // and % on two ints stay int, and why a complex value cannot be ordered with < at all. Pick the type from the problem: int for counting, indexing, and money held in cents; float for measured quantities where a tiny relative error is harmless; decimal.Decimal or fractions.Fraction when exact decimal or rational arithmetic is the point of the program.
count = 42
ratio = 0.75
signal = 3 + 4j
print(count, type(count).__name__)
print(ratio, type(ratio).__name__)
print(signal, type(signal).__name__)
print(2 ** 70)
print(0.1 + 0.2)
print(signal.real, signal.imag, abs(signal))int is exact and unbounded, float is a fixed-width binary approximation, and complex is a pair of floats, so the numeric type you pick decides whether your arithmetic is exact.
Worked examples
Which operator gives which type
Shows how the arithmetic operator, not the operand sizes, decides whether you get an int or a float.
print(7 / 2, type(7 / 2).__name__)
print(8 / 4, type(8 / 4).__name__)
print(7 // 2, -7 // 2)
print(7 % 2, -7 % 2)
print(2 + 3.0, type(2 + 3.0).__name__)Example explained
Line 1/ is true division and always builds a float, so 8 / 4 is 2.0 and never 2.
Line 2// floors toward negative infinity, which is why -7 // 2 is -4 rather than -3.
Line 3% takes the sign of the divisor, so -7 % 2 is 1 and (-7 // 2) * 2 + (-7 % 2) still equals -7.
Line 4Mixing an int with a float promotes the int, so 2 + 3.0 comes back as a float.
Watching a float drift
Adds 0.1 ten times to show accumulated rounding error and the right way to test the result.
import math
total = 0.0
for _ in range(10):
total += 0.1
print(total)
print(total == 1.0)
print(f"{total:.20f}")
print(math.isclose(total, 1.0))Example explained
Line 1Each += 0.1 rounds the running sum to the nearest double, and ten roundings land one step below 1.0.
Line 2print shows the shortest text that reads back as the same double, which hides the size of the error.
Line 3Formatting with .20f exposes the value actually stored: 0.99999999999999988898.
Line 4math.isclose compares with a relative tolerance (1e-09 by default), which is the correct test for measured float results.
Complex numbers in practice
Uses attributes, multiplication, and cmath to work with values that have an imaginary part.
import cmath
z = 3 + 4j
print(z.real, z.imag, z.conjugate())
print((1 + 2j) * (3 - 1j))
print(cmath.sqrt(-4))
print(complex(2, 0) == 2)Example explained
Line 1.real and .imag are floats even though the literal was written with whole numbers.
Line 2Multiplication follows (a+bi)(c+di) with j*j == -1, so 2j * -1j contributes +2 to the real part.
Line 3math.sqrt(-4) raises ValueError, while cmath.sqrt returns the complex root 2j.
Line 4A complex compares equal to an int or float when its imaginary part is zero, but it cannot be ordered.
Important notes
float has a finite range: the literal 1e309 silently evaluates to inf, and float(10 ** 400) raises OverflowError, even though the int 10 ** 400 is perfectly fine.
complex supports == but not <; 1j < 2j raises TypeError, so sort by abs(z) when you need an order.
Common mistakes
Testing floats with ==, as in if 0.1 + 0.2 == 0.3: the branch never runs because the stored sum is 0.30000000000000004.
Using / where a whole number is required: items[len(items) / 2] raises TypeError: list indices must be integers or slices, not float, because / returned a float; // is what you wanted.
Writing 3 + 4i or a bare j for an imaginary part: 4i is a SyntaxError (invalid decimal literal) and j alone is a NameError, since Python needs a digit before the suffix, as in 4j or 1j.
Try it yourself
Change, predict, then run
Assign n = 2 ** 53, then print n + 1 and also print float(n) + 1 == float(n). Add a comment explaining which type could represent that odd number and why the other could not.
Open the Python workspaceCheck your understanding
Why does 0.1 + 0.2 == 0.3 evaluate to False in Python?
- Python compares floats by identity rather than by value, so two separate float objects are never equal.
- 0.3 is stored exactly, but the sum is truncated to 15 significant digits before the comparison happens.
- 0.1, 0.2, and 0.3 are each stored as the nearest binary fraction, and the rounded sum is a different double from the one stored for 0.3.
- Floating-point addition deliberately rounds upward so that accumulated results are never exactly equal.
Show answer
== on floats is an exact comparison of the stored 64-bit values; the inequality comes from representation, not from the comparison. Option 2 is tempting but wrong on both counts: 0.3 has no exact binary representation either, and nothing truncates results to 15 digits — that figure only describes how many decimal digits survive a round trip through a double.