[Python-Dev] Floor division

Tim Peters tim.peters at gmail.com
Mon Jan 22 03:08:18 CET 2007


[Tim Peters]
>> ...
>> >>> decimal.Decimal(-1) % decimal.Decimal("1e100")
>> Decimal("-1")

[Armin Rigo]
> BTW - isn't that case in contradiction with the general Python rule that
> if b > 0, then a % b should return a number between 0 included and b
> excluded?

Sure.

> We try hard to do that for ints, longs and floats.

But fail in this case for floats:

>>> -1 % 1e100 < 1e100
False

Can't win.  The infinite-precision result is the mathematical
F(1e100)-1, where F(1e100) is the binary float closest to 10**100, but
F(1e100)-1 isn't representable as a float -- it rounds back up to
F(1e100):

>>> -1 % 1e100 == 1e100
True

There simply is no /representable/ float value in [0, 10**100)
congruent to -1 modulo 10**100 (or modulo F(1e100)), so it's
impossible to return a non-surprising (to everyone) result in that
range.  0 and 1e100 are in some sense "the best" answers in that
range, both off by only 1 part in F(1e100), the smallest possible
error among representable floats in that range.  -1/1e100 certainly
isn't 0, so

    -1 // 1e100 == -1.0

is required.  Picking -1 % 1e100 == 1e100 then follows, to try to preserve that

    a = (a//b)*b + a%b

as closely as is possible.

Ints and longs never have problems here, because the exact % result is
always exactly representable.

That isn't true of floats (whether binary or decimal), but under a
different definition of "mod" the mathematically exact result is
always exactly representable:  a%b takes the sign of `a` rather than
the sign of `b`.  C's fmod (Python's math.fmod), and the proposed
standard for decimal arithmetic implemented by the `decimal` module,
use that meaning for "mod" instead.

>>> math.fmod(-1, 1e100)
-1.0

> The fact that it works differently with Decimal could be unexpected.

Yup.  See "can't win" above :-(

Another good definition of "mod" for floats is to return the
representative of smallest absolute value; i.e., satisfy

    abs(a%b) <= abs(b) / 2

The mathematically exact value for that is also exactly representable
(BTW, the proposed standard for decimal arithmetic calls this
"remainder-near", as opposed to "remainder").

It's just a fact that different definitions of mod are most useful
most often depending on data type.  Python's is good for integers and
often sucks for floats.  The C99 and `decimal` definition(s) is/are
good for floats and often suck(s) for integers.  Trying to pretend
that integers are a subset of floats can't always work ;-)


More information about the Python-Dev mailing list