Pergunta

Em Python e Ruby, assinado inteiro trunca divisão para infinito negativo, e assinado inteiro módulo tem o mesmo sinal o segundo operando:

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

No entanto, em C e Java, assinado trunca divisão inteira na direção de 0, e assinado inteiro módulo tem o mesmo sinal que o primeiro operando:

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

O que é a maneira mais simples e mais eficiente em C para executar o mesmo tipo de divisão e módulo como em Python e Ruby?

Foi útil?

Solução

A direção de arredondamento com divisão inteiro assinado não é especificado no padrão C mais velhos. No entanto, em C99 é especificado para arredondar para zero.

Aqui está o código portátil que funciona com todas as versões dos padrões C e arquiteturas de CPU:

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;
}

Eu fiz alguns testes superficiais e parece dar os mesmos resultados que Python. Este código pode não ser maximamente eficiente, mas um compilador bom C provavelmente pode otimizá-lo de forma adequada, especialmente se você colocar o código em um cabeçalho como funções estáticas.

Você também pode querer dar uma olhada nesta questão intimamente relacionados: Integer divisão de arredondamento com negativos em C ++ .

Outras dicas

Para módulo, acho mais simples o seguinte. Não importa o que convenção de sinais da implementação é, nós apenas coagir o resultado para o sinal que queremos:

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

Obviamente que é para um positivo. Para um negativo que você precisa:

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

O que (talvez um pouco confusa) se combina para dar a seguinte (. Em C ++ Em C fazer a mesma coisa com o int e, em seguida, tediosamente escrever uma duplicata por muito tempo longo):

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;
}

Podemos usar um pão-duro função de "sinal" dois valores porque nós já sabemos! = 0, ou a% seria indefinida.

Aplicando o mesmo princípio à divisão (olhar para a saída em vez da entrada):

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

As multiplicações sem dúvida poderia ser mais caro do que o necessário, mas pode ser micro-otimizado depois em uma base per-arquitetura se for necessário. Por exemplo, se você tem uma op divisão que lhe dá quociente e o resto, então você está classificado para a divisão.

[Editar: pode haver alguns casos extremos onde isso der errado, por exemplo, se o quociente ou o resto é INT_MAX ou INT_MIN. Mas emulando matemática Python para grandes valores é toda uma outra questão de qualquer maneira; -)]

[Outro edit: não é a implementação padrão do Python escrito em C? Você poderia arrasto a fonte para o que eles fazem]

Aqui está uma implementação simples da divisão de piso e módulo no 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;
}

Aqui, div é usado porque tem bem definida comportamento .

Se você estiver usando C ++ 11, aqui é uma implementação templated da divisão de piso e módulo:

#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);
}

Em C99 e C ++ 11, você pode evitar o uso div vez que o comportamento da divisão e módulo em C não são mais dependem da implementação.

Há uma solução para esta questão que é muito mais curto (em código) do que os já apresentados. Vou usar o formato da resposta de Ville Laurikari para meu:

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;
}

Infelizmente, parece que as soluções acima não são um bom desempenho. Quando aferição esta solução com a de Ville Laurikari, torna-se evidente que esta solução executa apenas metade tão rápido.

A lição é: Enquanto instruções de desvio tornar o código lento, instruções de divisão são muito piores

Eu pensei que, no entanto, postar esta solução se apenas por sua elegância.

A pergunta sobre como emular divisão inteira Python-estilo e modulo. Todas as respostas dadas aqui assumir os operandos desta operação para serem eles mesmos números inteiros, mas Python também pode usar carros alegóricos para a sua operação de módulo. Assim, acho que os seguintes resolve responder ao problema ainda melhor:

#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));
}

E para o módulo:

#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));
}

Eu testei o acima de dois programas contra a forma como se comporta Python usando o seguinte código de teste:

#!/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)

O código acima irá comparar o comportamento da divisão de Python e modulo com o C implementações I apresentado em 6320 testcases. Desde a comparação sucedido, Acredito que a minha solução implementa corretamente o comportamento do Python respectivas operações.

Ele mergulha no mundo feio de carros alegóricos, mas estes dão respostas corretas em 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);
}

Não faço afirmações sobre a sua eficiência.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top