[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 ;-)