a % b == b when a is a very small negative number

Tim Peters tim.one at comcast.net
Sun Nov 24 12:51:42 EST 2002


[Lee Harr]
> I have some code which is storing the direction an object
> is moving, and I want to keep that value: 0 <= direction < 2*PI
>
> So I do something like this:
>
> class Directed:
>     def setDirection(self, direction):
>         self.direction = direction % (2*PI)  # PI = math.pi
>
> This works fine until direction is some very very small negative number,
> like -0.1e-16.
>
> With that input, self.direction == 2*PI.

Numerically, that's unfortunately so.  Python guarantees that x%y has the
same sign as y, so Python can't return -1e-16 in this case (its sign differs
from the sign of 2*PI).  So it adds 2*PI to -1e16, and numerically (although
not mathematically) that's exactly equal to 2*PI.

> What I do right now to work around this is take the modulus twice:
>
>     def setDirection(self, direction):
>         self.direction = direction % (2*PI) % (2*PI)  # weird, but works
>
> Though thinking about it now, it might be better to simply
> check for a very small number:
>
>     def setDirection(self, direction):
>         if direction> 0 and direction < 0.00001:
>             self.direction = 0
>         else:
>             self.direction = direction % (2*PI)

Thatwouldn't help your original case, since direction was < 0 there.
Depending on the accuracy requirements of your application, replacing the
guard with

    if abs(direction) < 1e-5:

may be appropriate.

> Is this not a proper use of the % operator?
> Should not a % b always be less than b?

Mathematically, yes.  Numerically, something has to lose:  it's not always
possible for all of these to be true numerically:

1. sign(x%y) == sign(y)
2. abs(x%y) < abs(y)
3. divmod(x, y)[0] * y + x % y is very close to x

Python's % applied to floats preserves #1 and #3 at the expense of #2 in
some cases.  If you want #2 more than #1, use math.fmod instead:

>>> math.fmod(-1e-16, 2*math.pi)
-9.9999999999999998e-017
>>>





More information about the Python-list mailing list