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

jepler at unpythonic.net jepler at unpythonic.net
Sun Nov 24 12:47:50 EST 2002


Welcome to the wonderful world of floating point!

The result of -1e-18 % (2*math.pi) is the same as (2*math.pi-1e-18).
But the closest representable number to that is the same as the closest
representable number to (2*math.pi).

You can see the same thing with this code:
>>> -1. % 1e30 == 1e30
True

This is the body of "float_rem" (implements float%float) in
Objects/floatobject.c:
static PyObject *
float_rem(PyObject *v, PyObject *w)
{
        double vx, wx;
        double mod;
        CONVERT_TO_DOUBLE(v, vx);
        CONVERT_TO_DOUBLE(w, wx);
        if (wx == 0.0) {
                PyErr_SetString(PyExc_ZeroDivisionError, "float modulo");
                return NULL;
        }
        PyFPE_START_PROTECT("modulo", return 0)
        mod = fmod(vx, wx);
        /* note: checking mod*wx < 0 is incorrect -- underflows to
           0 if wx < sqrt(smallest nonzero double) */
        if (mod && ((wx < 0) != (mod < 0))) {
                mod += wx;
        }
        PyFPE_END_PROTECT(mod)
        return PyFloat_FromDouble(mod);
}

The problem code is
        if (mod && ((wx < 0) != (mod < 0))) {
                mod += wx;
fmod returned -1e-18, which triggers this test (result is nonzero and has
opposite sign from mod).  mod+=wx == wx (since mod is so small compared to
wx), and that's the result you get.

I don't know if it's "saner" to write
        if (mod && ((wx < 0) != (mod < 0))) {
                mod += wx;
                if(mod == wx) mod = 0;
though in your case it would make the "problem" go away.  Tim Peters will
probably tell me why this is a bad idea if he reads the thread.

Jeff




More information about the Python-list mailing list