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:
- bool represents the integers 1 and 0. (Why is
boolnumeric? See PEP 285 for details.) - int represents the integers ℤ using arbitrary-precision integer arithmetic.
- float represents the real numbers ℝ using machine-level double precision floating point numbers.
- complex represents the complex numbers ℂ using a pair of
floatobjects.
The standard library adds 2 more numeric types:
- fractions.Fraction
represents the rational numbers
ℚ using a pair of
intobjects. - decimal.Decimal represents the real numbers ℝ using arbitrary precision decimal floating point numbers.
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") + .10.2 -
Sometimes it fails:
from decimal import Decimal as D D("0.1") + .1TypeError: 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 tower | Numeric types |
|---|---|
Number | Decimal*
|
Complex | complex
|
Real | float
|
Rational | Fraction
|
Integral | int, bool
|
* Although
Decimalis "in" the numeric tower (it subclassesNumber), it doesn't follow the implicit conversion rules used by the other numeric types.Decimalinteracts withint:from decimal import Decimal as D D(3) + 4Decimal('7')But,
Decimaldoes not interact withcomplex,float, orFraction: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
Decimalto 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) plusfloat(Real) isfloat.type(int() + float())float -
Fraction(Rational) pluscomplex(Complex) iscomplex: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:
-
intcan represent any integer ℤ as long as there is enough memory. E.g. 21,000,0002 ** 1_000_00099006562292958982506979236163019 03250733624241787567332866396114 53170948330948610305461455123464 83914824315070345837238835106589 [...] 74619678367983172072707268320759 17571004282091924202288726677490 52301871236104888403162747109376(NOTE: digits omitted for clarity.)
But
floatandcomplexcan't represent numbers this big, and interactions raise an exception:float(2 ** 1_000_000)OverflowError: int too large to convert to floatcomplex(2 ** 1_000_000)OverflowError: int too large to convert to float(2 ** 1_000_000) + 1.0OverflowError: int too large to convert to float -
Fractionobjects can represent any rational number ℚ as long as there is enough memory. E.g. 2-1,000,000Fraction(1, 2 ** 1_000_000)Fraction(1, 99006562292958982506979236163019 03250733624241787567332866396114 53170948330948610305461455123464 83914824315070345837238835106589 [...] 74619678367983172072707268320759 17571004282091924202288726677490 52301871236104888403162747109376 )(NOTE: digits omitted for clarity.)
But
floatandcomplexcan't represent numbers this small, and interactions produce 0:float(Fraction(1, 2 ** 1_000_000))0.0complex(Fraction(1, 2 ** 1_000_000))0jFraction(1, 2 ** 1_000_000) + 1.01.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:
intandFractionobjects can represent any integer ℤ or rational ℚ (as long as it fits into memory).floatandcomplexuse machine-level double precision numbers, likely the IEEE Standard for Floating-Point Arithmetic (IEEE 754).boolis a numeric type for historical reasons (see PEP 285 for details).Decimaluses 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, andcomplexare in the numeric tower and can interact with each other.Decimalis 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:
Read about the different numeric types and compare them to Python's standard 6 types.
Ask yourself questions, like:
If you enjoyed this week's post, share it with your friends and stay tuned for next week's post. See you then!
(If you spot any errors or typos on this post, contact me via my contact page.)