C ++ Melhor maneira de obter divisão inteira e resto

103

Só estou pensando, se quero dividir a por b, e estou interessado no resultado ce no restante (por exemplo, digamos que tenho o número de segundos e quero dividi-lo em minutos e segundos), qual é a melhor maneira de ir sobre isso?

Seria

int c = (int)a / b;
int d = a % b;

ou

int c = (int)a / b;
int d = a - b * c;

ou

double tmp = a / b;
int c = (int)tmp;
int d = (int)(0.5+(tmp-c)*b);

ou

talvez haja uma função mágica que dá a ambos ao mesmo tempo?

Biscoito
fonte
5
todas as respostas abaixo parecem razoáveis, gostaria apenas de acrescentar que qualquer sujeira com double(seu último item) parece-me uma má ideia, você acabará com números que não se alinham e podem custar em desempenho e tamanho do executável (sempre foi um problema para mim em certos sistemas embarcados).
nhed
2
A terceira é uma opção RUIM: e se tmp = 54.999999999999943157? Dito isso, o elenco antigo nunca é uma coisa inteligente de se fazer.
Jimifiki

Respostas:

98

No x86, o resto é um subproduto da própria divisão, então qualquer compilador decente deve ser capaz de apenas usá-lo (e não executar a divnovamente). Isso provavelmente também é feito em outras arquiteturas.

Instrução: DIVsrc

Nota: Divisão sem sinal. Acumulador de divisões (AX) por "src". Se divisor for um valor de byte, o resultado é colocado em AL e o resto em AH . Se divisor for um valor de palavra, então DX: AX é dividido por "src" e o resultado é armazenado em AX e o resto é armazenado em DX .

int c = (int)a / b;
int d = a % b; /* Likely uses the result of the division. */
cnicutar
fonte
9
Acho que muitos sabem desde o ensino fundamental que, ao fazer uma divisão, você recebe o restante de graça. A verdadeira questão é: nossos compiladores são inteligentes o suficiente para tirar vantagem disso?
1
@jdv: Eu não ficaria surpreso. É uma otimização muito simples.
Jon Purdy de
68
Tentei um teste rápido. Com g ++ 4.3.2 usando -O2, a saída do assembler mostra claramente usando uma idivlinstrução e usando os resultados em eax e edx. Eu ficaria chocado se não o fizesse.
Fred Larson
2
@EuriPinhollow Concordo que não é uma pergunta sobre x86. Eu apenas dei como exemplo, com a suposição altamente duvidosa de que outras arquiteturas provavelmente fazem algo semelhante.
cnicutar
3
Você precisa dizer ao compilador para otimizar, pelo menos para g ++. Um experimento simples com g ++ 6.3.0 em x86_64 revelou que, sem nenhuma otimização, você obtém duas idivlinstruções, mas com -O1ou superior, você obtém uma. Como diz o manual, “Sem qualquer opção de otimização… as declarações são independentes” .
Tom Zych,
80

std::div retorna uma estrutura com resultado e resto.

pezcode
fonte
5
Estou curioso para saber se isso é realmente mais eficiente do que, digamos, a opção 1 em um compilador moderno.
Greg Howell
2
Legal, eu não sabia. É mais rápido?
Agradável. Você saberia se um for implementado por muito tempo em algum lugar?
Cookie de
@Cookie: C ++ 03 não tem o conceito de long long, mas é altamente provável que seu compilador tenha uma long longsobrecarga de std::divcomo uma extensão.
ildjarn de
@Greg: Não acho que seja, dado que provavelmente terá que gravar os resultados em uma estrutura na memória e retorná-los, o que (a menos que compile como inline e o acesso à estrutura seja otimizado) é uma penalidade maior do que fazer uma instrução de divisão extra.
pezcode
28

No x86, pelo menos, g ++ 4.6.1 apenas usa IDIVL e obtém ambos dessa única instrução.

Código C ++:

void foo(int a, int b, int* c, int* d)
{
  *c = a / b;
  *d = a % b;
}

código x86:

__Z3fooiiPiS_:
LFB4:
    movq    %rdx, %r8
    movl    %edi, %edx
    movl    %edi, %eax
    sarl    $31, %edx
    idivl   %esi
    movl    %eax, (%r8)
    movl    %edx, (%rcx)
    ret
Peter Alexander
fonte
O pedido importa? Por exemplo, se você estiver iterando sobre um /=- você pode precisar usar uma variável temporária para manter a divisão primeiro.
AnnanFay
@Annan Não importava naquela época, e não importa hoje em dia. Compiladores são inteligentes o suficiente para reordená-lo.
Sahsahae
10

Amostra de teste de código div () e divisão e mod combinados. Compilei isso com gcc -O3, tive que adicionar a chamada para doNothing para impedir o compilador de otimizar tudo (a saída seria 0 para a solução division + mod).

Tome com um grão de sal:

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>

extern doNothing(int,int); // Empty function in another compilation unit

int main() {
    int i;
    struct timeval timeval;
    struct timeval timeval2;
    div_t result;
    gettimeofday(&timeval,NULL);
    for (i = 0; i < 1000; ++i) {
        result = div(i,3);
        doNothing(result.quot,result.rem);
    }
    gettimeofday(&timeval2,NULL);
    printf("%d",timeval2.tv_usec - timeval.tv_usec);
}

Saídas: 150

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>

extern doNothing(int,int); // Empty function in another compilation unit

int main() {
    int i;
    struct timeval timeval;
    struct timeval timeval2;
    int dividend;
    int rem;
    gettimeofday(&timeval,NULL);
    for (i = 0; i < 1000; ++i) {
        dividend = i / 3;
        rem = i % 3;
        doNothing(dividend,rem);
    }
    gettimeofday(&timeval2,NULL);
    printf("%d",timeval2.tv_usec - timeval.tv_usec);
}

Saídas: 25

Greg Howell
fonte
3

Com tudo o mais sendo igual, a melhor solução é aquela que expressa claramente sua intenção. Assim:

int totalSeconds = 453;
int minutes = totalSeconds / 60;
int remainingSeconds = totalSeconds % 60;

é provavelmente a melhor das três opções apresentadas. Porém, conforme observado em outras respostas, o divmétodo calculará os dois valores para você de uma vez.

Jon
fonte
3

Você não pode confiar no g ++ 4.6.3 aqui com inteiros de 64 bits em uma plataforma Intel de 32 bits. a / b é calculado por uma chamada para divdi3 e a% b é calculado por uma chamada para moddi3. Posso até dar um exemplo que calcule a / be ab * (a / b) com essas chamadas. Portanto, uso c = a / be ab * c.

O método div dá uma chamada para uma função que calcula a estrutura div, mas uma chamada de função parece ineficiente em plataformas que têm suporte de hardware para o tipo integral (ou seja, inteiros de 64 bits em plataformas intel / amd de 64 bits).

brac37
fonte
-4

Você pode usar um módulo para obter o restante. Embora a resposta de @cnicutar pareça mais limpa / direta.

Sinthet
fonte
2
Sim, o pôster original usou o operador de módulo, a questão é como torná-lo eficiente.
Mark Lakata