# 17. Integers, Floats, and Complex Numbers

# 17. Integers, Floats, and Complex Numbers

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:

| Type           | Python name | Main representation         |
| -------------- | ----------- | --------------------------- |
| Integer        | `int`       | Arbitrary-precision integer |
| Boolean        | `bool`      | Singleton subclass of `int` |
| Floating point | `float`     | C double                    |
| Complex        | `complex`   | Pair 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.

```python
x = 42
```

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

Conceptually:

```text
x ---> PyLongObject
          object header
          integer payload
```

The same is true for floats and complex numbers.

```python
a = 1.5
b = 1 + 2j
```

Conceptually:

```text
a ---> PyFloatObject
          object header
          double value

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

The object model gives numbers normal Python behavior:

```python
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.

```python
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:

```text
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:

```python
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:

```text
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:

```text
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.

```text
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:

```text
ob_size > 0
    positive integer

ob_size == 0
    zero

ob_size < 0
    negative integer
```

The digit array stores magnitude.

Example:

```text
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.

```python
x = 10
x += 1
```

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

Conceptually:

```text
x ---> int object 10

x += 1

x ---> int object 11
```

This behavior is visible when objects are shared:

```python
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.

```python
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:

```python
if x == 1:
    ...
```

Incorrect:

```python
if x is 1:
    ...
```

`is` tests object identity. `==` tests numeric equality.

## 17.7 Boolean Objects

`bool` is a subclass of `int`.

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

There are exactly two boolean singleton objects:

```python
True
False
```

The type relationship is:

```text
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.

```python
if ready:
    ...
```

Rather than:

```python
if ready == True:
    ...
```

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

```c
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.

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

The rule for numeric types is simple:

```text
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:

```python
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:

```text
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.

```python
a / b     # true division
a // b    # floor division
```

For integers:

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

`/` returns a float for ordinary integers.

`//` returns the floor result.

For negative values:

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

The identity holds:

```text
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.

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

Example:

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

The result of `~x` follows:

```text
~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.

```python
x = 1024

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

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

```python
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.

```python
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.

```python
x = 1024

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

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

This matters for:

```text
binary protocols
cryptography
file formats
network byte order
serialization
```

Endianness must be explicit.

```python
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:

```text
PyFloatObject
    PyObject header
    double value
```

A float object is fixed-size.

```python
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.

```python
print(0.1 + 0.2)
```

This commonly prints:

```text
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:

```python
print(0.1 + 0.2 == 0.3)    # False
```

Use tolerance-based comparison for approximate numeric work:

```python
import math

math.isclose(0.1 + 0.2, 0.3)
```

## 17.16 Float Special Values

Floats include special values:

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

Infinity behaves as expected in many comparisons:

```python
print(inf > 1e308)     # True
```

NaN is unusual:

```python
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.

```python
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:

```python
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:

```python
float(1)
int(1.9)
round(1.9)
```

`int` truncates toward zero:

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

`round` follows Python’s rounding rules:

```python
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:

```text
PyComplexObject
    PyObject header
    double real
    double imag
```

Example:

```python
z = 3 + 4j

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

The real and imaginary parts are floats.

Complex numbers support arithmetic:

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

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

They do not support ordering:

```python
(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:

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

Python handles this inside the complex type implementation.

Example:

```python
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.

```python
import cmath

print(cmath.sqrt(-1))
```

## 17.21 Numeric Tower

Python’s numeric model has a conceptual hierarchy:

```text
numbers.Number
    numbers.Complex
        numbers.Real
            numbers.Rational
                numbers.Integral
```

Built-in types roughly fit as:

| Type      | Conceptual role               |
| --------- | ----------------------------- |
| `complex` | Complex                       |
| `float`   | Real                          |
| `int`     | Integral                      |
| `bool`    | Integral 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.

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

Typical widening direction:

```text
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.

```python
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.

```python
class Index:
    def __index__(self):
        return 2

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

This is stricter than `__int__`.

Use cases include:

```text
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.

```python
class Value:
    def __int__(self):
        return 10

    def __float__(self):
        return 10.5

    def __complex__(self):
        return 10.5 + 2j
```

Usage:

```python
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:

```text
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.

| Syntax     | Slot concept          |
| ---------- | --------------------- |
| `a + b`    | addition              |
| `a - b`    | subtraction           |
| `a * b`    | multiplication        |
| `a @ b`    | matrix multiplication |
| `-a`       | negation              |
| `abs(a)`   | absolute value        |
| `bool(a)`  | truth value           |
| `int(a)`   | integer conversion    |
| `float(a)` | float conversion      |
| `a << b`   | left 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:

```python
x += y
x -= y
x *= y
x //= y
x **= y
```

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

```python
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.

```python
from decimal import Decimal

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

`fractions.Fraction` provides rational arithmetic.

```python
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:

| Need                                | Type       |
| ----------------------------------- | ---------- |
| General integers                    | `int`      |
| Approximate scientific numeric work | `float`    |
| Complex arithmetic                  | `complex`  |
| Decimal financial arithmetic        | `Decimal`  |
| Exact rational arithmetic           | `Fraction` |

## 17.28 C API: Creating Numbers

Creating an integer:

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

Creating from a wider C type:

```c
PyObject *x = PyLong_FromLongLong(value);
```

Creating a float:

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

Creating a complex:

```c
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:

```c
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:

```c
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:

```c
long value = PyLong_AsLong(obj);
```

if `obj` is too large for C `long`.

Python-level example:

```python
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:

```python
total = 0

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

does many Python-level operations:

```text
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.

```python
xs = [1, 2, 3, 4]
```

Conceptually:

```text
list
    [ptr][ptr][ptr][ptr]
      |    |    |    |
      v    v    v    v
     int  int  int  int
```

An array stores raw values:

```python
from array import array

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

Conceptually:

```text
array
    [int][int][int][int]
```

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

## 17.33 Common Numeric Pitfalls

| Pitfall                                | Example                         | Better approach             |
| -------------------------------------- | ------------------------------- | --------------------------- |
| Using `is` for numbers                 | `x is 1000`                     | `x == 1000`                 |
| Expecting decimal exactness from float | `0.1 + 0.2 == 0.3`              | `math.isclose` or `Decimal` |
| Persisting `hash()`                    | `hash(x)` as stable ID          | `hashlib`                   |
| Assuming Python int fits C long        | `PyLong_AsLong` unchecked       | Check overflow              |
| Using list for dense numeric arrays    | `[0] * n` for huge numeric data | `array` or NumPy            |
| Comparing NaN normally                 | `nan == nan`                    | Use `math.isnan`            |
| Ignoring integer growth cost           | huge powers in hot path         | Consider algorithmic bounds |

## 17.34 Mental Model

Use this model:

```text
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:

```text
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.

