# Numeric types in Python

By John Lekberg on August 29, 2020.

In this week's post, you will learn about Python's 6 different numeric types, and the numeric tower exposed by the numbers module.

# Python's numeric types

Python has 4 built-in numeric types:

The standard library adds 2 more numeric types:

It's useful to have these different numeric types for different use cases. E.g. rounding error occurs when adding `float` objects, but not when adding `Fraction` objects:

``````.1 + .2 == .3
``````
``````False
``````
``````.1 + .2
``````
``````0.30000000000000004
``````
``````from fractions import Fraction as F

F("0.1") + F("0.2") == F("0.3")
``````
``````True
``````
``````F("0.1") + F("0.2")
``````
``````Fraction(3, 10)
``````

But what happens when different numeric types interact?

• Sometimes it succeeds:

``````F("0.1") + .1
``````
``````0.2
``````
• Sometimes it fails:

``````from decimal import Decimal as D

D("0.1") + .1
``````
``````TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'
``````

So, what are the rules that control how different numeric types interact? In Python, these rules are known as the "numeric tower".

# Python's numeric tower

Python's numeric tower is exposed by the numbers module. (See PEP 3141 for details.)

`numbers` exposes 5 abstract base classes:

These classes form a hierarchy:

``````from numbers import Number, Complex, Real, Rational, Integral

issubclass(Complex, Number)
``````
``````True
``````
``````issubclass(Real, Complex)
``````
``````True
``````
``````issubclass(Rational, Real)
``````
``````True
``````
``````issubclass(Integral, Rational)
``````
``````True
``````

If you want to know how the numeric types fit into this hierarchy, it's most useful to pair each numeric type with the most specific class in the numeric tower:

Numeric towerNumeric types
`Number``Decimal`*
`Complex``complex`
`Real``float`
`Rational``Fraction`
`Integral``int`, `bool`

* Although `Decimal` is "in" the numeric tower (it subclasses `Number`), it doesn't follow the implicit conversion rules used by the other numeric types. `Decimal` interacts with `int`:

``````from decimal import Decimal as D

D(3) + 4
``````
``````Decimal('7')
``````

But, `Decimal` does not interact with `complex`, `float`, or `Fraction`:

``````D(3) + complex(4)
``````
``````TypeError: unsupported operand type(s) for +: 'decimal.Decimal'
and 'complex'
``````
``````D(3) + float(4)
``````
``````TypeError: unsupported operand type(s) for +: 'decimal.Decimal'
and 'float'
``````
``````from fractions import Fraction as F

D(3) + F(4)
``````
``````TypeError: unsupported operand type(s) for +: 'decimal.Decimal'
and 'Fraction'
``````

As a result, I don't consider `Decimal` to be a part of the numeric tower.

Making these pairings will allow us to better understand what happens when different numeric types interact.

# What happens when different numeric types interact?

When two different numeric types interact, the type of the result is the more general type. E.g.

• `int` (`Integral`) plus `float` (`Real`) is `float`.

``````type(int() + float())
``````
``````float
``````
• `Fraction` (`Rational`) plus `complex` (`Complex`) is `complex`:

``````type(Fraction() + complex())
``````
``````complex
``````

However, this can cause problems with `float` and `complex` objects, because of the limitations of their representation of the real ℝ (and complex ℂ) numbers:

• `int` can represent any integer ℤ as long as there is enough memory. E.g. 21,000,000

``````2 ** 1_000_000
``````
``````99006562292958982506979236163019
03250733624241787567332866396114
53170948330948610305461455123464
83914824315070345837238835106589
[...]
74619678367983172072707268320759
17571004282091924202288726677490
52301871236104888403162747109376
``````

(NOTE: digits omitted for clarity.)

But `float` and `complex` can't represent numbers this big, and interactions raise an exception:

``````float(2 ** 1_000_000)
``````
``````OverflowError: int too large to convert to float
``````
``````complex(2 ** 1_000_000)
``````
``````OverflowError: int too large to convert to float
``````
``````(2 ** 1_000_000) + 1.0
``````
``````OverflowError: int too large to convert to float
``````
• `Fraction` objects can represent any rational number ℚ as long as there is enough memory. E.g. 2-1,000,000

``````Fraction(1, 2 ** 1_000_000)
``````
``````Fraction(1,
99006562292958982506979236163019
03250733624241787567332866396114
53170948330948610305461455123464
83914824315070345837238835106589
[...]
74619678367983172072707268320759
17571004282091924202288726677490
52301871236104888403162747109376
)
``````

(NOTE: digits omitted for clarity.)

But `float` and `complex` can't represent numbers this small, and interactions produce 0:

``````float(Fraction(1, 2 ** 1_000_000))
``````
``````0.0
``````
``````complex(Fraction(1, 2 ** 1_000_000))
``````
``````0j
``````
``````Fraction(1, 2 ** 1_000_000) + 1.0
``````
``````1.0
``````

# In conclusion...

In this week's post, you learned about Python's 6 different numeric types, and the numeric tower exposed by the `numbers` module:

• `int` and `Fraction` objects can represent any integer ℤ or rational ℚ (as long as it fits into memory).
• `float` and `complex` use machine-level double precision numbers, likely the IEEE Standard for Floating-Point Arithmetic (IEEE 754).
• `bool` is a numeric type for historical reasons (see PEP 285 for details).
• `Decimal` uses decimal floating point numbers, following the "General Decimal Arithmetic" specification.
• The numeric tower has abstract base classes `Number`, `Complex` (ℂ), `Real` (ℝ), `Rational` (ℚ), `Integral` (ℤ).
• `bool`, `int`, `Fraction`, `float`, and `complex` are in the numeric tower and can interact with each other.
• `Decimal` is technically "in" the numeric tower, but doesn't interact well with the other numeric types.

My challenge to you:

NumPy a popular 3rd-party library introduces more numeric types:

"Data type objects (dtype)"

Read about the different numeric types and compare them to Python's standard 6 types.