Что лежит в основе целочисленного деления в python? - PullRequest
0 голосов
/ 17 февраля 2020

Я бегу python 3.7.3

Относительно оператора целочисленного деления: "//" (двойное деление, двойное прямое sla sh, оператор двойного деления? Я не уверен точно имя.)

Кажется, он не дает последовательных результатов, и многие из найденных мной объяснений не полностью объясняют его результаты.

Здесь [ В чем причина для '//' в Python? (и других местах) сказано, что оператор "//" дает частное без остатка. Как будто a // b - это то же самое, что и floor(a / b) (или округление в большую сторону, если a / b отрицательно).

Однако иногда это не дает такого ответа. Например, 1 // 0.2 оценивается как 4. Однако 1 / 0.2 возвращает 5, а math.floor(1 / 2) также возвращает 5. Это дает число на единицу меньше целочисленного деления. Оператор // возвращает 5, если вы делите 10 на 2, но 1, разделенное на 0,2, работает неправильно.

Эта проблема возникает в других случаях, когда я использую оператор // для разделения чисел с плавающей запятой. Как 5 // 0.2 или 100 // 0.2. Я не знаю, является ли это какой-то причудой арифметики с плавающей точкой c, но эти проблемы, похоже, исчезают go, если вы наберете math.floor(5 / 0.2) (или любой другой набор чисел, вызывающих проблемы). За исключением случаев деления отрицательных чисел, в этом случае вы должны использовать math.ceil() вместо math.floor()

Мое текущее решение таково:

import math
def integerDivision(a,b):
    tmp = a / b 
    if tmp > 1:
        return(math.floor(tmp))
    else:
        return(math.ceil(tmp))

Что такое реализация оператора //, который не дает правильный результат в некоторых случаях с плавающей запятой? Есть ли лучший способ обойти эту проблему оператора //, кроме приведенного выше кода?

1 Ответ

2 голосов
/ 17 февраля 2020

Это не о реализации. Это о семантике оператора. Независимо от реализации, оператор // требуется, чтобы дать вам результаты, которые вы видите при применении к плавающим, и эти результаты действительно верны (для плавающих). Если вам не нужны эти результаты, float, вероятно, не тот инструмент, который вы делаете.

1 // 0.2 дает число с плавающей запятой, представляющее пол точного значения отношения его аргументов. , Однако правый аргумент не совсем соответствует введенному вами значению. Значение правого аргумента является наиболее близким к значению 0,2, представляемому в 64-битной двоичной IEEE с плавающей запятой, что немного выше 0,2:

>>> import decimal
>>> decimal.Decimal(0.2)
Decimal('0.200000000000000011102230246251565404236316680908203125')

Таким образом, точное значение коэффициента немного меньше 5, поэтому 1 // 0.2 дает вам 4.0.

1 / 0.2 дает вам 5.0, потому что точное значение отношения isn не может быть представлен как поплавок. Результат должен быть округлен, и он округляется до 5.0. // не выполняет это округление; он вычисляет пол точного значения, а не пол округленного числа. (Результат округления // может потребоваться округлить, но это другое округление.)

Учитывая все вышесказанное, реализация должна быть более сложной, чем floor(x / y), поскольку это может привести к неправильному результату. результат. CPython основывает свою реализацию // для чисел с плавающей точкой на fmod. Вы можете увидеть реализацию в Objects/floatobject.c в репозитории CPython.

static PyObject *
float_divmod(PyObject *v, PyObject *w)
{
    double vx, wx;
    double div, mod, floordiv;
    CONVERT_TO_DOUBLE(v, vx);
    CONVERT_TO_DOUBLE(w, wx);
    if (wx == 0.0) {
        PyErr_SetString(PyExc_ZeroDivisionError, "float divmod()");
        return NULL;
    }
    PyFPE_START_PROTECT("divmod", return 0)
    mod = fmod(vx, wx);
    /* fmod is typically exact, so vx-mod is *mathematically* an
       exact multiple of wx.  But this is fp arithmetic, and fp
       vx - mod is an approximation; the result is that div may
       not be an exact integral value after the division, although
       it will always be very close to one.
    */
    div = (vx - mod) / wx;
    if (mod) {
        /* ensure the remainder has the same sign as the denominator */
        if ((wx < 0) != (mod < 0)) {
            mod += wx;
            div -= 1.0;
        }
    }
    else {
        /* the remainder is zero, and in the presence of signed zeroes
           fmod returns different results across platforms; ensure
           it has the same sign as the denominator. */
        mod = copysign(0.0, wx);
    }
    /* snap quotient to nearest integral value */
    if (div) {
        floordiv = floor(div);
        if (div - floordiv > 0.5)
            floordiv += 1.0;
    }
    else {
        /* div is zero - get the same sign as the true quotient */
        floordiv = copysign(0.0, vx / wx); /* zero w/ sign of vx/wx */
    }
    PyFPE_END_PROTECT(floordiv)
    return Py_BuildValue("(dd)", floordiv, mod);
}

static PyObject *
float_floor_div(PyObject *v, PyObject *w)
{
    PyObject *t, *r;

    t = float_divmod(v, w);
    if (t == NULL || t == Py_NotImplemented)
        return t;
    assert(PyTuple_CheckExact(t));
    r = PyTuple_GET_ITEM(t, 0);
    Py_INCREF(r);
    Py_DECREF(t);
    return r;
}

Другие типы аргументов будут использовать другие реализации, в зависимости от типов.

...