web-dev-qa-db-ja.com

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

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

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

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

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

222
andand

正数の場合

unsigned int x, y, q;

切り上げるには...

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

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

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

正数の場合:

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

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を除き、同等に高速になります。

57
Jørgen Fogh

Cstdlibでdiv関数を使用して、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;
}
16
Nathan Ernst

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

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

y/yを1つに減らし、x + y - 1という用語を削除し、オーバーフローの可能性を排除しました。

xが符号なしの型であり、ゼロを含む場合、x - 1がラップアラウンドするのを避けます。

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

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

12
Ben Voigt

正と負のxの両方に対する解決策がありますが、正のyに対しては、1部門のみで分岐はありません。

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

xが正の場合、除算はゼロに向かっており、リマインダーがゼロでない場合は1を追加する必要があります。

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

5
RiaD

これは、正または負の数に対して機能します。

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

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

4
Mark Conway

簡略化された汎用形式、

int div_up(int n, int d) {
    return n / d + (((n < 0) ^ (d > 0)) && (n % d));
} //i.e. +1 iff (not exact int && positive result)

より一般的な答えについては、 明確に定義された丸め戦略を使用した整数除算用のC++関数

2
Atif Hussain

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

私の知る限り、+ ve&pow 2の場合、これが最速の方法です(CUDAでテスト済み)

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

そうでなければ(これも+ veのみ)私はそうする傾向があります:

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