C ++ Najlepszy sposób na dzielenie liczb całkowitych i resztę


103

Zastanawiam się tylko, czy chcę podzielić a przez b i interesuje mnie zarówno wynik c, jak i reszta (np. Powiedz, że mam liczbę sekund i chcę to podzielić na minuty i sekundy), jaki jest najlepszy sposób, aby zająć się tym?

Czy może być

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

lub

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

lub

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

lub

może istnieje magiczna funkcja, która daje jedno i drugie na raz?


5
wszystkie poniższe odpowiedzi wydają się rozsądne, chciałbym tylko dodać, że jakiekolwiek pomieszanie z double(twoim ostatnim przedmiotem) wydaje mi się złym pomysłem, skończysz z liczbami, które się nie zgadzają i mogą kosztować cię wydajność i rozmiar pliku wykonywalnego (zawsze był to problem w niektórych systemach wbudowanych).
nhed

2
Trzecia to ZŁA opcja: co jeśli tmp = 54.999999999999943157? To powiedziawszy, casting w starym stylu nigdy nie jest mądrą rzeczą.
jimifiki

Odpowiedzi:


98

Na x86 reszta jest produktem ubocznym samego dzielenia, więc każdy w połowie przyzwoity kompilator powinien być w stanie po prostu go użyć (i nie wykonywać divponownie). Prawdopodobnie dzieje się tak również na innych architekturach.

Instrukcja: DIVsrc

Uwaga: Podział bez znaku. Dzieli akumulator (AX) przez „src”. Jeśli dzielnik jest wartością bajtową, wynik jest umieszczany w AL, a reszta do AH . Jeśli dzielnik jest wartością słowa, to DX: AX jest dzielone przez "src" i wynik jest zapisywany w AX, a reszta w DX .

int c = (int)a / b;
int d = a % b; /* Likely uses the result of the division. */

9
Myślę, że wielu ludzi wie ze szkoły podstawowej, że robiąc podział, resztę dostajesz za darmo. Prawdziwe pytanie brzmi: czy nasze kompilatory są wystarczająco inteligentne, aby to wykorzystać?

1
@jdv: Nie zdziwiłbym się. To bardzo prosta optymalizacja.
Jon Purdy,

68
Spróbowałem szybkiego testu. W przypadku g ++ 4.3.2 używającego -O2, wyjście asemblera wyraźnie pokazuje to za pomocą jednej idivlinstrukcji i używając wyników w eax i edx. Byłbym zszokowany, gdyby tak się nie stało.
Fred Larson

2
@EuriPinhollow Zgadzam się, że to nie jest kwestia dotycząca x86. Podałem to tylko jako przykład, z wysoce wątpliwym założeniem, że inne architektury prawdopodobnie robią coś podobnego.
cnicutar

3
Musisz jednak powiedzieć kompilatorowi, aby zoptymalizował, przynajmniej dla g ++. Prosty eksperyment z g ++ 6.3.0 na x86_64 wykazał, że bez żadnej optymalizacji otrzymujesz dwie idivlinstrukcje, ale z -O1lub większym otrzymujesz jedną. Jak mówi podręcznik: „Bez opcji optymalizacji… Instrukcje są niezależne” .
Tom Zych

81

std::div zwraca strukturę zawierającą zarówno wynik, jak i resztę.


5
Jestem ciekawy, czy jest to rzeczywiście bardziej wydajne niż, powiedzmy, opcja 1 w nowoczesnym kompilatorze.
Greg Howell

2
Fajnie, nie wiedziałem. Czy to jest szybsze?

Miły. Czy zdarzyło Ci się wiedzieć, czy gdzieś długo jest zaimplementowany?
Cookie

@Cookie: C ++ 03 nie ma pojęcia long long, ale jest bardzo prawdopodobne, że Twój kompilator ma long longprzeciążenie std::divjako rozszerzenie.
ildjarn

@Greg: Nie sądzę, że tak jest, biorąc pod uwagę, że najprawdopodobniej musi zapisać wyniki do struktury w pamięci i zwrócić ją, co (chyba że kompiluje się jako inline, a dostęp do struktury jest zoptymalizowany) jest większą karą niż zrobienie dodatkowa instrukcja dzielenia.
pezcode

28

Przynajmniej na x86 g ++ 4.6.1 po prostu używa IDIVL i pobiera oba z tej pojedynczej instrukcji.

Kod C ++:

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

Kod x86:

__Z3fooiiPiS_:
LFB4:
    movq    %rdx, %r8
    movl    %edi, %edx
    movl    %edi, %eax
    sarl    $31, %edx
    idivl   %esi
    movl    %eax, (%r8)
    movl    %edx, (%rcx)
    ret

Czy kolejność ma znaczenie? Na przykład, jeśli wykonujesz iterację po a /=- może być konieczne użycie zmiennej tymczasowej, aby najpierw zachować dzielenie.
AnnanFay,

@Annan Wtedy nie miało to znaczenia, a obecnie nie ma. Kompilatory są wystarczająco sprytne, aby zmienić jego kolejność.
Sahsahae

10

Przykładowy kod testujący div () i połączony podział i mod. Skompilowałem je za pomocą gcc -O3, musiałem dodać wywołanie doNothing, aby zatrzymać kompilator przed optymalizacją wszystkiego (wyjście byłoby 0 dla rozwiązania dzielenia + mod).

Dodaj szczyptę soli:

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

Wyjścia: 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);
}

Wyjścia: 25



3

Mając wszystko inne na równi, najlepszym rozwiązaniem jest takie, które jasno wyraża Twoje zamiary. Więc:

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

jest prawdopodobnie najlepszą z trzech przedstawionych przez Ciebie opcji. Jak zauważono w innych odpowiedziach, divmetoda obliczy dla Ciebie obie wartości naraz.


3

Nie możesz ufać g ++ 4.6.3 tutaj z 64-bitowymi liczbami całkowitymi na 32-bitowej platformie Intel. a / b jest obliczane przez wywołanie divdi3, a% b jest obliczane przez wywołanie moddi3. Mogę nawet wymyślić przykład, który oblicza a / b i ab * (a / b) z tymi wywołaniami. Więc używam c = a / b i ab * c.

Metoda div wywołuje funkcję, która oblicza strukturę div, ale wywołanie funkcji wydaje się nieefektywne na platformach, które mają sprzętową obsługę typu integralnego (tj. 64-bitowe liczby całkowite na 64-bitowych platformach intel / amd).


-4

Możesz użyć modułu, aby uzyskać resztę. Chociaż odpowiedź @ cnicutar wydaje się czystsza / bardziej bezpośrednia.


2
Tak, na oryginalnym plakacie zastosowano operator modułu, pytanie, jak sprawić, by był efektywny.
Mark Lakata
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.