C / C ++ 'da bir tamsayı bölümünün hızlı tavanı


262

Verilen tamsayı değerleri xve yC ve C ++ her ikisi q = x/yde kayan nokta eşdeğerinin zemini olarak geri döner . Bunun yerine tavanı döndürme yöntemiyle ilgileniyorum. Örneğin, ceil(10/5)=2veceil(11/5)=3 .

Açık yaklaşım şöyle bir şey içerir:

q = x / y;
if (q * y < x) ++q;

Bu ekstra bir karşılaştırma ve çarpma gerektirir; ve gördüğüm diğer yöntemler (aslında kullanıldı) bir floatveya olarak döküm yapmayı içerir double. Ek çarpma (veya ikinci bir bölme) ve daldan kaçınan ve aynı zamanda bir kayan nokta sayısı olarak dökümü önleyen daha doğrudan bir yöntem var mı?


70
bölme talimatı çoğu zaman hem bölümü hem de geri kalanını aynı anda döndürür, bu yüzden çoğalmaya gerek yoktur, sadece q = x/y + (x % y != 0);yeterlidir
phuclv

2
@ LưuVĩnhPhúc bu yorum kabul edilen cevap olmalı, imo.
Andreas Grapentin 09

1
@ LưuVĩnhPhúc Cidden bunu cevap olarak eklemeniz gerekiyor. Bunu sadece bir kodluluk testi sırasında cevabım için kullandım. Cevabın mod kısmının nasıl çalıştığından emin olmasam da bir cazibe gibi çalıştı ama işi yaptı.
Zachary Kraus

2
@AndreasGrapentin Miguel Figueiredo'nun aşağıdaki cevabı Lưu Vĩnh Phúc yukarıdaki yorumu bırakmadan yaklaşık bir yıl önce gönderildi. Miguel'in çözümünün ne kadar çekici ve zarif olduğunu anlasam da, kabul edilen cevabı bu son tarihte değiştirmeye meyilli değilim. Her iki yaklaşım da sağlam kalır. Bu konuda yeterince güçlü hissediyorsanız, desteklerinizi Miguel'in cevabını aşağıda oylayarak göstermenizi öneririm.
ve

1
Tuhaf, önerilen çözümlerin aklı başında bir ölçüm ya da analizi görmedim. Kemiğe yakın hız hakkında konuşuyorsunuz, ancak mimariler, boru hatları, dallanma talimatları ve saat döngüleri hakkında tartışma yok.
Rado

Yanıtlar:


394

Pozitif sayılar için

unsigned int x, y, q;

Yuvarlamak ...

q = (x + y - 1) / y;

veya (x + y cinsinden taşmayı önleme)

q = 1 + ((x - 1) / y); // if x != 0

6
@bitc: Negatif sayılar için, C99'un sıfıra yuvarladığını x/y, bölümün tavanının da olduğunu düşünüyorum. C90 nasıl yuvarlanacağını belirtmedi ve mevcut C ++ standardının da yaptığını sanmıyorum.
David Thornley

6
Eric
Lippert'in

3
Not: Bu taşabilir. q = ((uzun uzun) x + y - 1) / y değişmez. Kodum daha yavaş olsa da, sayılarınızın taşmayacağını biliyorsanız Sparky'nin sürümünü kullanmalısınız.
Jørgen Fogh

1
@bitc: David'in amacı, sonuç negatifse yukarıdaki hesaplamayı kullanmayacağınızdırq = x / y;
caf

12
İkincisinin x'in 0 olduğu bir sorunu vardır.
Tavan

78

Pozitif sayılar için:

    q = x/y + (x % y != 0);

5
en yaygın mimarinin bölme talimatı da sonuçta kalanları içerir, bu yüzden bu gerçekten sadece bir bölüme ihtiyaç duyar ve çok hızlı olur
phuclv

58

Sparky'nin cevabı, bu sorunu çözmenin standart bir yoludur, ancak yorumumda da yazdığım gibi, taşma riski taşıyorsunuz. Bu, daha geniş bir tür kullanılarak çözülebilir, ancak bölmek istersenizlong long ?

Nathan Ernst'ün cevabı bir çözüm sunuyor, ancak bir işlev çağrısı, değişken bir bildirim ve bir koşul içeriyor, bu da OPs kodundan daha kısa ve muhtemelen daha da yavaş yapıyor, çünkü optimize etmek daha zor.

Benim çözümüm şudur:

q = (x % y) ? x / y + 1 : x / y;

OPs kodundan biraz daha hızlı olacaktır, çünkü modulo ve bölme işlemci üzerinde aynı talimat kullanılarak gerçekleştirilir, çünkü derleyici eşdeğer olduğunu görebilir. En azından gcc 4.4.1 bu optimizasyonu x86'da -O2 bayrağıyla gerçekleştirir.

Teoride derleyici, Nathan Ernst kodundaki fonksiyon çağrısını sıralayabilir ve aynı şeyi yayabilir, ancak gcc test ettiğimde bunu yapmadı. Bunun nedeni, derlenmiş kodu standart kitaplığın tek bir sürümüne bağlaması olabilir.

Son bir not olarak, son derece sıkı bir döngüde olmanız ve tüm verileriniz kayıtlarda veya L1-önbelleğinde olması haricinde, bunların hiçbiri modern bir makinede önemli değildir. Aksi takdirde, muhtemelen Nathan Ernst'in dışında, bu çözümlerin tümü eşit derecede hızlı olacaktır, bu fonksiyonun ana bellekten alınması gerekiyorsa önemli ölçüde daha yavaş olabilir.


3
Taşmayı düzeltmenin daha kolay bir yolu vardı, sadece y / y'yi azaltın:q = (x > 0)? 1 + (x - 1)/y: (x / y);
Ben Voigt

-1: Bu, pahalı bir% olarak ucuz bir * işlem yaptığından verimsiz bir yoldur; OP yaklaşımından daha kötü.
Yves Daoust

2
Hayır. Cevabımda açıkladığım gibi, bölümü zaten yaptığınızda% operatörü ücretsizdir.
Jørgen Fogh

1
O zaman anlatımdan q = x / y + (x % y > 0);daha kolay ? :mı?
Han

"Daha kolay" ile ne demek istediğinize bağlı. Derleyicinin onu nasıl çevirdiğine bağlı olarak daha hızlı olabilir veya olmayabilir. Benim tahminim daha yavaş olurdu ama emin olmak için ölçmek zorunda kalacağım.
Jørgen Fogh

18

divTek bir çağrıda bölüm ve kalanı almak için cstdlib'deki işlevi kullanabilir ve daha sonra, aşağıdaki gibi tavanı ayrı ayrı kullanabilirsiniz.

#include <cstdlib>
#include <iostream>

int div_ceil(int numerator, int denominator)
{
        std::div_t res = std::div(numerator, denominator);
        return res.rem ? (res.quot + 1) : res.quot;
}

int main(int, const char**)
{
        std::cout << "10 / 5 = " << div_ceil(10, 5) << std::endl;
        std::cout << "11 / 5 = " << div_ceil(11, 5) << std::endl;

        return 0;
}

12
Çift patlamanın ilginç bir örneği olarak, şunları da yapabilirsiniz return res.quot + !!res.rem;:)
Sam Harwell

Ldiv her zaman argümanları uzun zamana yaymaz mı? Üstelik veya aşağı döküm bir ücrete tabi değil mi?
einpoklum

12

Buna ne dersin? (negatif olmayan y gerektirir, bu nedenle y'nin olumsuzluk garantisi olmayan bir değişken olduğu nadir durumlarda bunu kullanmayın)

q = (x > 0)? 1 + (x - 1)/y: (x / y);

Ben azaltılmış y/yterimini ortadan kaldırarak, birine x + y - 1taşma ihtimalini ve onunla.

İmzasız bir tür olduğunda ve sıfır içerdiğinde x - 1etrafta dolaşmaktan kaçınırım x.

İmzalı x, negatif ve sıfır hala tek bir vakada birleşir.

Muhtemelen modern bir genel amaçlı CPU üzerinde büyük bir fayda değil, ancak bu gömülü bir sistemde diğer doğru cevaplardan daha hızlı olacaktır.


Başka her zaman 0 döndürür, hiçbir şey hesaplamaya gerek yoktur.
Ruud Althuizen

@ Ruud: doğru değil. X = -45 ve y = 4'ü düşünün
Ben Voigt

7

Hem pozitif hem de negatif için bir çözüm var, xancak ysadece 1 bölümlü ve şubesiz pozitif için bir çözüm var :

int ceil(int x, int y) {
    return x / y + (x % y > 0);
}

Olumluysa, xbölüm sıfıra doğrudur ve hatırlatıcı sıfır değilse 1 eklemeliyiz.

xNegatif ise , bölünme sıfıra doğrudur, ihtiyacımız olan budur ve hiçbir şey eklemeyeceğiz çünkü x % ypozitif değil


ilginç, çünkü y'nin sabit olduğu yaygın durumlar var
Wolf

1
mod bölme gerektirir, bu yüzden burada sadece 1 bölme değil, belki de complier iki benzer bölmeyi bire optimize edebilir.
M.kazem Akhgary

4

Bu, pozitif veya negatif sayılar için çalışır:

q = x / y + ((x % y != 0) ? !((x > 0) ^ (y > 0)) : 0);

Geriye kalanlar varsa x,y aynı işarete sahiptir ve ekler 1buna göre.


3

Ben daha çok yorum olurdu ama yeterince yüksek bir temsilcisi yok.

Bildiğim kadarıyla, olumlu argümanlar ve 2'nin gücü olan bir bölen için, bu en hızlı yoldur (CUDA'da test edildi):

//example y=8
q = (x >> 3) + !!(x & 7);

Sadece jenerik pozitif argümanlar için, ben böyle yapmak eğilimindedir:

q = x/y + !!(x % y);

Çağdaş CUDA'da nasıl q = x/y + !!(x % y);istiflendiğini q = x/y + (x % y == 0);ve q = (x + y - 1) / y;performans açısından çözümleri görmek ilginç olacaktır .
Greg Kramida


-2

O3 ile derleyin, derleyici optimizasyonu iyi yapar.

q = x / y;
if (x % y)  ++q;
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.