Python

Python Number Types Explained: int, float, and complex

Python has three numeric types: int (unlimited whole numbers), float (64-bit IEEE-754 decimals), and complex (numbers with a j suffix). Here is when each applies, why 0.1 + 0.2 is not 0.3, and how // and / differ.

By InventiveHQ Team

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

Python's three numeric types int stores whole numbers with arbitrary precision, float stores 64-bit IEEE-754 decimals, and complex stores a real and imaginary part with a j suffix. Three numeric types, three trade-offs int whole numbers 42 arbitrary precision 2 ** 10000 no maximum exact float decimals 3.14 64-bit IEEE-754 0.1 + 0.2 rounding error ~15-17 digits complex real + imaginary 3 + 4j j suffix .real / .imag two floats inside signal / EE math

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:

TypeLiteral examplePrecisionThe gotcha
int42, 0xFF, 0b1010, 1_000_000Exact, unboundedNone on range — but // floors toward negative infinity, so -7 // 2 == -4
float3.14, 1.5e6, .564-bit IEEE-754 (~15–17 significant digits)0.1 + 0.2 != 0.3; never compare floats with ==
complex3 + 4j, complex(3, 4)Two IEEE-754 floats (real + imag)Uses j, not i; .real and .imag are always floats
DecimalDecimal("0.1")Exact base-10, configurableNot a built-in — from decimal import Decimal; pass a string, not a float
FractionFraction(1, 3)Exact rationalNot 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)
Advertisement

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
Why 0.1 + 0.2 drifts from 0.3 0.1 and 0.2 have no exact binary form, so their stored values are slightly off and the error surfaces in the sum. Base-10 decimals do not fit in base-2 storage 0.1 0.2 0.3? 0.1000000000000000055… 0.2000000000000000111… 0.30000000000000004 the drift becomes visible in the sum

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

FunctionPurposeExampleResult
int()Truncate to whole number (toward zero)int(3.8)3
float()Convert to floatfloat(5)5.0
complex()Build a complex numbercomplex(2, 3)(2+3j)
abs()Magnitude / absolute valueabs(-5)5
round()Round (banker's rounding)round(2.5)2
type()Check the type at runtimetype(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.

Frequently Asked Questions

What are the three number types in Python?

Python 3 has exactly three built-in numeric types: int for whole numbers of any size, float for real numbers with a decimal point (stored as 64-bit IEEE-754 double precision), and complex for numbers with a real and an imaginary part written with a j suffix, such as 3 + 4j. The old Python 2 long type no longer exists as a separate type — in Python 3 the ordinary int handles arbitrarily large values, so there is no separate long integer anymore.

Why does 0.1 + 0.2 not equal 0.3 in Python?

Because float values are stored in binary using the IEEE-754 double-precision format, and 0.1, 0.2, and 0.3 have no exact finite representation in base 2 — just as 1/3 has no exact finite representation in base 10. Python stores the closest available binary value, and the tiny rounding errors accumulate, so 0.1 + 0.2 evaluates to 0.30000000000000004. This is not a Python bug; every language that uses IEEE-754 floats behaves the same way. Use math.isclose() to compare floats, or the decimal module when you need exact decimal arithmetic.

What is the difference between / and // in Python?

The single slash / is true division and always returns a float, even when the result is a whole number: 6 / 2 returns 3.0, not 3. The double slash // is floor division: it divides and rounds down toward negative infinity, returning an int when both operands are int (7 // 2 is 3) or a float when either operand is a float (7.0 // 2 is 3.0). Note that floor division rounds toward negative infinity, so -7 // 2 is -4, not -3.

Does Python have a maximum integer value?

No. In Python 3 the int type has arbitrary precision, meaning it grows to hold any whole number your memory can store — you can compute 2 ** 10000 directly. This is different from Python 2, where a plain int was capped by the machine word size and larger values automatically became the separate long type. That distinction is gone in Python 3; sys.maxint no longer exists, and sys.maxsize only reports the largest size a container can have, not a limit on integer math.

How do you write a complex number in Python?

Attach a j (or J) suffix to the imaginary part and add it to the real part: z = 3 + 4j. You can also call the constructor complex(3, 4). Access the parts with z.real and z.imag (both returned as floats), and get the complex conjugate with z.conjugate(). Note that Python uses j rather than the mathematician's i because j is the engineering convention for the imaginary unit.

When should I use Decimal or Fraction instead of float?

Use decimal.Decimal when you need exact base-10 arithmetic — money, invoices, tax, anything where 0.30000000000000004 is unacceptable. Decimal represents values exactly as written and lets you control rounding precisely. Use fractions.Fraction when you need exact rational arithmetic, such as 1/3 + 1/3 + 1/3 equalling exactly 1 rather than 0.9999999999999999. Both trade speed for exactness, so keep plain float for scientific and general-purpose math where small rounding error is acceptable.

How do you convert between number types in Python?

Use the constructor functions: int() truncates toward zero (int(3.9) is 3, int(-3.9) is -3), float() adds a decimal point (float(5) is 5.0), and complex() builds a complex value (complex(2, 3) is 2+3j). int() and float() also parse strings, so int("25") is 25 and float("29.99") is 29.99 — but a malformed string raises ValueError, so validate or wrap the call in try/except when converting user input.

Is there a separate long type in Python 3?

No. Python 2 had two integer types — int (machine-word sized) and long (unbounded, written with an L suffix like 100L). Python 3 merged them into a single int type that is unbounded by default, and the L suffix is now a syntax error. If you are reading old code or tutorials that mention long or sys.maxint, that is Python 2 material and does not apply to Python 3.

How do you round a float to a set number of decimal places?

Use round(value, ndigits), for example round(3.14159, 2) gives 3.14. Be aware that round() uses banker's rounding (round half to even), so round(2.5) is 2 and round(3.5) is 4. Because the input is still a binary float, some results can look surprising; for guaranteed decimal rounding on money, use decimal.Decimal with an explicit quantize() step instead.