整数を2で除算するために使用するより良いオプションはどれですか?


406

整数を2で割るのに最適なオプションは次のうちどれですか。なぜですか。

テクニック1:

x = x >> 1;

テクニック2:

x = x / 2;

これxは整数です。


75
本当に結果をx再び割り当てたい場合は、どちらもこの方法では適切ではありません。それは、操作で表現しようとしている内容に応じて、x >>= 1またはのどちらかである必要がありますx /= 2。それがより高速であるという理由ではありません(最新のコンパイラは同等のバリアントをすべて同じ高速アセンブリにコンパイルします)が、混乱が少ないためです。
leftaroundabout

33
私はレフトアラウンドアバウトに同意しません。-しかし、私は多くのプログラミング言語で算術シフトと呼ばれる演算があり、符号ビットを所定の位置に保ち、そのため符号付き値に対して期待どおりに機能することは注目に値すると思います。構文は次のようになりx = x >>> 1ます。また、プラットフォームとコンパイラーによっては、シフトを使用して除算と乗算を手動で最適化することが非常に合理的である場合があることにも注意してください。-たとえば、マイクロコントローラーについて考える。乗算の直接ALUサポートなし。
JimmyB

36
モナドバインドのように見えるx /= 2ので、私は好みx >>= 1ます;)
fredoverflow '22年

19
@leftaroundabout- x = x / 2ではなく、書く方が読みやすいと思いx /= 2ます。主観的な好み多分:)
JimmyB

8
@HannoBinder:確かに主観的で、特に癖が多い。IMO、すべての算術演算子に⬜=組み合わせがある言語では、可能な限りこれらを使用する必要があります。これは事実上のノイズとプット重視削除xされた変更を一般的ながら、=オペレータは、むしろそれは古いものの完全に新しい価値の独立を取ることを示唆しています。-常には同様にそのポイントを持っているかもしれないが、その後、あなたは非常に有用を放棄する必要があるだろう(のみ算術演算子を知っている人はそう、それが読めるだそうという)を組み合わせ演算子を避け++--+=、あまりにも。
leftaroundabout

回答:


847

実行しようとしていることを最もよく表す操作を使用してください。

  • 数をビットのシーケンスとして扱う場合は、ビットシフトを使用します。
  • 数値として扱う場合は除算を使用してください。

これらは完全に等価ではないことに注意してください。負の整数の場合、結果が異なる可能性があります。例えば:

-5 / 2  = -2
-5 >> 1 = -3

(ideone)


20
元の質問も「最高」という用語について曖昧でした。速度、読みやすさ、学生をだますための試験問題などの点で「最高」...「最高」の意味の説明がない場合、これが最も正しい答えのようです。
レイ

47
C ++ 03では、両方とも負の数に対して定義された実装であり、同じ結果得られる可能性があります。C ++ 11では、除算は負の数に対して明確に定義されていますが、シフトは実装によって定義されています。
James Kanze、2012年

2
/の定義は初期のC標準で定義された実装(負の数の場合は切り上げまたは切り捨て)ですが、常に%(剰余演算子/剰余演算子)と一致している必要があります。
ctrl-alt-delor

7
「定義済みの実装」とは、コンパイラの実装者がいくつかの実装の選択肢の中から選択することを意味し、通常はかなりの制約があります。ここでは、1つの制約があること%と、/それはので、事業者は、正と負の両方のオペランドのために一貫していなければならない(a/b)*b+(a%b)==aにかかわらずの兆候の真実であるab。通常、作成者は、CPUから可能な限り最高のパフォーマンスを得る選択を行います。
RBerteig 2012年

6
それで、「コンパイラーはとにかくそれをシフトに変換する」と言う人は誰でも間違っていますよね?コンパイラーが負でない整数(定数または符号なし整数のいずれか)を処理していることを保証できない限り、シフトに変更することはできません
Kip

225

最初のものは分割のように見えますか?いいえx / 2。分割したい場合は、を使用してください。コンパイラーは、可能な場合はビットシフトを使用するように最適化できます(これは強度削減と呼ばれます)。これは、自分で行うと役に立たないマイクロ最適化になります。


15
多くのコンパイラーは、2の累乗による除算をビットシフトに変換しません。これは、符号付き整数の不適切な最適化になります。コンパイラーからのアセンブリー出力を見て、自分の目で確かめてください。
exDM69 2012年

1
IIRC私はそれを使用して、CUDAで並列削減をより速くしました(整数のdivを避けます)。しかし、これは1年以上前のことでしたが、今日のCUDAコンパイラはどれほど賢いのでしょうか。
ニルス

9
@ exDM69:多くのコンパイラーは、符号付き整数でもこれを実行し、符号付きに従ってそれらを調整します。これらを試すのに最適
PlasmaHH

19
@ exDM69:そして、それはどのように関連していますか?私は「常に」ではなく、「可能なら」と言いました。最適化が正しくない場合、手動で行っても正しくはなりません(加えて、前述のように、GCCは符号付き整数の適切な置き換えを理解するのに十分スマートです)。
Cat Plus Plus

4
WikiPediaのページを見ると、これは論争の的になっているようですが、強度の低下とは言いません。強度の低下とは、ループ内で、たとえば、ループ内の以前の値に加算することにより、乗算から加算へと減少する場合です。これは、のぞき穴の最適化であり、コンパイラはかなり確実に実行できます。
SomeCallMeTim

189

重ねる:x = x / 2; ここを使用することを支持する理由はたくさんあります:

  • それはあなたの意図をより明確に表現します(レジスタのビットをいじるビットなどを扱っていない場合)

  • コンパイラはこれをとにかくシフト演算に減らします

  • コンパイラがそれを削減せず、シフトよりも遅い操作を選択した場合でも、これが測定可能な方法でプログラムのパフォーマンスに影響を与える可能性は、それ自体が非常に小さいです(そして、それがかなりの影響を与える場合、実際のシフトを使用する理由)

  • 除算がより大きな式の一部になる場合、除算演算子を使用すると、優先順位が高くなる可能性が高くなります。

    x = x / 2 + 5;
    x = x >> 1 + 5;  // not the same as above
  • 符号付き演算は、上記の優先順位の問題よりもさらに複雑になる可能性があります

  • 繰り返しますが、コンパイラはとにかく既にこれを行います。実際、定数による除算を、2の累乗だけでなく、あらゆる種類の数値の一連のシフト、加算、乗算に変換します。これに関するさらに詳しい情報へのリンクについては、この質問を参照してください。

要するに、多分バグを導入する可能性が高まることを除いて、乗算または除算を実際に意味している場合は、シフトをコーディングして何も購入しません。コンパイラはこの種のものを適切なときにシフトに最適化するほど賢くなかったので、生涯を過ごしました。


5
優先順位の規則はありますが、括弧の使用に問題はないことも付け加えておきます。いくつかの量産コードを改造している間に、私は実際にははるかに読みやすいではなくa/b/c*da..d数値変数で示される)形式の何かを見ました(a*d)/(b*c)

1
パフォーマンスと最適化は、コンパイラとターゲットによって異なります。たとえば、市販のコンパイラーを購入しない限り、-O0より高いものが無効になっているマイクロコントローラーでいくつかの作業を行います。そのため、コンパイラーはビットシフトに分割されません。さらに、ビットシフトは1サイクルかかり、除算はこのターゲットで18サイクルかかります。マイクロコントローラーのクロック速度はかなり低いため、これは確かにパフォーマンスに大きな影響を与える可能性があります(ただし、コードによって異なります。その問題!)

4
@JackManey、オーバーフローが発生する可能性がある、a*dまたはb*c発生する可能性がある場合、可読性の低いフォームは同等ではなく、明らかな利点があります。PS私は括弧があなたの親友であることに同意します。
Mark Ransom

@MarkRansom-公平な点(たとえa/b/c*dオーバーフローがデータに深刻な問題があることを意味し、Cコードのパフォーマンスが重要なブロックではない場合でも、Rコードで遭遇したとしても)。

このコードx=x/2;は、奇数の負の値になることがないx>>=1場合xや、1つずつのエラーを気にしない場合よりも「明確」になります。そうでなければx=x/2;x>>=1;異なる意味があります。で計算される値が必要な場合は、または、または2での除算を使用して考えることができる他の定式化x>>=1よりも明確であると見なします。同様に、によって計算された値が必要な場合、それはよりも明確です。x = (x & ~1)/2x = (x < 0) ? (x-1)/2 : x/2x/=2((x + ((unsigned)x>>31)>>1)
スーパーキャット2013年

62

どちらが最善の選択肢であり、整数を2で除算するのはなぜですか?

あなたが何を意味するかに依存して最高

同僚に嫌われたり、コードを読みにくくしたりする場合は、最初のオプションを選択します。

数値を2で割りたい場合は、2番目の数値を使用します。

この2つは同等ではなく、数値が負の場合や大きな式の内部にある場合は同じように動作しません。ビットシフトの優先順位は+またはより低く-、除算の優先順位は高くなります。

コードの目的を表すコードを書く必要があります。パフォーマンスが気になる場合は、心配しないでください。オプティマイザは、この種のマイクロ最適化でうまく機能します。


58

divide(/)を使用してください。コンパイラーはそれに応じて最適化します。


34
コンパイラーそれに応じて最適化する必要があります。
Noctisスカイタワー

12
コンパイラがそれに応じて最適化しない場合は、より優れたコンパイラ使用する必要があります。
David Stone

3
@DavidStone:コンパイラは、シフトと同じくらい効率的になるように、1以外の定数による負の可能性がある符号付き整数の除算をどのプロセッサで最適化できますか?
スーパーキャット2013年

1
@supercat:それは良い点です。もちろん、値を符号なし整数に格納することもできます(符号付き/符号なしの不一致警告と組み合わせると、評判が悪いと感じます)。ほとんどのコンパイラには、最適化時に何かが真であると想定するように指示する方法もあります。 。私はそれを互換性マクロでラップしてASSUME(x >= 0); x /= 2;overのようなものを持っていることを望みますx >>= 1;が、それはまだ持ち出す重要なポイントです。
David Stone、

39

あなたが支持すべき他の答えに同意します x / 2意図がより明確であり、コンパイラーが最適化する必要があるため、ます。

ただし、優先さx / 2れるもう1つの理由は、が署名されていて負の場合x >> 1、の動作>>は実装依存であることです。xint

ISO C99標準のセクション6.5.7、箇条書き5から:

の結果E1 >> E2は、E1右シフトされたE2ビット位置です。場合は、E1符号なしのタイプを持っている場合、またはE1署名されたタイプと非負の値を有し、その結果の値の商の整数部分であるE1/ 2 E2。場合E1署名されたタイプと負の値を有する、得られた値は、実装定義です。


3
多くの実装x>>scalepowerが負の数値に対して定義する動作は、画面のレンダリングなどの目的で値を2の累乗で除算する場合に必要な動作ですが、x/scalefactor負の値に修正を適用しない限り、使用は間違っていることに注意してください。
スーパーキャット2013年

32

x / 2より明確で、x >> 1それほど高速ではありません(マイクロベンチマークによると、Java JVMの場合は約30%高速です)。他の人が指摘したように、負の数の場合、丸めはわずかに異なるため、負の数を処理する場合はこれを考慮する必要があります。一部のコンパイラは、数値が負にならないことがわかっx / 2ているx >> 1場合(これを確認できなかったと思っていても)、自動的にに変換する場合があります。

いくつかのショートカットが可能であるx / 2ため、(遅い)除算CPU命令を使用しない場合でも、それよりも遅いです。x >> 1

(これは、C / C ++質問、他のプログラミング言語は、より多くの演算子を持っている。するためにJavaの符号なし右シフトもありでx >>> 1再び異なっている、。それはそうすることを、正確に2つの値の平均値(平均)値を計算することができます(a + b) >>> 1意志aandの非常に大きな値でも平均値を返しますb。これは、たとえば、配列インデックスが非常に大きくなる可能性がある場合にバイナリ検索で必要になります。平均の計算に使用されていたため、バイナリ検索の多くのバージョンにバグがありまし(a + b) / 2。これは正しく機能しません。正しい解決策は、(a + b) >>> 1代わりに使用することです。)


1
コンパイラは変換できませんx/2x>>1場合によってはx負でもよいです。x>>1計算する値が必要な場合x/2は、同じ値を計算する式よりもほぼ確実に高速になります。
スーパーキャット2013年

あなたが正しいです。コンパイラは変換してもよいx/2x>>1、彼が値を知っていればマイナスではありません。回答を更新してみます。
Thomas Mueller、

コンパイラは、依然として回避ないdiv変換することによって、しかし命令x/2(x + (x<0?1:0)) >> 1(ここで>>、その符号ビットのシフト算術右シフトです)。これには4つの命令が必要です。値をコピーする、shr(regで符号ビットだけを取得する)、add、sar。 goo.gl/4F8Ms4
Peter Cordes

質問にはCおよびC ++のタグが付けられています。
Josh Sanford

22

クヌースは言った:

時期尚早の最適化は、すべての悪の根源です。

だから私は使用することをお勧めします x /= 2;

このようにコードは理解しやすく、また、この形式でのこの操作の最適化は、プロセッサにとって大きな違いを意味するものではないと思います。


4
(n + d)/ d =(n / d)+という公理(自然数と実数に適用されます)を維持するために整数が必要な場合、数値を2の累乗で縮小する好ましい方法をどのように検討しますか1?グラフィックのスケーリング時に公理を違反すると、結果に「継ぎ目」が表示されます。均一でゼロにほぼ対称なものが必要な場合、(n+8)>>4うまく機能します。署名付き右シフトを使用せずに、明確または効率的なアプローチを提供できますか?
スーパーキャット2013年

19

コンパイラーの出力を見て、判断に役立ててください。このテストをx86-64で
gcc(GCC)4.2.1 20070719 [FreeBSD]を使用して実行しました

godboltのコンパイラ出力もオンラインで参照してください。

コンパイラはsarlどちらの場合も(算術右シフト)命令を使用するため、2つの式の類似性が認識されます。除算を使用する場合、コンパイラは負の数を調整する必要もあります。そのために、符号ビットを最下位ビットにシフトし、それを結果に追加します。これにより、除算の場合と比較して、負の数をシフトするときの1つずれる問題が修正されます。
除算の場合は2つのシフトを実行しますが、明示的なシフトの場合は1つしか実行しないため、ここで他の回答によって測定されたパフォーマンスの違いのいくつかを説明できます。

アセンブリ出力を含むCコード:

除算の場合、入力は次のようになります

int div2signed(int a) {
  return a / 2;
}

これはコンパイルされます

    movl    %edi, %eax
    shrl    $31, %eax
    addl    %edi, %eax
    sarl    %eax
    ret

同様にシフトについて

int shr2signed(int a) {
  return a >> 1;
}

出力あり:

    sarl    %edi
    movl    %edi, %eax
    ret

何をしているのかに応じて、off-by-oneエラーを修正したり、(実際に必要なものと比較して)off-by-oneエラーを引き起こしたりするため、さらにコードを使用して修正する必要があります。必要なものがフロアの結果である場合、右シフトは、私が知っているどの代替手段よりも速くて簡単です。
スーパーキャット2013年

あなたが床を必要とする場合、あなたが「2で割る」とあなたが望むものを記述しますほとんどありません
マイケル・ドノヒュー

自然数と実数の両方の除算は、(n + d)/ d =(n / d)+1という公理を支持します。実数の除算も(-n)/ d =-(n / d)を支持します。これは自然数では意味がありません。整数に対して閉じていて、両方の公理を維持する除算演算子を持つことはできません。最初の公理はすべての数値に当てはまり、2番目の公理は実数のみに当てはまると言うのは、最初の公理は整数ではなく整数または実数に当てはまると言うよりも自然に思えます。さらに、2番目の公理が実際にどのような場合に役立つかについても知りたいです
スーパーキャット2013年

1
最初の公理を満たす整数除算法は、数直線をサイズの領域に分割しますd。このような分割は、多くの目的に役立ちます。0〜-1以外のどこかにブレークポイントを設定したい場合でも、オフセットを追加すると移動します。2番目の公理を満たす整数除算は、数直線をほとんどサイズの領域に分割しますがd、そのうちの1つはサイズ2*d-1です。正確に「等しい」分割ではありません。オッドボールパーティションが実際に役立つ時期についての提案はありますか?
スーパーキャット2013年

shr2signedのコンパイラ出力が間違っています。x86上のgccは、算術シフト(sar)を使用して>>の符号付き整数を実装することを選択します。goo.gl/KRgIkb。このメーリングリストの投稿(gcc.gnu.org/ml/gcc/2000-04/msg00152.html)は、gccが歴史的に符号付き整数に対して算術シフトを使用していることを確認しているため、FreeBSD gcc 4.2.1が符号なしシフトを使用した可能性はほとんどありません。私はそれを修正するためにあなたの投稿を更新し、両方が実際にSARであるときに両方がshrを使用したと述べた初期のパラグラフは両方が使用します。SHRは、/ケースの符号ビットを抽出する方法です。ゴッドボルトのリンクも含まれています。
Peter Cordes、2015年

15

追加されたメモ-

x * = 0.5は、一部のVMベースの言語、特にactionscriptで高速になります。これは、変数の除算を0でチェックする必要がないためです。


2
@minitech:それはとても悪いテストです。テスト内のすべてのコードは一定です。コードがJITされる前に、すべての定数が削除されます。

@ M28:jsPerfの内部(つまりeval)が毎回新たに発生することを確信していた。とにかく、はい、それは非常に愚かな最適化なので、それはかなり悪いテストです。
Ry-

13

x = x / 2; ORを 使用x /= 2;するのは、将来新しいプログラマーがそれに取り組む可能性があるためです。そのため、コード行で何が行われているのかを簡単に見つけることができます。誰もがそのような最適化に気づいていないかもしれません。


12

プログラミング大会を目的に語っています。一般に、2による除算が何度も行われる非常に大きな入力があり、入力が正または負であることが知られています。

x >> 1はx / 2よりも優れています。私はideone.comをチェックして、2の演算で10 ^ 10以上の除算が行われるプログラムを実行しました。同じプログラムでx / 2は5.5秒近くかかりましたが、x >> 1は2.6秒近くかかりました。


1
符号なしの値の場合、コンパイラーはに最適化x/2する必要がありx>>1ます。符号付き値の場合、ほぼすべての実装x>>1は、と同等の意味を持つように定義されてx/2いますが、xが正の場合はより高速に計算できx/2xが負の場合とは効果的に異なります。
スーパーキャット2013年

12

考慮すべき点がいくつかあると思います。

  1. ビットシフトは、ビットをシフトするために実際に特別な計算が必要ないため、より高速である必要がありますが、指摘されたように、負の数には潜在的な問題があります。あなたが正の数を持っていることが確実で、スピードを探しているなら、私はビットシフトをお勧めします。

  2. 除算演算子は人間にとって非常に読みやすいものです。したがって、コードを読みやすくする必要がある場合は、これを使用できます。コンパイラの最適化の分野は長い道のりを歩んでいるので、コードを読みやすく理解しやすくすることは良い習慣です。

  3. 基盤となるハードウェアによって、操作の速度が異なる場合があります。アムダルの法則は、一般的なケースを迅速にすることです。したがって、さまざまな操作を他よりも高速に実行できるハードウェアを使用している可能性があります。たとえば、0.5による乗算は、2による除算よりも高速な場合があります(整数除算を適用する場合は、乗算の下限をとる必要がある場合もあります)。

純粋なパフォーマンスが必要な場合は、何百万回も実行できるテストを作成することをお勧めします。実行を数回サンプリングして(サンプルサイズ)、OS /ハードウェア/コンパイラ/コードで統計的に最適なものを決定します。


2
「ビットシフトはもっと速いはずです」。コンパイラーはビットシフトへの分割を最適化します
Trevor Hickey

私は彼らがそうすることを望みますが、コンパイラのソースにアクセスできない限り、確信が持てません:)
James Oravec '31

1
また、実装が最も一般的な方法でビットシフトを処理し、負の数を処理する方法が何と一致し、何>>と一致しない場合にも、ビットシフトをお勧め/します。
スーパーキャット2013年

12

CPUに関する限り、ビットシフト演算は除算演算より高速です。ただし、コンパイラーはこれを認識し、可能な範囲で適切に最適化するため、コードが効率的に実行されていることを認識して、最も理にかなった方法でコードを記述し、簡単に休めることができます。ただし、以前に指摘した理由により、unsigned int缶は(場合によっては)よりも最適化できることに注意intしてください。符号付き算術が必要ない場合は、符号ビットを含めないでください。


11

x = x / 2; は、使用するのに適したコードです。しかし、操作は、出力をどのように生成したいかについての独自のプログラムに依存します。


11

意図をより明確にします。たとえば、除算を行う場合は、x / 2を使用し、コンパイラーがシフト演算子(またはその他)に最適化できるようにします。

今日のプロセッサでは、これらの最適化がプログラムのパフォーマンスに影響を与えることはありません。


10

これに対する答えは、作業している環境によって異なります。

  • あなたは、乗算のハードウェアサポートなしの8ビットマイクロコントローラ、または何かに取り組んでいる場合は、ビットシフトが予想され、一般的、およびコンパイラがほぼ確実になりますしながら、されx /= 2x >>= 1、分割シンボルの存在は、その環境よりも、より眉を上げますシフトを使用して除算を行う。
  • パフォーマンスが重要な環境またはコードのセクションで作業している場合、またはコンパイラの最適化をオフにしてコードをコンパイルできる場合は、 x >>= 1しその理由を説明するコメントを付けて、目的を明確にする。
  • 上記のいずれの条件にも該当しない場合は、単にを使用して、コードを読みやすくしますx /= 2。シフトがコンパイラの最適化よりも効率的であることが不必要にわかっていることを不必要に証明するよりも、シフト操作で10秒のダブルテイクでコードをたまたま見る次のプログラマを救う方が良いでしょう。

これらはすべて符号なし整数を想定しています。単純なシフトは、おそらくあなたが署名したいものではありません。また、DanielHは、x *= 0.5ActionScriptなどの特定の言語での使用について優れた点を示しています。


8

mod 2、= 1をテストします。cの構文を知らない。しかし、これは最速かもしれません。


7

一般的に、右シフトは分割します:

q = i >> n; is the same as: q = i / 2**n;

これは、明確さを犠牲にしてプログラムを高速化するために使用されることがあります。私はあなたがそれをすべきではないと思います。コンパイラーは、自動的にスピードアップを実行できるほどスマートです。これは、シフト入れても明確さを犠牲にしても何も得られないこと意味します

実践的なC ++プログラミングのこのページをご覧ください。


たとえば、最大(x+128)>>8値に近づかxない値を計算する値を計算したい場合、シフトせずに簡潔にそれを行うにはどうすればよいでしょうか。(x+128)/256動作しないことに注意してください。何かいい表現を知っていますか?
スーパーキャット2013年

7

明らかに、あなたがそれを読む次の人のためにあなたのコードを書いているなら、「x / 2」の明快さを求めてください。

ただし、速度が目標の場合は、両方の方法を試し、結果の時間を計ってください。数か月前、整数の配列をステップスルーし、各要素を2で除算することを含むビットマップ畳み込みルーチンを作成しました。 "x >> 1"を "x / 2 "。

実際に両方の時間を計ったとき、x / 2がx >> 1よりも高速であることに驚きました。

これは、デフォルトの最適化がオンになっているMicrosoft VS2008 C ++を使用していました。


4

パフォーマンスの面で。CPUのシフト操作は、分割オペコードよりも大幅に高速です。したがって、2で除算したり、2で乗算したりすることはすべて、シフト演算の恩恵を受けます。

ルックアンドフィールについて。いつ頃化粧品にこだわって、綺麗なお嬢さんも使ってない!:)


3

X / Yは正しいものであり、「>>」シフト演算子です。整数を2で除算したい場合は、(/)被除数演算子を使用できます。シフト演算子はビットをシフトするために使用されます。

x = x / 2; x / = 2; このように使用できます。


0

x >> 1はx / 2より高速ですが、負の値を処理するときの>>の適切な使用は少し複雑です。次のようなものが必要です。

// Extension Method
public static class Global {
    public static int ShiftDivBy2(this int x) {
        return (x < 0 ? x + 1 : x) >> 1;
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.