C / C ++での整数除算の高速上限


262

与えられた整数値xy、CとC ++は両方ともq = x/y、等価の浮動小数点の床を商として返します。代わりに天井を返す方法に興味があります。たとえば、ceil(10/5)=2およびceil(11/5)=3

明らかなアプローチには、次のようなものが含まれます。

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

これには、追加の比較と乗算が必要です。そして私が見た(実際に使用された)他の方法は、floatまたはとしてキャストすることを含みdoubleます。追加の乗算(または2番目の除算)と分岐を回避し、浮動小数点数としてのキャストを回避するより直接的な方法はありますか?


70
除算命令は、商と剰余の両方を同時に返すことが多いため、乗算する必要はなく、q = x/y + (x % y != 0);十分です
phuclv

2
@LưuVĩnhPhúcそのコメントは受け入れられる答えであるはずです、imo。
Andreas Grapentin 2014

1
@LưuVĩnhPhúc真剣にそれを答えとして追加する必要があります。私は、それを性別テストの際の答えに使用しました。回答のmod部分がどのように機能するかはわかりませんが、それは魅力的でした。
ザカリークラウス2014

2
@AndreasGrapentinミゲルフィゲイレドによる以下の回答は、ルーヴァンプックが上記のコメントを残すほぼ1年前に提出されました。私はミゲルのソリューションがいかに魅力的でエレガントであるかを理解していますが、この遅い日付で受け入れられた回答を変更する傾向はありません。どちらのアプローチも引き続き健全です。それについて十分に強く感じている場合は、以下のミゲルの回答に賛成票を投じて支持を表明することをお勧めします。
そして、2014

1
奇妙なことに、私は提案された解決策の正気な測定や分析を見たことがありません。骨組みに近い速度について話しますが、アーキテクチャ、パイプライン、分岐命令、およびクロックサイクルについては説明していません。
Rado

回答:


394

正の数の場合

unsigned int x, y, q;

切り上げるには...

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

または(x + yでのオーバーフローを回避)

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

6
@bitc:負の数の場合、C99 x/yはゼロへの丸めを指定するので、除算の上限もそうです。C90は丸め方を指定していませんでした。また、現在のC ++標準でもそうではないと思います。
David Thornley、2010

6
Eric Lippertの投稿をご覧ください:stackoverflow.com/questions/921180/c-round-up/926806#926806
Mashmagar

3
注:これはオーバーフローする可能性があります。q =((long long)x + y-1)/ yはできません。私のコードは遅いので、数値がオーバーフローしないことがわかっている場合は、Sparkyのバージョンを使用する必要があります。
ヨルゲンFogh

1
@bitc:デビッドのポイントは、結果が負の場合は上記の計算を使用しないということでした-使用するだけq = x / y;
caf

12
2番目のものには、xが0であるという問題があります
。ceil

78

正の数の場合:

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

5
最も一般的なアーキテクチャの除算命令には、結果に剰余も含まれるため、これは本当に1つの除算のみを必要とし、非常に高速です
phuclv

58

Sparkyの答えはこの問題を解決するための1つの標準的な方法ですが、私のコメントにも書いているように、オーバーフローのリスクがあります。これはより広い型を使用することで解決できますが、long longs を分割したい場合はどうでしょうか?

Nathan Ernstの答えは1つの解決策を提供しますが、関数呼び出し、変数宣言、および条件を含みます。最適化が難しいため、OPsコードより短くなく、おそらくさらに遅くなります。

私の解決策はこれです:

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

モジュロと除算はプロセッサ上で同じ命令を使用して実行されるため、コンパイラはそれらが同等であると認識できるため、OPsコードよりもわずかに高速になります。少なくともgcc 4.4.1は、x86で-O2フラグを使用してこの最適化を実行します。

理論的には、コンパイラはNathan Ernstのコードで関数呼び出しをインライン化して同じことを出力する可能性がありますが、gccはテストしたときにそれを行いませんでした。これは、コンパイルされたコードを標準ライブラリの単一バージョンに結び付けるためと考えられます。

最後に、非常にタイトなループにあり、すべてのデータがレジスタまたはL1キャッシュにある場合を除いて、最近のマシンではこれは問題になりません。それ以外の場合、これらのソリューションはすべて同じように高速になりますが、Nathan Ernstの場合は例外ですが、関数をメインメモリからフェッチする必要がある場合は、速度が大幅に低下する可能性があります。


3
修正オーバーフローの簡単な方法がありました、単にy / yのを減らす:q = (x > 0)? 1 + (x - 1)/y: (x / y);
ベンフォークト

-1:安価な*を高価な%と交換するため、これは非効率的な方法です。OPアプローチよりも悪い。
Yves Daoust 2014

2
いいえ、違います。答えで説明したように、すでに除算を実行している場合、%演算子は無料です。
ヨルゲンFogh

1
それでは表現q = x / y + (x % y > 0);より簡単? :ですか?
ハン、

それはあなたが「より簡単に」という意味に依存します。コンパイラがそれをどのように変換するかによって、高速になる場合とそうでない場合があります。私の推測は遅くなりますが、確かにそれを測定する必要があります。
ヨルゲンFogh

18

div以下のように、cstdlib の関数を使用して、1回の呼び出しで商と剰余を取得し、上限を個別に処理できます。

#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
ダブルバンの興味深いケースとして、次のこともできますreturn res.quot + !!res.rem;:)
Sam Harwell

ldivは常に引数をlong longに昇格しないのですか?そして、それはアップキャストやダウンキャストに費用はかかりませんか?
アインポクルム2016

12

これはどう?(非負のyが必要なので、yが非負の保証のない変数であるまれなケースではこれを使用しないでください)

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

y/yは1つに減らし、用語x + y - 1を排除し、それによりオーバーフローの可能性があります。

が符号なしの型でゼロが含まれている場合、x - 1ラップアラウンドを回避しxます。

signedのx場合、負とゼロは1つのケースに結合されます。

おそらく、現代の汎用CPUに大きなメリットはありませんが、組み込みシステムでは、他のどの正解よりもはるかに高速です。


elseは常に0を返します。何も計算する必要はありません。
Ruud Althuizen、2015年

@Ruud:真実ではない。x = -45とy = 4を検討してください
Ben Voigt、

7

ポジティブとネガティブの両方の解決策がありますが、ブランチが1つだけでブランチxがないポジティブの解決策だけがyあります。

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

もしノートは、xポジティブその後、除算がゼロに向かっている、とリマインダーがゼロでない場合、我々は1を追加する必要があります。

xが負の場合、除算はゼロに向かっています。それが必要なものであり、x % y正ではないため、何も追加しません。


興味深いのは、yが一定である一般的なケースがあるためです
Wolf

1
modは除算を必要とするため、ここでは1つの除算だけでなく、コンパイラーが2つの類似した除算を1つに最適化できる場合があります。
M.kazem Akhgary

4

これは正または負の数で機能します。

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

余りがある場合x、とyが同じ符号であるかどうかを確認し、1それに応じて追加します。


3

私はむしろコメントしたかったが、私は十分に高い担当者を持っていません。

私の知る限り、正の引数と2の累乗である除数の場合、これが最も速い方法です(CUDAでテスト済み)。

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

一般的な正の引数についてのみ、私はそうする傾向があります:

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

現代のCUDAでパフォーマンスとソリューションがどのようにq = x/y + !!(x % y);対抗q = x/y + (x % y == 0);し、 q = (x + y - 1) / y;解決するかを見るのは興味深いでしょう。
グレッグクラミダ


-2

O3でコンパイルすると、コンパイラーは最適化を適切に実行します。

q = x / y;
if (x % y)  ++q;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.