Python 3 has three built-in numeric types: int for whole numbers of any size, float for real numbers with a decimal point, and complex for numbers with a real and imaginary part. An int has arbitrary precision — it grows to hold any whole number your memory allows, so 2 ** 10000 works directly. A float is a 64-bit IEEE-754 double, which is why 0.1 + 0.2 is 0.30000000000000004 rather than 0.3. A complex is written with a j suffix, as in 3 + 4j. When exactness matters — money, tax, invoices — you reach for decimal.Decimal or fractions.Fraction instead of float.
That is the summary an AI overview gives you. The rest of this article is what it can't: why floats round the way they do, how // and / actually differ, when arbitrary-precision integers quietly save you, and the specific gotchas that trip up every Python developer at least once. First, the whole system at a glance.
The three types at a glance
Here is the same information as a lookup table — type, how you write a literal, how exact it is, and the one gotcha that bites people:
| Type | Literal example | Precision | The gotcha |
|---|---|---|---|
int | 42, 0xFF, 0b1010, 1_000_000 | Exact, unbounded | None on range — but // floors toward negative infinity, so -7 // 2 == -4 |
float | 3.14, 1.5e6, .5 | 64-bit IEEE-754 (~15–17 significant digits) | 0.1 + 0.2 != 0.3; never compare floats with == |
complex | 3 + 4j, complex(3, 4) | Two IEEE-754 floats (real + imag) | Uses j, not i; .real and .imag are always floats |
Decimal | Decimal("0.1") | Exact base-10, configurable | Not a built-in — from decimal import Decimal; pass a string, not a float |
Fraction | Fraction(1, 3) | Exact rational | Not a built-in — from fractions import Fraction |
Integers: whole numbers with no ceiling
An int represents a whole number — positive, negative, or zero. The defining feature in Python 3 is arbitrary precision: unlike C, Java, or Python 2, a Python 3 int has no maximum. It grows to fit whatever value you give it, limited only by available memory.
# Arbitrary precision: this just works, no overflow
big = 2 ** 10000
print(len(str(big))) # 3011 (a 3011-digit number)
# Integer literals in other bases
hex_val = 0xFF # 255 (hexadecimal)
octal = 0o17 # 15 (octal)
binary = 0b1010 # 10 (binary)
# Underscores make long literals readable (Python 3.6+)
population = 8_000_000_000
print(population) # 8000000000
There is no long type in Python 3
Older tutorials talk about a separate long type and a sys.maxint constant. That is Python 2 and does not apply. Python 2 had two integer types — int (capped at the machine word size, 2**63 - 1 on 64-bit systems) and long (unbounded, written 100L). Python 3 merged them into a single unbounded int. The L suffix is now a syntax error, and sys.maxint no longer exists. If you need the largest container size, that is sys.maxsize — but it is not a limit on integer arithmetic.
Converting to int
The int() constructor truncates toward zero (it does not round):
print(int(3.9)) # 3 (truncates, does not round)
print(int(-3.9)) # -3 (truncates toward zero, not down)
print(int("25")) # 25 (parses a string)
# int() raises ValueError on a decimal string
# int("3.9") -> ValueError
print(int(float("3.9"))) # 3 (go through float first)
Floating-point: fast, and slightly wrong on purpose
A float represents a real number with a decimal point. Python floats are 64-bit IEEE-754 double-precision values, giving roughly 15 to 17 significant decimal digits of precision. That precision is finite, and the storage is binary — which produces the single most-asked question in Python.
Why 0.1 + 0.2 is not 0.3
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False
This is not a bug. The numbers 0.1, 0.2, and 0.3 cannot be represented exactly in base-2, the same way 1/3 cannot be written exactly in base-10 (0.3333…). Python stores the nearest available binary value, and the tiny errors add up. Every language using IEEE-754 does this. The fix is to never compare floats with ==:
import math
print(math.isclose(0.1 + 0.2, 0.3)) # True
Creating and converting floats
price = 19.99 # decimal point makes it a float
scaled = 1.5e6 # scientific notation -> 1500000.0
whole = float(10) # 10.0 (int -> float)
parsed = float("29.99") # 29.99 (string -> float)
# Special float values
print(float("inf")) # inf
print(float("nan")) # nan (nan != nan is always True)
For money and any exact base-10 requirement, do not use float. Use decimal.Decimal.
Decimal and Fraction for exact math
from decimal import Decimal
# Pass a STRING, not a float — Decimal(0.1) inherits the float's error
print(Decimal("0.1") + Decimal("0.2")) # 0.3 (exact)
from fractions import Fraction
print(Fraction(1, 3) + Fraction(1, 3) + Fraction(1, 3)) # 1 (exact)
Decimal gives exact base-10 arithmetic with controllable rounding — the right tool for currency. Fraction gives exact rational arithmetic. Both are slower than float, so reserve them for when exactness beats speed.
/ versus //: true division versus floor division
This trips up nearly everyone once. In Python 3, / is true division and always returns a float; // is floor division and rounds down toward negative infinity.
print(6 / 2) # 3.0 (always a float, even for whole results)
print(7 // 2) # 3 (int floor division)
print(7.0 // 2) # 3.0 (float in -> float out)
print(-7 // 2) # -4 (floors DOWN, not toward zero)
print(7 % 3) # 1 (modulo pairs with //)
The -7 // 2 == -4 result surprises people who expect truncation toward zero. Floor division always rounds toward negative infinity, which keeps the identity a == (a // b) * b + (a % b) true.
Complex numbers: the j suffix
A complex number holds a real and an imaginary part, written a + bj. Python uses j (the electrical-engineering convention) rather than the mathematician's i. Internally, both parts are stored as float.
z1 = 3 + 4j # direct notation
z2 = complex(5, 7) # constructor -> (5+7j)
z3 = complex(10) # (10+0j)
print(z1.real) # 3.0 (always a float)
print(z1.imag) # 4.0
print(z1.conjugate()) # (3-4j)
print(abs(3 + 4j)) # 5.0 (magnitude: sqrt(3**2 + 4**2))
# Arithmetic works as expected
print((3 + 4j) + (1 + 2j)) # (4+6j)
print((3 + 4j) * (1 + 2j)) # (-5+10j)
Complex numbers show up in signal processing, electrical engineering, and the cmath module (complex-aware versions of math functions). If you are not doing that kind of math, you will rarely touch this type.
Type conversion cheat sheet
| Function | Purpose | Example | Result |
|---|---|---|---|
int() | Truncate to whole number (toward zero) | int(3.8) | 3 |
float() | Convert to float | float(5) | 5.0 |
complex() | Build a complex number | complex(2, 3) | (2+3j) |
abs() | Magnitude / absolute value | abs(-5) | 5 |
round() | Round (banker's rounding) | round(2.5) | 2 |
type() | Check the type at runtime | type(5.0) | <class 'float'> |
Two footguns in that table: int() truncates rather than rounds, and round() uses banker's rounding (round half to even), so round(2.5) is 2 but round(3.5) is 4.
price = "29.99"
quantity = "5"
total = float(price) * int(quantity)
print(f"Total: ${total}") # Total: $149.95
print(type(total)) # <class 'float'>
Always validate before converting user input
int() and float() raise ValueError on malformed strings. Wrap conversions of untrusted input in try/except:
def to_int(text, default=0):
try:
return int(text)
except ValueError:
return default
print(to_int("25")) # 25
print(to_int("oops")) # 0
The bottom line
Reach for int for anything countable — it never overflows in Python 3. Use float for general and scientific math, but never trust its last few digits and never compare with ==. Escalate to Decimal or Fraction the moment exactness matters, especially money. Keep complex in your back pocket for engineering and signal work. And remember the two division operators do different jobs: / always hands you a float, // floors toward negative infinity.