C ++ Cara terbaik untuk mendapatkan pembagian integer dan sisa

103

Saya hanya ingin tahu, jika saya ingin membagi a dengan b, dan tertarik pada hasil c dan sisanya (misalnya saya punya jumlah detik dan ingin membaginya menjadi menit dan detik), apa cara terbaik untuk pergi tentang itu?

Apakah itu

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

atau

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

atau

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

atau

mungkinkah ada fungsi magis yang memberikan keduanya sekaligus?

Kue kering
sumber
5
semua jawaban di bawah tampaknya masuk akal, saya hanya ingin menambahkan bahwa setiap penyia-nyiaan dengan double(item terakhir Anda) menurut saya seperti ide yang buruk, Anda akan berakhir dengan angka yang tidak sesuai, dan dapat merugikan Anda dalam kinerja dan ukuran yang dapat dieksekusi (selalu menjadi masalah bagi saya pada sistem tertanam tertentu).
nhed
2
Yang ketiga adalah opsi BURUK: bagaimana jika tmp = 54.999999999999943157? Meskipun demikian, casting gaya lama bukanlah hal yang pintar untuk dilakukan.
jimifiki

Jawaban:

98

Pada x86, sisanya adalah produk sampingan dari divisi itu sendiri sehingga setiap kompiler yang setengah layak harus dapat menggunakannya (dan tidak melakukan divlagi). Ini mungkin dilakukan pada arsitektur lain juga.

Instruksi: DIVsrc

Catatan: Divisi tidak bertanda tangan. Membagi akumulator (AX) dengan "src". Jika pembagi adalah nilai byte, hasilnya dimasukkan ke AL dan sisanya ke AH . Jika pembagi adalah nilai kata, maka DX: AX dibagi dengan "src" dan hasilnya disimpan di AX dan sisanya disimpan di DX .

int c = (int)a / b;
int d = a % b; /* Likely uses the result of the division. */
cnicutar
sumber
9
Saya rasa banyak orang yang tahu dari SD bahwa ketika melakukan pembagian Anda mendapatkan sisanya gratis. Pertanyaan sebenarnya adalah: apakah penyusun kami cukup pintar untuk memanfaatkan ini?
1
@jdv: Saya tidak akan terkejut. Ini adalah pengoptimalan yang sangat sederhana.
Jon Purdy
68
Saya mencoba tes cepat. Dengan g ++ 4.3.2 menggunakan -O2, keluaran assembler dengan jelas menunjukkannya menggunakan satu idivlinstruksi dan menggunakan hasil di eax dan edx. Saya akan terkejut jika tidak.
Fred Larson
2
@EuriPinhollow Setuju ini bukan pertanyaan tentang x86. Saya hanya memberikannya sebagai contoh, dengan asumsi yang sangat meragukan bahwa arsitektur lain mungkin melakukan hal serupa.
cnicutar
3
Anda memang perlu memberi tahu compiler untuk mengoptimalkan, setidaknya untuk g ++. Eksperimen sederhana dengan g ++ 6.3.0 di x86_64 mengungkapkan bahwa tanpa pengoptimalan sama sekali, Anda mendapatkan dua idivlinstruksi, tetapi dengan -O1atau lebih tinggi, Anda mendapatkan satu. Seperti yang dikatakan manual, "Tanpa opsi pengoptimalan apa pun ... Pernyataan bersifat independen" .
Tom Zych
80

std::div mengembalikan struktur dengan hasil dan sisa.

pezcode
sumber
5
Saya penasaran untuk mengetahui apakah ini sebenarnya lebih efisien daripada, katakanlah, opsi 1 pada kompiler modern.
Greg Howell
2
Bagus, saya tidak tahu. Apakah lebih cepat?
Bagus. Apakah Anda tahu jika ada yang diterapkan untuk waktu yang lama di suatu tempat?
Cookie
@ Cookie: C ++ 03 tidak memiliki konsep long long, tetapi sangat mungkin kompilator Anda memiliki long longkelebihan beban std::divsebagai ekstensi.
ildjarn
@Greg: Saya rasa tidak, mengingat kemungkinan besar harus menulis hasil ke struktur dalam memori dan mengembalikannya, yang (kecuali jika dikompilasi sebagai inline dan akses struct dioptimalkan) adalah hukuman yang lebih besar daripada melakukan instruksi divisi tambahan.
pezcode
28

Setidaknya pada x86, g ++ 4.6.1 hanya menggunakan IDIVL dan mendapatkan keduanya dari instruksi tunggal tersebut.

Kode C ++:

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

kode 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
sumber
Apakah urutannya penting? Misalnya jika Anda mengulangi a /=- Anda mungkin perlu menggunakan variabel sementara untuk menjaga pembagian terlebih dahulu.
AnnanFay
@Annan Tidak masalah dulu, dan sekarang tidak lagi. Penyusun cukup pintar untuk menyusun ulang.
Sahsahae
10

Contoh pengujian kode div () dan gabungan divisi & mod. Saya mengkompilasi ini dengan gcc -O3, saya harus menambahkan panggilan ke doNothing untuk menghentikan kompiler dari mengoptimalkan semuanya (output akan menjadi 0 untuk solusi divisi + mod).

Ambillah dengan sebutir garam:

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

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

Hasil: 25

Greg Howell
sumber
3

Semuanya sama, solusi terbaik adalah solusi yang dengan jelas mengungkapkan niat Anda. Begitu:

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

mungkin yang terbaik dari tiga opsi yang Anda berikan. Namun, seperti disebutkan dalam jawaban lain, divmetode ini akan menghitung kedua nilai untuk Anda sekaligus.

Jon
sumber
3

Anda tidak dapat mempercayai g ++ 4.6.3 di sini dengan integer 64 bit pada platform intel 32 bit. a / b dihitung dengan panggilan ke divdi3 dan% b dihitung dengan panggilan ke moddi3. Saya bahkan dapat memberikan contoh yang menghitung a / b dan ab * (a / b) dengan panggilan ini. Jadi saya menggunakan c = a / b dan ab * c.

Metode div memberikan panggilan ke fungsi yang menghitung struktur div, tetapi panggilan fungsi tampaknya tidak efisien pada platform yang memiliki dukungan perangkat keras untuk tipe integral (mis. 64 bit integer pada platform intel / amd 64 bit).

brac37
sumber
-4

Anda dapat menggunakan modulus untuk mendapatkan sisanya. Padahal jawaban @cnicutar terkesan lebih bersih / lugas.

Sinthet
sumber
2
Ya poster asli pakai operator modulus, pertanyaannya bagaimana biar efisien.
Mark Lakata