Целочисленное деление и модуль в стиле Python в C

В Python и Ruby целочисленное деление со знаком обрезается до отрицательной бесконечности, а целочисленный модуль со знаком имеет тот же знак, что и второй операнд:

>>> (-41) / 3
-14
>>> (-41) % 3
1

Однако в C и Java целочисленное деление со знаком усекает до 0, а целочисленный модуль со знаком имеет тот же знак, что и первый операнд:

printf("%d\n", (-41) / 3); /* prints "-13" */
printf("%d\n", (-41) % 3); /* prints "-2" */

Каков самый простой и эффективный способ в C выполнить такое же деление и модуль, как в Python и Ruby?


person user102008    schedule 06.05.2009    source источник
comment
То же самое происходит в JavaScript: (-41)% 3 === -2   -  person VFDan    schedule 16.12.2018


Ответы (6)


Направление округления с целочисленным делением со знаком не указано в старых стандартах C. Однако в C99 указано округление до нуля.

Вот переносимый код, который работает со всеми версиями стандартов C и архитектур ЦП:

int py_div(int a, int b)
{
  if (a < 0)
    if (b < 0)
      return -a / -b;
    else
      return -(-a / b) - (-a % b != 0 ? 1 : 0);
  else if (b < 0)
      return -(a / -b) - (a % -b != 0 ? 1 : 0);
    else
      return a / b;
}

int py_mod(int a, int b)
{
  if (a < 0)
    if (b < 0)
      return -(-a % -b);
    else
      return -a % b - (-a % -b != 0 ? 1 : 0);
  else if (b < 0)
      return -(a % -b) + (-a % -b != 0 ? 1 : 0);
    else
      return a % b;
}

Я провел несколько поверхностных тестов и, похоже, дал те же результаты, что и Python. Этот код может быть не максимально эффективным, но хороший компилятор C, вероятно, сможет оптимизировать его адекватно, особенно если вы поместите код в заголовок как статические функции.

Вы также можете взглянуть на этот тесно связанный вопрос: Округление целочисленного деления с негативы в C ++.

person Ville Laurikari    schedule 06.05.2009
comment
Если вы хотите эффективно использовать справочную таблицу. Если этот код является проблемой эффективности, единственной реальной альтернативой было бы использование обычных операторов / и% и их округление. - person Chris Lutz; 07.05.2009
comment
Это довольно аккуратно. Было бы полезно добавить в этот код фигурные скобки (с таким большим количеством условных вложений трудно сказать, что и где происходит…) - person Jonathan Sterling; 17.03.2011
comment
Возможно, это дело вкуса, но я не согласен с тем, что добавление фигурных скобок упростит чтение. При чтении кода я смотрю на отступы, а не считаю фигурные скобки в голове. - person Ville Laurikari; 17.03.2011
comment
Этот код не выдает правильный модуль, когда либо делимое, либо делитель отрицательны. - person Rufflewind; 02.09.2014
comment
@VilleLaurikari: Разве это не должно быть b : 0, а не 1 : 0? Мне кажется, это работает: `int py_mod (int a, int b) {if ((a› = 0) == (b ›= 0)) return a% b; иначе return (a% -b) + ((a% -b)! = 0? b: 0); } ` - person Dwayne Robinson; 25.02.2020
comment
Внимание! py_mod дает неправильный ответ на a = -1611640551, b = 2. Python возвращает 1, а фрагмент в ответе дает 0. - person kelin; 05.03.2020
comment
ВНИМАНИЕ! Этот ответ не дает ожидаемого результата по модулю. Однако ответ @josch ниже имеет значение. Используйте это вместо этого (и не тратьте время на отладку, как это сделал я). (Другие ответы тоже могут быть правильными, просто я их не проверял) - person sodiumnitrate; 05.08.2020

Я считаю, что по модулю проще всего. Не имеет значения, каково соглашение о знаках реализации, мы просто приводим результат к желаемому знаку:

r = n % a;
if (r < 0) r += a;

Очевидно, это для положительного а. Для отрицательного a вам необходимо:

r = n % a;
if (r > 0) r += a;

Что (возможно, немного сбивает с толку) объединяет, чтобы дать следующее (в C ++. В C сделайте то же самое с int, а затем утомительно напишите дубликат надолго):

template<typename T> T sign(T t) { return t > T(0) ? T(1) : T(-1); }

template<typename T> T py_mod(T n, T a) {
    T r = n % a;
    if (r * sign(a) < T(0)) r += a;
    return r;
}

Мы можем использовать скупую двузначную «знаковую» функцию, потому что мы уже знаем! = 0, иначе% будет неопределенным.

Применяя тот же принцип к делению (смотрите на вывод, а не на ввод):

q = n / a;
// assuming round-toward-zero
if ((q < 0) && (q * a != n)) --q;

Возможно, умножение может быть дороже, чем необходимо, но при необходимости его можно микрооптимизировать позже для каждой архитектуры. Например, если у вас есть операция деления, которая дает вам частное и остаток, тогда вы отсортированы для деления.

[Изменить: могут быть некоторые крайние случаи, когда это пойдет не так, например, если частное или остаток - INT_MAX или INT_MIN. Но эмуляция математики Python для больших значений в любом случае - это совсем другой вопрос ;-)]

[Еще одно изменение: разве стандартная реализация Python не написана на C? Вы можете найти источник того, что они делают]

person Steve Jessop    schedule 06.05.2009

Есть решение этого вопроса, гораздо более короткое (в коде), чем уже представленные. Я буду использовать формат ответа Вилле Лаурикари:

int py_div(int a, int b)
{
    return (a - (((a % b) + b) % b)) / b);
}

int py_mod(int a, int b)
{
    return ((a % b) + b) % b;
}

К сожалению, похоже, что вышеуказанные решения не работают. При сравнении этого решения с решением Вилле Лаурикари становится очевидно, что это решение работает вдвое медленнее.

Урок такой: в то время как инструкции ветвления замедляют код, инструкции деления намного хуже!

Я думал, что все же выложу это решение хотя бы из-за его элегантности.

person josch    schedule 17.08.2016
comment
Есть ли указатели на почему, доказательство или интуитивное объяснение? - person Emilio M Bumachar; 26.06.2020
comment
@EmilioMBumachar Попробуйте вставить значения в выражение, и вы увидите. Например, для a=-1 и b=3, ((-1%3)+3)%3=2, но для a=1 и b=3, ((1%3)+3)%3=1, как и ожидалось. - person sodiumnitrate; 05.08.2020

Вот простая реализация полового деления и модуля в C89:

#include <stdlib.h>

div_t div_floor(int x, int y)
{
    div_t r = div(x, y);
    if (r.rem && (x < 0) != (y < 0)) {
        r.quot -= 1;
        r.rem  += y;
    }
    return r;
}

Здесь div используется, потому что он имеет четко определенное поведение.

Если вы используете C ++ 11, вот шаблонная реализация полового деления и модуля:

#include <tuple>

template<class Integral>
std::tuple<Integral, Integral> div_floor(Integral x, Integral y)
{
    typedef std::tuple<Integral, Integral> result_type;
    const Integral quot = x / y;
    const Integral rem  = x % y;
    if (rem && (x < 0) != (y < 0))
        return result_type(quot - 1, rem + y);
    return result_type(quot, rem);
}

В C99 и C ++ 11 вы можете избежать использования div, поскольку поведение деления и модуля в C больше не зависит от реализации.

person Rufflewind    schedule 02.09.2014

Был задан вопрос о том, как имитировать целочисленное деление в стиле Python и по модулю. Все приведенные здесь ответы предполагают, что операнды этой операции сами являются целыми числами, но Python также может использовать числа с плавающей запятой для своей операции по модулю. Таким образом, я думаю, что следующий ответ решает проблему еще лучше:

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

int pydiv(double a, double b) {
    int q = a/b;
    double r = fmod(a,b);
    if ((r != 0) && ((r < 0) != (b < 0))) {
        q -= 1;
    }
    return q;
}

int main(int argc, char* argv[])
{
    double a = atof(argv[1]);
    double b = atof(argv[2]);
    printf("%d\n", pydiv(a, b));
}

И по модулю:

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

double pymod(double a, double b) {
    double r = fmod(a, b);
    if (r!=0 && ((r<0) != (b<0))) {
        r += b;
    }
    return r;
}

int main(int argc, char* argv[])
{
    double a = atof(argv[1]);
    double b = atof(argv[2]);
    printf("%f\n", pymod(a, b));
}

Я проверил эти две программы против того, как ведет себя Python, используя следующий тестовый код:

#!/usr/bin/python3
import subprocess
subprocess.call(["cc", "pydiv.c", "-lm", "-o", "cdiv"])
subprocess.call(["cc", "pymod.c", "-lm", "-o", "cmod"])
def frange(start, stop, step=1):
    for i in range(0, int((stop-start)/step)):
        yield start + step*i
for a in frange(-10.0, 10.0, 0.25):
    for b in frange(-10.0, 10.0, 0.25):
        if (b == 0.0):
            continue
        pydiv = a//b
        pymod = a%b
        cdiv = int(subprocess.check_output(["./cdiv", str(a), str(b)]))
        cmod = float(subprocess.check_output(["./cmod", str(a), str(b)]))
        if pydiv != cdiv:
            exit(1)
        if pymod != cmod:
            exit(1)

Вышеупомянутое будет сравнивать поведение Python деления и по модулю с реализациями C, которые я представил на 6320 тестовых примерах. Поскольку сравнение прошло успешно, я считаю, что мое решение правильно реализует поведение Python для соответствующих операций.

person josch    schedule 20.08.2016

Он углубляется в уродливый мир поплавков, но они дают правильные ответы на Java:

public static int pythonDiv(int a, int b) {
    if (!((a < 0) ^ (b < 0))) {
        return a / b;
    }
    return (int)(Math.floor((double)a/(double)b));
}

public static int pythonMod(int a, int b) {
    return a - b * pythonDiv(a,b);
}

Я не утверждаю об их эффективности.

person Ry4an Brase    schedule 06.05.2009
comment
Хотя этот конкретный экземпляр, скорее всего, даст правильные результаты для всех возможных входов, я бы все же избегал его, потому что он использует функцию с плавающей запятой для интегральных операций. Если вы замените float на double или long на int, это приведет к неправильным результатам для некоторых входных данных. Кроме того, если вы переносите этот экземпляр на C или C ++ на платформе, где int имеет ширину 64 бита, он также будет давать неправильные результаты для определенных входных данных. - person blubberdiblub; 21.01.2018