C ++ Meilleur moyen d'obtenir la division entière et le reste

103

Je me demande simplement, si je veux diviser a par b, et si je suis intéressé à la fois par le résultat c et le reste (par exemple, disons que j'ai un nombre de secondes et que je veux le diviser en minutes et secondes), quelle est la meilleure façon de allez-y?

Serait-ce

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

peut-être y a-t-il une fonction magique qui donne les deux à la fois?

Biscuit
la source
5
toutes les réponses ci-dessous semblent raisonnables, je voudrais juste ajouter que tout raclage avec double(votre dernier élément) me semble être une mauvaise idée, vous vous retrouverez avec des chiffres qui ne correspondent pas, et peuvent vous coûter en performances et taille de l'exécutable (a toujours été un problème pour moi sur certains systèmes embarqués).
nhed
2
La troisième est une mauvaise option: et si tmp = 54,999999999999943157? Cela dit, le casting à l'ancienne n'est jamais une chose intelligente à faire.
jimifiki

Réponses:

98

Sur x86, le reste est un sous-produit de la division elle-même, donc tout compilateur à moitié décent devrait pouvoir simplement l'utiliser (et ne pas en exécuter à divnouveau). Ceci est probablement également fait sur d'autres architectures.

Instruction: DIVsrc

Remarque: division non signée. Divise l'accumulateur (AX) par "src". Si le diviseur est une valeur d'octet, le résultat est placé sur AL et le reste sur AH . Si le diviseur est une valeur de mot, alors DX: AX est divisé par "src" et le résultat est stocké dans AX et le reste est stocké dans DX .

int c = (int)a / b;
int d = a % b; /* Likely uses the result of the division. */
cnicutar
la source
9
Je pense que beaucoup de gens savent de l'école élémentaire que lorsque vous faites une division, vous obtenez le reste gratuitement. La vraie question est: nos compilateurs sont-ils assez intelligents pour en profiter?
1
@jdv: Je ne serais pas surpris. C'est une optimisation très simple.
Jon Purdy
68
J'ai essayé un test rapide. Avec g ++ 4.3.2 utilisant -O2, la sortie de l'assembleur le montre clairement en utilisant une idivlinstruction et en utilisant les résultats dans eax et edx. J'aurais été choqué si ce n'était pas le cas.
Fred Larson
2
@EuriPinhollow D'accord, ce n'est pas une question sur x86. Je ne l'ai donné qu'à titre d'exemple, avec l'hypothèse très douteuse que d'autres architectures font probablement quelque chose de similaire.
cnicutar
3
Vous devez cependant dire au compilateur d'optimiser, au moins pour g ++. Une simple expérience avec g ++ 6.3.0 sur x86_64 a révélé que sans optimisation du tout, vous obtenez deux idivlinstructions, mais avec -O1ou plus, vous en obtenez une. Comme le dit le manuel, «Sans aucune option d'optimisation… les déclarations sont indépendantes» .
Tom Zych
80

std::div renvoie une structure avec à la fois résultat et reste.

pezcode
la source
5
Je suis curieux de savoir si c'est réellement plus efficace que, disons, l'option 1 sur un compilateur moderne.
Greg Howell
2
Sympa, je ne savais pas. Est-ce plus rapide?
Agréable. Sauriez-vous si l'un est mis en œuvre depuis longtemps quelque part?
Cookie du
@Cookie: C ++ 03 n'a pas de concept de long long, mais il est fort probable que votre compilateur ait une long longsurcharge de std::diven tant qu'extension.
ildjarn
@Greg: Je ne pense pas que ce soit le cas, étant donné qu'il doit très probablement écrire des résultats dans une structure en mémoire et les renvoyer, ce qui (à moins qu'il ne se compile en ligne et que l'accès à la structure ne soit optimisé) est une plus grande pénalité que de faire une instruction de division supplémentaire.
pezcode
28

Sur x86 au moins, g ++ 4.6.1 utilise simplement IDIVL et obtient les deux à partir de cette seule instruction.

Code C ++:

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

code 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
la source
L'ordre est-il important? Par exemple, si vous effectuez une itération sur un /=- vous devrez peut-être utiliser une variable temporaire pour conserver la division en premier.
AnnanFay
@Annan Cela n'avait pas d'importance à l'époque, et ce n'est plus le cas de nos jours. Les compilateurs sont assez intelligents pour le réorganiser.
Sahsahae
10

Exemple de test de code div () et division et mod combinés. Je les ai compilés avec gcc -O3, j'ai dû ajouter l'appel à doNothing pour empêcher le compilateur d'optimiser tout (la sortie serait 0 pour la solution division + mod).

Prenez-le avec un grain de sel:

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

Sorties: 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);
}

Sorties: 25

Greg Howell
la source
5

En plus de la famille de fonctions std :: div mentionnée ci-dessus , il y a aussi la famille de fonctions std :: remquo , qui retourne le rem -ainder et obtient le quo -tient via un pointeur passé.

[Edit:] Il semble que std :: remquo ne renvoie pas vraiment le quotient après tout.

Jamin Gray
la source
3

Toutes choses étant égales par ailleurs, la meilleure solution est celle qui exprime clairement votre intention. Alors:

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

est probablement la meilleure des trois options que vous avez présentées. Cependant, comme indiqué dans d'autres réponses, la divméthode calculera les deux valeurs pour vous en même temps.

Jon
la source
3

Vous ne pouvez pas faire confiance à g ++ 4.6.3 ici avec des entiers 64 bits sur une plate-forme Intel 32 bits. a / b est calculé par un appel à divdi3 et a% b est calculé par un appel à moddi3. Je peux même proposer un exemple qui calcule a / b et ab * (a / b) avec ces appels. J'utilise donc c = a / b et ab * c.

La méthode div donne un appel à une fonction qui calcule la structure div, mais un appel de fonction semble inefficace sur les plates-formes qui ont un support matériel pour le type intégral (c'est-à-dire des entiers 64 bits sur les plates-formes Intel / AMD 64 bits).

brac37
la source
-4

Vous pouvez utiliser un module pour obtenir le reste. Bien que la réponse de @ cnicutar semble plus claire / plus directe.

Sinthet
la source
2
Oui, l'affiche originale utilisait l'opérateur module, la question est de savoir comment le rendre efficace.
Mark Lakata