C ++ Tamsayı bölme ve kalanı almanın en iyi yolu


103

Merak ediyorum, a'yı b'ye bölmek istiyorsam ve hem c sonucuyla hem de kalanla ilgileniyorsam (örneğin, saniyelerim var ve bunu dakikalara ve saniyelere bölmek istiyorum), bunun için en iyi yol nedir? bunun hakkında gitmek?

Olurdu

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

veya

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

veya

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

veya

belki ikisini birden veren büyülü bir işlev vardır?


5
Aşağıdaki tüm cevaplar mantıklı görünüyor, sadece şunu eklemek isterim ki double(son öğeniz) ile uğraşmanın bana kötü bir fikir gibi göründüğünü, sıraya girmeyen sayılarla karşılaşacaksınız ve performans açısından size mal olabilir ve yürütülebilir boyut (belirli gömülü sistemlerde benim için her zaman bir sorundu).
nhed

2
Üçüncüsü bir KÖTÜ seçenektir: tmp = 54.99999999999999943157 ise ne olur? Bu, eski tarz oyuncu kadrosunun asla yapılacak akıllıca bir şey olmadığını söyledi.
jimifiki

Yanıtlar:


98

X86'da kalan kısım, bölümün kendisinin bir yan ürünüdür, bu nedenle yarı iyi bir derleyici onu sadece kullanabilmelidir (ve bir divdaha yapmamalıdır ). Bu muhtemelen diğer mimarilerde de yapılır.

Talimat: DIVsrc

Not: İmzasız bölme. Akümülatörü (AX) "src" ye böler. Bölen bir bayt değeriyse, sonuç AL'ye ve kalan AH'ye konur . Bölen bir kelime değeriyse, DX: AX "src" ye bölünür ve sonuç AX'te saklanır ve kalan DX'te saklanır .

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

9
Sanırım pek çok kişi ilkokuldan bir bölüm yaparken geri kalanını bedavaya aldığınızı biliyor. Gerçek soru şudur: derleyicilerimiz bundan yararlanacak kadar akıllı mı?

1
@jdv: Şaşırmam. Bu çok basit bir optimizasyon.
Jon Purdy

68
Hızlı bir test denedim. -O2 kullanan g ++ 4.3.2 ile, assembler çıktısı, bunu bir idivlkomut kullanarak ve eax ve edx'teki sonuçları kullanarak açıkça gösterir . Olmasa şok olurdum.
Fred Larson

2
@EuriPinhollow Kabul ediyorum Bu x86 ile ilgili bir soru değil. Bunu sadece bir örnek olarak verdim, diğer mimarilerin muhtemelen benzer bir şey yaptığına dair oldukça şüpheli varsayımla.
cnicutar

3
Derleyiciye en azından g ++ için optimize etmesini söylemeniz gerekir. X86_64 üzerinde g ++ 6.3.0 ile yapılan basit bir deney, hiç optimizasyon olmadan iki idivltalimat aldığınızı , ancak bununla -O1veya daha fazlasıyla bir tane aldığınızı ortaya koydu . Kılavuzun dediği gibi, "Herhangi bir optimizasyon seçeneği olmadan… İfadeler bağımsızdır" .
Tom Zych

81

std::div hem sonucu hem de kalanı olan bir yapı döndürür.


5
Bunun modern bir derleyicideki 1. seçenekten daha verimli olup olmadığını merak ediyorum.
Greg Howell

2
Güzel, bilmiyordum. Daha hızlı mı?

Güzel. Bir yerde uzun süre uygulanıp uygulanmadığını biliyor musunuz?
Cookie

@Cookie: C ++ 03'ün kavramı yoktur long long, ancak büyük olasılıkla derleyicinizin bir uzantı olarak long longaşırı yüklemesi vardır std::div.
ildjarn

@Greg: Muhtemelen sonuçları bellekteki bir yapıya yazıp geri döndürmesi gerektiğine göre öyle olduğunu sanmıyorum, ki bu (satır içi olarak derlenmedikçe ve yapı erişimi optimize edilmedikçe) yapmaktan daha büyük bir ceza. ekstra bölme talimatı.
pezcode

28

En azından x86'da, g ++ 4.6.1 sadece IDIVL kullanır ve her ikisini de bu tek komuttan alır.

C ++ kodu:

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

x86 kodu:

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

Sipariş önemli mi? Örneğin, a üzerinde yineleme yapıyorsanız, /=önce bölmeyi korumak için geçici bir değişken kullanmanız gerekebilir.
AnnanFay

@Annan O zamanlar önemli değildi ve bugünlerde de yok. Derleyiciler onu yeniden sıralayacak kadar akıllıdır.
Sahsahae

10

Örnek kod testi div () ve birleşik bölme ve mod. Bunları gcc -O3 ile derledim, derleyicinin her şeyi optimize etmesini durdurmak için doNothing çağrısını eklemem gerekiyordu (bölme + mod çözümü için çıktı 0 olurdu).

Bir tuz tanesi ile alın:

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

Çıkışlar: 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);
}

Çıkışlar: 25



3

Diğer her şey eşit olduğunda, en iyi çözüm, niyetinizi açıkça ifade eden çözümdür. Yani:

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

muhtemelen sunduğunuz üç seçenekten en iyisidir. Ancak diğer yanıtlarda belirtildiği gibi, divyöntem sizin için her iki değeri de aynı anda hesaplayacaktır.


3

32 bitlik bir intel platformunda 64 bit tamsayılarla burada g ++ 4.6.3'e güvenemezsiniz. a / b, bir divdi3 çağrısı ile hesaplanır ve a% b, bir moddi3 çağrısı ile hesaplanır. Bu çağrılarla a / b ve ab * (a / b) yi hesaplayan bir örnek bile bulabilirim. Bu yüzden c = a / b ve ab * c kullanıyorum.

Div yöntemi, div yapısını hesaplayan bir işleve çağrı verir, ancak integral türü için donanım desteğine sahip platformlarda (yani 64 bit intel / amd platformlarında 64 bit tamsayılar) bir işlev çağrısı yetersiz görünür.


-4

Kalanı elde etmek için bir modül kullanabilirsiniz. @ Cnicutar'ın cevabı daha temiz / daha doğrudan görünüyor.


2
Evet, orijinal poster modül operatörünü kullandı, soru onu nasıl verimli hale getireceğidir.
Mark Lakata
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.