Skip to content

17. Integers, Floats, and Complex Numbers

PyLongObject digit array for arbitrary precision, PyFloatObject IEEE 754 storage, and complex number layout.

Python’s numeric objects are ordinary objects with specialized implementations. They participate in the same object model as lists, dictionaries, functions, classes, and modules: each value has an object header, a type pointer, reference counting behavior, and type slots for operations.

The main built-in numeric types are:

TypePython nameMain representation
IntegerintArbitrary-precision integer
BooleanboolSingleton subclass of int
Floating pointfloatC double
ComplexcomplexPair of C doubles

These types look simple at the Python level, but each carries important runtime tradeoffs.

17.1 Numeric Objects Are Objects

A Python integer is not normally stored as a raw CPU integer in Python variables.

x = 42

At the CPython level, x refers to a Python integer object.

Conceptually:

x ---> PyLongObject
          object header
          integer payload

The same is true for floats and complex numbers.

a = 1.5
b = 1 + 2j

Conceptually:

a ---> PyFloatObject
          object header
          double value

b ---> PyComplexObject
          object header
          double real
          double imag

The object model gives numbers normal Python behavior:

print(type(42))
print((42).__class__)
print((42).bit_length())

Numeric values have methods, participate in protocols, and are passed by reference like all other objects.

17.2 Integer Objects

Python int is arbitrary precision.

x = 10 ** 100
print(x)

The value does not overflow at 32 or 64 bits. CPython grows the internal representation as needed.

The C-level type is commonly called PyLongObject.

Conceptually:

PyLongObject
    PyVarObject header
        ob_size
    digit array

The ob_size field encodes both the number of internal digits and the sign. The digit array stores the absolute value in a large base.

This is why Python can compute:

x = 2 ** 1000
y = x * x
print(y)

without integer overflow.

The cost is that large integers require more memory and more CPU time than machine integers.

17.3 Integer Digit Storage

CPython stores an integer as multiple internal digits.

A simplified model:

value = sign * (d0 + d1 * base + d2 * base^2 + ...)

For small integers, only one digit may be needed.

For large integers, many digits are needed.

Conceptually:

42
    sign = positive
    digits = [42]

2**1000
    sign = positive
    digits = [d0, d1, d2, ...]

This representation is similar to how humans write decimal numbers as digits, except CPython uses a much larger internal base for efficiency.

Arithmetic operations work over these digit arrays.

small int addition
    cheap

large int addition
    cost grows with number of digits

large int multiplication
    cost grows more quickly, with specialized algorithms for large cases

17.4 Integer Sign

The sign is represented through the size metadata, not as a separate Python object.

Conceptually:

ob_size > 0
    positive integer

ob_size == 0
    zero

ob_size < 0
    negative integer

The digit array stores magnitude.

Example:

123
    ob_size = positive digit count
    digits = magnitude

-123
    ob_size = negative digit count
    digits = magnitude

This representation keeps the object compact.

17.5 Integer Immutability

Integers are immutable.

x = 10
x += 1

This does not mutate the integer object 10. It creates or retrieves another integer object and rebinds x.

Conceptually:

x ---> int object 10

x += 1

x ---> int object 11

This behavior is visible when objects are shared:

a = 10
b = a

a += 1

print(a)    # 11
print(b)    # 10

b still refers to the original integer object.

17.6 Small Integer Reuse

CPython reuses some small integer objects.

a = 1
b = 1

Both names may refer to the same runtime-owned integer object.

This is an optimization. It reduces allocation for common values.

Do not rely on identity for integer value comparison.

Correct:

if x == 1:
    ...

Incorrect:

if x is 1:
    ...

is tests object identity. == tests numeric equality.

17.7 Boolean Objects

bool is a subclass of int.

print(isinstance(True, int))   # True
print(True + True)             # 2

There are exactly two boolean singleton objects:

True
False

The type relationship is:

bool
    subclass of int

This exists for historical and practical compatibility. Boolean values work in numeric contexts, but code should use them for truth values.

if ready:
    ...

Rather than:

if ready == True:
    ...

At the C level, extension code commonly returns booleans with:

Py_RETURN_TRUE;
Py_RETURN_FALSE;

These macros return owned references to the singleton objects.

17.8 Truth Value Testing

Numeric objects participate in truth value testing.

bool(0)        # False
bool(1)        # True
bool(-1)       # True
bool(0.0)      # False
bool(0j)       # False

The rule for numeric types is simple:

zero value
    false

nonzero value
    true

Truth testing uses type protocol machinery. At the C level, numeric types provide behavior through slots such as boolean conversion.

17.9 Integer Operations

Common integer operations include:

a + b
a - b
a * b
a // b
a % b
a ** b
a << n
a >> n
a & b
a | b
a ^ b
~a

These map to numeric slots.

Conceptually:

PyLong_Type
    nb_add
    nb_subtract
    nb_multiply
    nb_floor_divide
    nb_remainder
    nb_power
    nb_lshift
    nb_rshift
    nb_and
    nb_or
    nb_xor
    nb_invert

Each operation receives Python objects and returns a Python object.

Even when the result fits in a machine word, the result is a Python object.

17.10 Integer Division

Python has two main division operators.

a / b     # true division
a // b    # floor division

For integers:

print(5 / 2)     # 2.5
print(5 // 2)    # 2

/ returns a float for ordinary integers.

// returns the floor result.

For negative values:

print(-5 // 2)   # -3
print(-5 % 2)    # 1

The identity holds:

a == (a // b) * b + (a % b)

with % carrying the sign convention required by Python semantics.

17.11 Bit Operations

Integers support bit operations as though represented in two’s complement with infinite sign extension.

x & y
x | y
x ^ y
~x
x << n
x >> n

Example:

print(5 & 3)     # 1
print(5 | 3)     # 7
print(5 ^ 3)     # 6
print(~5)        # -6

The result of ~x follows:

~x == -x - 1

This model is different from fixed-width C integer behavior because Python integers do not have a fixed bit width.

17.12 Integer Methods

Integer methods expose useful properties of the internal value.

x = 1024

print(x.bit_length())
print(x.bit_count())

bit_length returns the number of bits needed to represent the absolute value.

print((0).bit_length())     # 0
print((1).bit_length())     # 1
print((2).bit_length())     # 2
print((3).bit_length())     # 2

bit_count returns the population count of the absolute value.

print((7).bit_count())      # 3

These methods often map efficiently to internal digit operations and CPU intrinsics where available.

17.13 Integer Conversion to Bytes

Integers can be converted to and from byte sequences.

x = 1024

data = x.to_bytes(2, byteorder="big")
print(data)

again = int.from_bytes(data, byteorder="big")
print(again)

This matters for:

binary protocols
cryptography
file formats
network byte order
serialization

Endianness must be explicit.

x.to_bytes(4, "big")
x.to_bytes(4, "little")

The same integer produces different byte layouts depending on byte order.

17.14 Float Objects

Python float is usually a wrapper around a C double.

Conceptually:

PyFloatObject
    PyObject header
    double value

A float object is fixed-size.

x = 1.5
y = 2.25
print(x + y)

Float operations are implemented through numeric slots and platform floating-point operations.

The key point: floats are approximate binary floating-point numbers.

17.15 Binary Floating-Point

Decimal fractions often cannot be represented exactly in binary.

print(0.1 + 0.2)

This commonly prints:

0.30000000000000004

The issue is representation. 0.1 has no finite binary expansion, just as 1/3 has no finite decimal expansion.

So the stored value is the nearest representable binary float.

This affects equality:

print(0.1 + 0.2 == 0.3)    # False

Use tolerance-based comparison for approximate numeric work:

import math

math.isclose(0.1 + 0.2, 0.3)

17.16 Float Special Values

Floats include special values:

inf = float("inf")
nan = float("nan")
neg_inf = float("-inf")

Infinity behaves as expected in many comparisons:

print(inf > 1e308)     # True

NaN is unusual:

nan = float("nan")

print(nan == nan)      # False
print(nan != nan)      # True

NaN means “not a number.” It does not compare equal to itself.

This behavior follows floating-point rules, not ordinary object identity logic.

17.17 Float Hashing and Equality

Numeric types coordinate equality and hashing.

print(1 == 1.0)          # True
print(hash(1) == hash(1.0))

If two numeric objects compare equal, they must have the same hash.

This allows dictionaries to behave correctly:

d = {}
d[1] = "int"
d[1.0] = "float"

print(d)

The second assignment updates the same key position because 1 == 1.0.

This cross-type numeric equality is convenient, but it can surprise users who expect type-distinct keys.

17.18 Float Conversion

Conversions:

float(1)
int(1.9)
round(1.9)

int truncates toward zero:

print(int(1.9))     # 1
print(int(-1.9))    # -1

round follows Python’s rounding rules:

print(round(2.5))
print(round(3.5))

Floating-point to integer conversion can lose information if the float does not exactly represent the intended value.

For decimal financial arithmetic, use decimal.Decimal rather than binary float.

17.19 Complex Objects

Python complex stores two floating-point values:

PyComplexObject
    PyObject header
    double real
    double imag

Example:

z = 3 + 4j

print(z.real)
print(z.imag)

The real and imaginary parts are floats.

Complex numbers support arithmetic:

a = 1 + 2j
b = 3 + 4j

print(a + b)
print(a * b)

They do not support ordering:

(1 + 2j) < (3 + 4j)    # TypeError

There is no natural total ordering for complex numbers in Python’s numeric model.

17.20 Complex Arithmetic

Complex multiplication follows:

(a + bi)(c + di) = (ac - bd) + (ad + bc)i

Python handles this inside the complex type implementation.

Example:

a = 1 + 2j
b = 3 + 4j

print(a * b)      # (-5+10j)

Division and powers are also supported.

For advanced numerical work, the cmath module provides complex-aware mathematical functions.

import cmath

print(cmath.sqrt(-1))

17.21 Numeric Tower

Python’s numeric model has a conceptual hierarchy:

numbers.Number
    numbers.Complex
        numbers.Real
            numbers.Rational
                numbers.Integral

Built-in types roughly fit as:

TypeConceptual role
complexComplex
floatReal
intIntegral
boolIntegral subclass in practice

The numbers module provides abstract base classes for numeric protocols.

Most ordinary CPython operations use concrete type slots rather than abstract base class dispatch, but the hierarchy helps define expected behavior.

17.22 Mixed Numeric Operations

Mixed numeric operations follow coercion and dispatch rules.

print(1 + 2.5)       # 3.5
print(1 + 2j)        # (1+2j)
print(1.5 + 2j)      # (3.5+2j)

Typical widening direction:

int -> float -> complex

This is a conceptual model. The actual implementation uses binary operation dispatch between operand types.

For user-defined numeric classes, returning NotImplemented lets the other operand try reflected behavior.

class N:
    def __add__(self, other):
        return NotImplemented

17.23 __index__

__index__ means an object can be interpreted as an exact integer for indexing and slicing.

class Index:
    def __index__(self):
        return 2

xs = [10, 20, 30, 40]
print(xs[Index()])

This is stricter than __int__.

Use cases include:

list indexes
slice bounds
range arguments
bit operations
low-level integer contexts

At the C level, this corresponds to integer-index protocol behavior.

17.24 __int__, __float__, and __complex__

Conversion methods allow custom numeric-like objects to convert to built-in numeric types.

class Value:
    def __int__(self):
        return 10

    def __float__(self):
        return 10.5

    def __complex__(self):
        return 10.5 + 2j

Usage:

v = Value()

print(int(v))
print(float(v))
print(complex(v))

These conversions create built-in numeric objects.

They do not automatically make the custom type fully numeric. Arithmetic methods still need to be implemented separately.

17.25 Numeric Slots

Numeric behavior is implemented through slots.

Conceptual slot table:

PyNumberMethods
    nb_add
    nb_subtract
    nb_multiply
    nb_remainder
    nb_divmod
    nb_power
    nb_negative
    nb_positive
    nb_absolute
    nb_bool
    nb_invert
    nb_lshift
    nb_rshift
    nb_and
    nb_xor
    nb_or
    nb_int
    nb_float
    nb_index
    nb_matrix_multiply
    nb_inplace_add
    ...

Python syntax maps into this table.

SyntaxSlot concept
a + baddition
a - bsubtraction
a * bmultiplication
a @ bmatrix multiplication
-anegation
abs(a)absolute value
bool(a)truth value
int(a)integer conversion
float(a)float conversion
a << bleft shift

This slot table is why built-in and extension numeric types can integrate with Python syntax.

17.26 In-Place Numeric Operations

In-place operators include:

x += y
x -= y
x *= y
x //= y
x **= y

For immutable numbers, in-place operations create a new object and rebind the name.

x = 10
before = id(x)

x += 1
after = id(x)

print(before == after)   # usually False

For mutable numeric-like objects, a type may implement true in-place mutation.

Built-in int, float, and complex are immutable.

17.27 Decimal and Fraction

The standard library provides numeric types outside the core built-ins.

decimal.Decimal provides decimal floating-point arithmetic.

from decimal import Decimal

print(Decimal("0.1") + Decimal("0.2"))

fractions.Fraction provides rational arithmetic.

from fractions import Fraction

print(Fraction(1, 3) + Fraction(1, 6))

These are Python-level library types with their own implementations and tradeoffs.

Use them when their semantics match the problem:

NeedType
General integersint
Approximate scientific numeric workfloat
Complex arithmeticcomplex
Decimal financial arithmeticDecimal
Exact rational arithmeticFraction

17.28 C API: Creating Numbers

Creating an integer:

PyObject *x = PyLong_FromLong(42);
if (x == NULL) {
    return NULL;
}

Creating from a wider C type:

PyObject *x = PyLong_FromLongLong(value);

Creating a float:

PyObject *f = PyFloat_FromDouble(3.14);
if (f == NULL) {
    return NULL;
}

Creating a complex:

PyObject *z = PyComplex_FromDoubles(1.0, 2.0);
if (z == NULL) {
    return NULL;
}

Each returns a new reference. The caller owns it.

17.29 C API: Extracting Numbers

Extracting a C long:

long value = PyLong_AsLong(obj);
if (value == -1 && PyErr_Occurred()) {
    return NULL;
}

The error check matters because -1 can be a valid result.

Extracting a double:

double value = PyFloat_AsDouble(obj);
if (value == -1.0 && PyErr_Occurred()) {
    return NULL;
}

Again, check the exception state.

For integer-like contexts, use index conversion APIs when exact integer semantics are required.

17.30 Overflow at C Boundaries

Python integers are arbitrary precision. C integers are fixed-width.

This conversion can fail:

long value = PyLong_AsLong(obj);

if obj is too large for C long.

Python-level example:

x = 10 ** 100

This value is valid as a Python int, but may not fit in any C integer type.

C extension code must handle overflow errors.

A common mistake is assuming Python int always fits into int, long, or size_t.

17.31 Performance Notes

Numeric performance depends on representation.

Small integer arithmetic is optimized but still operates on Python objects.

A tight loop:

total = 0

for i in range(10_000_000):
    total += i

does many Python-level operations:

load total
load i
integer addition
create or retrieve result integer
store total
loop control

This is much slower than equivalent C over raw machine integers.

For large numeric arrays, use specialized libraries that store raw values compactly, such as array, memoryview, or NumPy.

Python’s built-in numeric types optimize scalar semantics, not dense numeric vector computation.

17.32 Memory Notes

A list of integers stores references to integer objects.

xs = [1, 2, 3, 4]

Conceptually:

list
    [ptr][ptr][ptr][ptr]
      |    |    |    |
      v    v    v    v
     int  int  int  int

An array stores raw values:

from array import array

xs = array("i", [1, 2, 3, 4])

Conceptually:

array
    [int][int][int][int]

This is why arrays are more memory-efficient for large homogeneous numeric data.

17.33 Common Numeric Pitfalls

PitfallExampleBetter approach
Using is for numbersx is 1000x == 1000
Expecting decimal exactness from float0.1 + 0.2 == 0.3math.isclose or Decimal
Persisting hash()hash(x) as stable IDhashlib
Assuming Python int fits C longPyLong_AsLong uncheckedCheck overflow
Using list for dense numeric arrays[0] * n for huge numeric dataarray or NumPy
Comparing NaN normallynan == nanUse math.isnan
Ignoring integer growth costhuge powers in hot pathConsider algorithmic bounds

17.34 Mental Model

Use this model:

int
    immutable arbitrary-precision integer
    variable-size digit array
    no fixed overflow
    cost grows with magnitude

bool
    singleton subclass of int
    truth-value type
    values are True and False

float
    immutable wrapper around C double
    approximate binary floating-point
    supports inf and nan

complex
    immutable pair of doubles
    real and imaginary parts
    arithmetic supported
    ordering unsupported

At the CPython level:

numeric operation
    dispatch through type slots
    operate on concrete numeric representation
    allocate or return result object
    manage references

17.35 Summary

CPython implements integers, floats, and complex numbers as Python objects with specialized C layouts. int uses arbitrary-precision storage, so it avoids fixed-width overflow but pays costs that grow with value size. float wraps a C double and inherits binary floating-point behavior. complex stores two doubles and supports complex arithmetic.

These numeric types are immutable. Operations create result objects rather than mutating operands. Their behavior is integrated through numeric slots, conversion protocols, hashing rules, and C API functions.