[Python-Dev] Floor division

Tim Peters tim.peters at gmail.com
Fri Jan 26 03:05:47 CET 2007


[Armin]
> Thanks for the clarification.  Yes, it makes sense that __mod__,
> __divmod__ and __floordiv__ on float and decimal would eventually follow
> the same path as for complex (where they make even less sense and
> already raise a DeprecationWarning).

This truly has nothing to do with complex.  All meanings for "mod"
(whether in Python, IEEE-754, C89, C99, or IBM's proposed decimal
standard) are instances of this mathematical schema:

[1]    x%y = x - R(x/y)*y

by definition, where R(z) is a specific way of rounding z to an exact
mathematical ("infinite precision") integer.

For int, long, and float, Python uses R=floor (and again it's
important to note that these are mathematical statements, not
statements about computer arithmetic).

For ints, longs, and (mathematical) reals, that's the usual
"number-theoretic" definition of mod, as, e.g., given by Knuth:

    mod(x, y) = x - floor(x/y)*y

It's the only definition of all those mentioned here that guarantees
the result is non-negative when the modulus (y) is positive, and
that's a very nice property for integers.  It's /also/ the only
definition off all those mentioned here where the exact mathematical
result may /not/ be exactly representable as a computer float when x
and y are computer floats.

It's that last point that makes it a poor definition for working with
computer floats:  for any other plausible way of defining "mod", the
exact result /is/ exactly representable as a computer float.  That
makes reasoning much easier, just as you don't have to think twice
about seeing abs() or unary minus applied to a float.  No information
is lost, and you can rely on expected invariants like

[2]    0 <= abs(x%y) < abs(y)        if y != 0 and finite

provided one of the non- R=floor definitions of mod is used for computer floats.

For complex, Python uses R(z) = floor(real_part_of(z)).  AFAICT,
Python just made that up out of thin air.  There are no known use
cases, and it's bizarre.  For example, [2] isn't even approximately
reliable:

    >>> x = 5000 + 100j
    >>> y = 1j
    >>> x % y
    (5000+0j)
    >>> print abs(x%y), abs(y)
    5000.0 1.0

In short, while Python "does something" for complex % complex, what it
does seems more-than-less arbitrary.  That's why it was deprecated
years ago, and nobody complained.

But for computer floats, there are ways to instantiate R in [1] that
work fine, returning a result that is truly (exactly) congruent to x
modulo y, even though x, y and the result are all computer floats.
Two of those ways:

The C89 fmod = C99 fmod = C99 integral "%" = IBM spec "remainder"
picks R(z) = round z to the closest integer in the direction of 0.

The C99 "remainder" = IBM spec "remainder-near" = IEEE-754 REM picks
R(z) = round z to the nearest integer, or if z is exactly halfway
between integers to the nearest even integer.  This one has the nice
property (for floats!) that [2] can be strengthened to:

    0 <= abs(x%y) <= abs(y)/2

That's often useful for argument reduction of periodic functions,
which is an important use case for a floating-point mod.  You
typically don't care about the sign of the result in that case, but do
want the absolute value as small as possible.  That's probably why
this was the only definition standardized by 754.

In contrast, I don't know of any use for complex %.


More information about the Python-Dev mailing list