Plafon cepat dari divisi integer di C / C ++

262

Diberikan nilai integer xdan y, C dan C ++ keduanya kembali sebagai hasil bagi q = x/ylantai dari titik mengambang yang setara. Saya tertarik pada metode mengembalikan langit-langit sebagai gantinya. Sebagai contoh, ceil(10/5)=2dan ceil(11/5)=3.

Pendekatan yang jelas melibatkan sesuatu seperti:

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

Ini membutuhkan perbandingan dan penggandaan ekstra; dan metode lain yang pernah saya lihat (sebenarnya digunakan) melibatkan casting sebagai a floatatau double. Apakah ada metode yang lebih langsung yang menghindari multiplikasi tambahan (atau divisi kedua) dan cabang, dan itu juga menghindari casting sebagai angka floating point?

dan dan
sumber
70
instruksi pembagian sering mengembalikan hasil bagi dan sisa pada saat yang sama sehingga tidak perlu mengalikan, q = x/y + (x % y != 0);cukup saja
phuclv
2
@ LưuVĩnhPhúc bahwa komentar harus menjadi jawaban yang diterima, imo.
Andreas Grapentin
1
@ LưuVĩnhPhúc Serius Anda perlu menambahkan itu sebagai jawabannya. Saya hanya menggunakannya untuk jawaban saya selama tes kodilitas. Itu bekerja seperti pesona meskipun saya tidak yakin bagaimana mod bagian dari jawaban bekerja tetapi berhasil.
Zachary Kraus
2
@AndreasGrapentin jawaban di bawah ini oleh Miguel Figueiredo diajukan hampir setahun sebelum Lưu Vĩnh Phúc meninggalkan komentar di atas. Sementara saya mengerti betapa menarik dan elegannya solusi Miguel, saya tidak cenderung untuk mengubah jawaban yang diterima di akhir ini. Kedua pendekatan tetap sehat. Jika Anda merasa cukup kuat tentang hal itu, saya sarankan Anda menunjukkan dukungan Anda dengan memilih-up jawaban Miguel di bawah ini.
dan dan
1
Aneh, saya belum melihat pengukuran atau analisis yang waras dari solusi yang diusulkan. Anda berbicara tentang kecepatan di dekat-tulang, tetapi tidak ada diskusi tentang arsitektur, pipa, instruksi percabangan dan siklus jam.
Rado

Jawaban:

394

Untuk angka positif

unsigned int x, y, q;

Untuk mengumpulkan ...

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

atau (menghindari overflow dalam x + y)

q = 1 + ((x - 1) / y); // if x != 0
Sparky
sumber
6
@bitc: Untuk bilangan negatif, saya percaya C99 menentukan round-to-zero, begitu x/yjuga langit-langit divisi. C90 tidak menentukan cara membulatkan, dan saya pikir standar C ++ saat ini juga tidak.
David Thornley
6
Lihat posting Eric Lippert: stackoverflow.com/questions/921180/c-round-up/926806#926806
Mashmagar
3
Catatan: Ini mungkin meluap. q = ((lama) x + y - 1) / y tidak akan. Kode saya lebih lambat, jadi jika Anda tahu bahwa nomor Anda tidak akan meluap, Anda harus menggunakan versi Sparky.
Jørgen Fogh
1
@ bitc: Saya yakin maksud David adalah Anda tidak akan menggunakan perhitungan di atas jika hasilnya negatif - Anda hanya akan menggunakanq = x / y;
caf
12
Yang kedua memiliki masalah di mana x adalah 0. ceil (0 / y) = 0 tetapi mengembalikan 1.
Omry Yadan
78

Untuk angka positif:

    q = x/y + (x % y != 0);
Miguel Figueiredo
sumber
5
instruksi pembagian arsitektur yang paling umum juga mencakup sisa dalam hasilnya sehingga ini hanya membutuhkan satu divisi dan akan sangat cepat
phuclv
58

Jawaban Sparky adalah salah satu cara standar untuk mengatasi masalah ini, tetapi seperti yang saya juga tulis dalam komentar saya, Anda berisiko meluap. Ini dapat diselesaikan dengan menggunakan tipe yang lebih luas, tetapi bagaimana jika Anda ingin membagi long longs?

Jawaban Nathan Ernst memberikan satu solusi, tetapi melibatkan pemanggilan fungsi, deklarasi variabel dan kondisional, yang membuatnya tidak lebih pendek dari kode OP dan bahkan mungkin lebih lambat, karena lebih sulit untuk dioptimalkan.

Solusi saya adalah ini:

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

Ini akan sedikit lebih cepat daripada kode OP, karena modulo dan divisi dilakukan dengan menggunakan instruksi yang sama pada prosesor, karena kompiler dapat melihat bahwa mereka setara. Setidaknya gcc 4.4.1 melakukan optimasi ini dengan flag -O2 pada x86.

Secara teori, kompiler mungkin menyejajarkan fungsi memanggil kode Nathan Ernst dan memancarkan hal yang sama, tetapi gcc tidak melakukan itu ketika saya mengujinya. Ini mungkin karena itu akan mengikat kode yang dikompilasi ke versi tunggal dari perpustakaan standar.

Sebagai catatan terakhir, semua ini tidak penting pada mesin modern, kecuali jika Anda berada dalam loop yang sangat ketat dan semua data Anda ada di register atau L1-cache. Kalau tidak, semua solusi ini akan sama cepatnya, kecuali kemungkinan Nathan Ernst, yang mungkin lebih lambat secara signifikan jika fungsinya diambil dari memori utama.

Jørgen Fogh
sumber
3
Ada cara yang lebih mudah untuk memperbaiki overflow, cukup kurangi y / y:q = (x > 0)? 1 + (x - 1)/y: (x / y);
Ben Voigt
-1: ini adalah cara yang tidak efisien, karena memperdagangkan * murah untuk% yang mahal; lebih buruk dari pendekatan OP.
Yves Daoust
2
Tidak. Seperti yang saya jelaskan di jawaban, operator% bebas ketika Anda sudah melakukan pembagian.
Jørgen Fogh
1
Lalu q = x / y + (x % y > 0);apakah lebih mudah daripada ? :ekspresi?
Han
Itu tergantung pada apa yang Anda maksud dengan "lebih mudah." Mungkin atau mungkin tidak lebih cepat, tergantung pada bagaimana kompiler menerjemahkannya. Dugaan saya akan lebih lambat tetapi saya harus mengukurnya untuk memastikan.
Jørgen Fogh
18

Anda bisa menggunakan divfungsi di cstdlib untuk mendapatkan hasil bagi & sisa dalam satu panggilan dan kemudian menangani langit-langit secara terpisah, seperti di bawah ini

#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;
}
Nathan Ernst
sumber
12
Sebagai kasus menarik dari ledakan ganda, Anda juga bisa return res.quot + !!res.rem;:)
Sam Harwell
Bukankah ldiv selalu mempromosikan argumen menjadi panjang? Dan bukankah itu menghabiskan biaya, casting atau down-casting?
einpoklum
12

Bagaimana dengan ini? (membutuhkan y non-negatif, jadi jangan gunakan ini dalam kasus langka di mana y adalah variabel tanpa jaminan non-negatif)

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

Saya mengurangi y/ymenjadi satu, menghilangkan istilah x + y - 1dan dengan itu kemungkinan meluap.

Saya menghindari x - 1membungkus ketika xadalah tipe unsigned dan berisi nol.

Untuk yang ditandatangani x, negatif dan nol masih bergabung menjadi satu wadah.

Mungkin bukan manfaat besar pada CPU serba guna modern, tetapi ini akan jauh lebih cepat dalam sistem embedded daripada jawaban yang benar lainnya.

Ben Voigt
sumber
Anda yang lain akan selalu mengembalikan 0, tidak perlu menghitung apa pun.
Ruud Althuizen
@Ruud: tidak benar. Pertimbangkan x = -45 dan y = 4
Ben Voigt
7

Ada solusi untuk positif dan negatif xtetapi hanya untuk positif ydengan hanya 1 divisi dan tanpa cabang:

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

Catatan, jika xpositif maka pembagian adalah menuju nol, dan kita harus menambahkan 1 jika pengingat bukan nol.

Jika xnegatif maka pembagian menuju nol, itulah yang kami butuhkan, dan kami tidak akan menambahkan apa pun karena x % ytidak positif

RiaD
sumber
menarik, karena ada kasus umum dengan y menjadi konstan
Wolf
1
mod memerlukan pembagian jadi itu bukan hanya 1 divisi di sini, tapi mungkin kompilator dapat mengoptimalkan dua divisi serupa menjadi satu.
M.kazem Akhgary
4

Ini berfungsi untuk angka positif atau negatif:

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

Jika ada sisa, periksa untuk melihat apakah xdan ydari tanda yang sama dan tambahkan 1sesuai.

Mark Conway
sumber
3

Saya lebih suka berkomentar tetapi saya tidak memiliki perwakilan yang cukup tinggi.

Sejauh yang saya ketahui, untuk argumen positif dan pembagi yang merupakan kekuatan 2, ini adalah cara tercepat (diuji dalam CUDA):

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

Hanya untuk argumen positif umum, saya cenderung melakukannya seperti ini:

q = x/y + !!(x % y);
Anroca
sumber
Akan menarik untuk melihat bagaimana q = x/y + !!(x % y);menghadapi q = x/y + (x % y == 0);dan q = (x + y - 1) / y;solusi kinerja bijaksana dalam CUDA kontemporer.
Greg Kramida
-2

Kompilasi dengan O3, Kompiler melakukan optimasi dengan baik.

q = x / y;
if (x % y)  ++q;
dhb
sumber