This proposal defines a hierarchy of Abstract Base Classes (ABCs) (PEP
3119) to represent number-like classes. It proposes a hierarchy of
Number :> Complex :> Real :> Rational :> Integral where A :> B
means "A is a supertype of B". The hierarchy is inspired by Scheme's
numeric tower [4].

Functions that take numbers as arguments should be able to determine
the properties of those numbers, and if and when overloading based on
types is added to the language, should be overloadable based on the
types of the arguments. For example, slicing requires its arguments to
be Integrals, and the functions in the math module require
their arguments to be Real.

This PEP specifies a set of Abstract Base Classes, and suggests a
general strategy for implementing some of the methods. It uses
terminology from PEP 3119, but the hierarchy is intended to be
meaningful for any systematic method of defining sets of classes.

The type checks in the standard library should use these classes
instead of the concrete built-ins.

To support more precise narrowing from float to int (and more
generally, from Real to Integral), we propose the following new
__magic__ methods, to be called from the corresponding library
functions. All of these return Integrals rather than Reals.

__trunc__(self), called from a new builtin trunc(x), which
returns the Integral closest to x between 0 and x.

__floor__(self), called from math.floor(x), which returns
the greatest Integral <= x.

__ceil__(self), called from math.ceil(x), which returns the
least Integral >= x.

__round__(self), called from round(x), which returns the
Integral closest to x, rounding half as the type chooses.
float will change in 3.0 to round half toward even. There is
also a 2-argument version, __round__(self, ndigits), called
from round(x, ndigits), which should return a Real.

In 2.6, math.floor, math.ceil, and round will continue to
return floats.

The int() conversion implemented by float is equivalent to
trunc(). In general, the int() conversion should try
__int__() first and if it is not found, try __trunc__().

complex.__{divmod,mod,floordiv,int,float}__ also go away. It would
be nice to provide a nice error message to help confused porters, but
not appearing in help(complex) is more important.

Implementors should be careful to make equal numbers equal and
hash them to the same values. This may be subtle if there are two
different extensions of the real numbers. For example, a complex type
could reasonably implement hash() as follows:

def __hash__(self):
return hash(complex(self))

but should be careful of any values that fall outside of the built in
complex's range or precision.

We want to implement the arithmetic operations so that mixed-mode
operations either call an implementation whose author knew about the
types of both arguments, or convert both to the nearest built in type
and do the operation there. For subtypes of Integral, this means that
__add__ and __radd__ should be defined as:

There are 5 different cases for a mixed-type operation on subclasses
of Complex. I'll refer to all of the above code that doesn't refer to
MyIntegral and OtherTypeIKnowAbout as "boilerplate". a will be an
instance of A, which is a subtype of Complex (a : A <:
Complex), and b : B <: Complex. I'll consider a + b:

If A defines an __add__ which accepts b, all is well.

If A falls back to the boilerplate code, and it were to return
a value from __add__, we'd miss the possibility that B defines
a more intelligent __radd__, so the boilerplate should return
NotImplemented from __add__. (Or A may not implement __add__ at
all.)

Then B's __radd__ gets a chance. If it accepts a, all is well.

If it falls back to the boilerplate, there are no more possible
methods to try, so this is where the default implementation
should live.

If B <: A, Python tries B.__radd__ before A.__add__. This is
ok, because it was implemented with knowledge of A, so it can
handle those instances before delegating to Complex.

If A<:Complex and B<:Real without sharing any other knowledge,
then the appropriate shared operation is the one involving the built
in complex, and both __radd__s land there, so a+b == b+a.

The initial version of this PEP defined an algebraic hierarchy
inspired by a Haskell Numeric Prelude [3] including
MonoidUnderPlus, AdditiveGroup, Ring, and Field, and mentioned several
other possible algebraic types before getting to the numbers. We had
expected this to be useful to people using vectors and matrices, but
the NumPy community really wasn't interested, and we ran into the
issue that even if x is an instance of X <: MonoidUnderPlus
and y is an instance of Y <: MonoidUnderPlus, x + y may
still not make sense.

Then we gave the numbers a much more branching structure to include
things like the Gaussian Integers and Z/nZ, which could be Complex but
wouldn't necessarily support things like division. The community
decided that this was too much complication for Python, so I've now
scaled back the proposal to resemble the Scheme numeric tower much
more closely.

Thanks to Neal Norwitz for encouraging me to write this PEP in the
first place, to Travis Oliphant for pointing out that the numpy people
didn't really care about the algebraic concepts, to Alan Isaac for
reminding me that Scheme had already done this, and to Guido van
Rossum and lots of other people on the mailing list for refining the
concept.