Almost every program touches numbers somewhere — counting items, calculating totals, measuring something. Python data types for numbers cover more ground than you might expect, from simple whole numbers all the way to complex numbers used in engineering and scientific work. This article walks through all three: int, float, and complex.
Introduction: Python's Three Numeric Types
Because Python is dynamically typed, you never declare a number's type up front — you just assign a value, and Python figures out which numeric type it belongs to:
whole = 10
decimal = 10.5
scientific = 3 + 4j
If you're ever unsure what type a value is, the built-in type() function tells you directly:
print(type(whole)) # <class 'int'>
print(type(decimal)) # <class 'float'>
print(type(scientific)) # <class 'complex'>
At a high level, these three types map to different kinds of real-world quantities: whole numbers (counting people, items, iterations), decimals (prices, measurements, averages), and complex numbers (signal processing, electrical engineering, certain kinds of scientific computing). The rest of this article covers each one in detail.
Integers (int) in Python
An integer is a whole number — positive, negative, or zero — with no fractional component.
count = 42
temperature = -5
zero = 0
Unlimited precision
One thing that sets Python apart from many other languages: integers have no fixed size limit, and no overflow. In languages like C or Java, integers are constrained to a fixed number of bits, and exceeding that limit causes unexpected behavior. Python handles arbitrarily large integers automatically:
big_number = 2 ** 200
print(big_number)
# Prints the full number — no overflow, no special handling required
You don't need to think about integer size limits in everyday Python code; the interpreter manages it for you.
Alternate representations
Python lets you write integers in bases other than decimal, using a prefix:
binary_value = 0b1010 # binary — evaluates to 10
octal_value = 0o12 # octal — evaluates to 10
hex_value = 0xA # hexadecimal — evaluates to 10
print(binary_value, octal_value, hex_value)
# 10 10 10
These are especially useful when working with low-level data, bitwise operations, or color codes (hex is common there).
Underscores for readability
For large numbers, Python allows underscores as visual separators — they're purely cosmetic and don't affect the value:
population = 1_000_000
print(population) # 1000000
This makes it much easier to read a number's magnitude at a glance, the same way commas work in written numbers.
Floating-Point Numbers (float)
A float is any number with a decimal point — used whenever you need a fractional or real-world value that isn't a clean whole number.
price = 19.99
temperature = 98.6
pi_approx = 3.14159
Scientific notation
For very large or very small numbers, Python supports scientific notation using e or E:
small_number = 1e-2 # 0.01
large_number = 2.5e6 # 2500000.0
print(small_number, large_number)
The number after e represents the power of 10 to multiply by — negative for very small values, positive for very large ones.
The precision caveat
Here's something that catches a lot of beginners off guard: floats aren't always perfectly precise. This isn't a Python-specific bug — it's a consequence of how floating-point numbers are represented in binary at the hardware level, and it affects nearly every programming language.
print(0.1 + 0.2)
# 0.30000000000000004 — not exactly 0.3
For most everyday calculations, this tiny imprecision doesn't matter. But for anything involving money or other situations demanding exact decimal accuracy, it can cause real problems — rounding errors that compound over many transactions. For those cases, Python's built-in decimal module provides exact decimal arithmetic:
from decimal import Decimal
price = Decimal("19.99")
tax = Decimal("1.60")
print(price + tax) # 21.59 — exact, no floating-point drift
Worth knowing about even if you don't need it on day one.
Complex Numbers (complex)
Complex numbers might feel like the odd one out in this list, but they're built directly into Python's core numeric types — no imports required.
Structure and notation
A complex number has a real part and an imaginary part, written with a j (or J) suffix on the imaginary component:
z = 3 + 4j
print(z) # (3+4j)
print(type(z)) # <class 'complex'>
Creating complex numbers
You can write them as a literal, like above, or construct them explicitly with the complex() function:
z1 = 3 + 4j
z2 = complex(3, 4) # same value as z1
print(z1 == z2) # True
Accessing the parts
Once you have a complex number, you can pull out its components:
z = 3 + 4j
print(z.real) # 3.0
print(z.imag) # 4.0
print(z.conjugate()) # (3-4j)
.real and .imag return the respective components as floats, and .conjugate() returns a new complex number with the sign of the imaginary part flipped.
Where complex numbers actually get used
If this feels abstract, that's fair — most everyday scripts never touch complex numbers. But they're genuinely essential in specific fields: electrical engineering (analyzing alternating current circuits), signal processing (Fourier transforms), and various areas of scientific computing and physics simulations. Libraries like NumPy lean on Python's complex number support heavily for exactly this kind of work. It's not a type Python added for completeness — it earns its place.
Converting Between Number Types
Real programs frequently need to move between these types, either intentionally or automatically.
Explicit conversion
Python provides built-in functions to explicitly convert between numeric types:
# Convert float to int (truncates, doesn't round)
whole = int(9.8)
print(whole) # 9
# Convert int to float
decimal = float(9)
print(decimal) # 9.0
# Convert to complex
c = complex(5)
print(c) # (5+0j)
Note that int() truncates rather than rounds — int(9.8) gives 9, not 10. If you need rounding behavior, use the built-in round() function instead.
Implicit conversion in mixed expressions
When you combine numbers of different types in a single expression, Python automatically converts them to whichever type can represent both without losing information — this is called implicit type conversion, or type coercion:
result = 5 + 2.5
print(result) # 7.5
print(type(result)) # <class 'float'>
Here, the integer 5 was automatically treated as a float so the addition could produce an accurate result. You don't need to do anything for this to happen — Python handles it silently, following a predictable "widen to the more general type" rule.
Common conversion errors
The most common mistake beginners hit is trying to convert a non-numeric string:
value = int("hello")
# ValueError: invalid literal for int() with base 10: 'hello'
Python can convert numeric strings like int("42"), but it has no idea how to turn "hello" into a number, and raises a ValueError rather than guessing. If you're converting user input, it's worth wrapping the conversion in a try/except block to handle cases where the input isn't actually numeric.
A related gotcha: floor division vs. true division
While you're thinking about numeric behavior, it's worth knowing the difference between Python's two division operators:
print(7 / 2) # 3.5 — true division, always returns a float
print(7 // 2) # 3 — floor division, rounds down to the nearest whole number
/ always gives you a precise result as a float, even when dividing two integers evenly. // discards the remainder and rounds down. Mixing these up is an easy mistake, especially for anyone coming from a language where / behaves differently on integers.