Cで整数の最上位セットビット(msb)を見つけるための最速/最も効率的な方法は何ですか?


119

整数nがあり、最上位ビットの位置を知りたい場合(つまり、最下位ビットが右側にある場合、1である左端のビットの位置を知りたい)、見つけるための最も速く/最も効率的な方法は何ですか?

私はPOSIX ffs()が最初のセットビットを見つけるためにstrings.hのメソッドをサポートしていることを知っていますが、対応するものがないようですfls()メソッド。

これを行うための本当に明らかな方法はありますか?

移植性のためにPOSIX関数を使用できない場合はどうですか?

編集:32ビットと64ビットの両方のアーキテクチャで機能するソリューションについてはどうですか(多くのコードリストは32ビットの整数でしか機能しないように見えます)。


いくつかの実装がここにあります:graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear(編集:あなたの質問を再読み込みした後、私は上記のリンクは一番右のセットビットを見つけるためのものであることを実感ではなく、あなたは、必要左端のようなしているが単語の大きさの感覚、それは答えるのが難しいものです)
消費者


右側のゼロを数えます。問題は左側のゼロについてでした。少なくとも、ざっと目を通すと、そこには見当たりません。
ダライアスベーコン

2
ビット数 'n'が特に必要ですか、それとも2 ^ nで十分ですか?
アルニタク

1
「ログベース2」アルゴリズムをご覧ください。アンダーソンが記事で述べているように、「整数のログベース2は、最上位ビットセット(または最上位ビットセット、MSB)の位置と同じです」
Michael Burr

回答:


64

GCCは次の機能。

 -組み込み関数:int __builtin_clz(unsigned int x)
     Xの先頭の0ビットの数を最大で返します
     重要なビット位置。Xが0の場合、結果は未定義です。

 -組み込み関数:int __builtin_clzl(unsigned long)
     `__builtin_clz 'に似ていますが、引数のタイプが` unsigned
     長いです'。

 -組み込み関数:int __builtin_clzll(unsigned long long)
     `__builtin_clz 'に似ていますが、引数のタイプが` unsigned
     長い長い」。

私は、それらがあなたの現在のプラットフォームにとってかなり効率的なものに翻訳されることを期待しています。


便利なトリックは、あなたの入力があればできることはゼロです__builtin_clz(x | 1):無条件に他のものを変更することなく、低ビットを設定するには、出力作る31ためにx=0他の入力に対する出力を変更せずに、。

これを行う必要がないようにするために、他のオプションは、ARM GCC __clz(ヘッダーは不要)などのプラットフォーム固有の組み込み関数、または命令_lzcnt_u32をサポートするCPU上のx86 lzcntです。(フォールトの代わりに古いCPUのようにlzcntデコードすることに注意してくださいbsr。これにより、ゼロ以外の入力に対して31-lzcntが得られます。)

残念ながら、x以外のプラットフォームで、オペランド幅に応じてinput = 0の結果を32または64として定義するさまざまなCLZ命令を移植可能に利用する方法はありません。x86 lzcntもそれを行いますが、をbsr使用しない限りコンパイラーがフリップしなければならないビットインデックスを生成します31-__builtin_clz(x)

(「未定義の結果」は、C未定義の動作ではなく、単に定義されていない値です。実際には、命令が実行されたときに宛先レジスタにあったものです。AMDはこれを文書化していますが、Intelはそうではありませんが、IntelのCPUはその動作を実装しています。しかし、それはあなたが割り当てているC変数に以前あったものではなく、それは通常gccがCをasmに変換するときの動作ではありません。なぜLZCNTの「出力依存性」を壊すことが重要なのか。



1
LZCNTが利用できない場合でも、undefined-on-zero動作により、x86で単一のBSR命令にコンパイルできます。これは__builtin_ctzover にとって大きな利点ffsであり、BSFおよびCMOVにコンパイルして、入力がゼロであった場合を処理します。十分に短い実装のないアーキテクチャー(例えば、clz命令のない古いARM )では、gccはlibgccヘルパー関数の呼び出しを発行します。
Peter Cordes 2016

41

少しのインラインアセンブラでx86とゲームを使用していると仮定すると、IntelはBSR命令(「ビットスキャンリバース」)を提供します。それはだ、高速いくつかの(他人にマイクロコード)x86s。マニュアルから:

ソースオペランドで最上位セットビット(1ビット)を検索します。最上位の1ビットが見つかった場合、そのビットインデックスは宛先オペランドに格納されます。ソースオペランドは、レジスタまたはメモリロケーションです。宛先オペランドはレジスターです。ビットインデックスは、ソースオペランドのビット0からの符号なしオフセットです。コンテンツソースオペランドが0の場合、デスティネーションオペランドのコンテンツは未定義です。

(PowerPCを使用している場合、同様の cntlz(「先行ゼロを数える」)命令があります。)

gccのコード例:

#include <iostream>

int main (int,char**)
{
  int n=1;
  for (;;++n) {
    int msb;
    asm("bsrl %1,%0" : "=r"(msb) : "r"(n));
    std::cout << n << " : " << msb << std::endl;
  }
  return 0;
}

このインラインアセンブラチュートリアルも参照してください。これは、コードのループよりもかなり高速であることを示しています(セクション9.4)。


4
実際、この命令は通常、ループにマイクロコード化されており、かなり低速です。
rlbond 2009年

2
どれ ?BSRまたはCNTLZ?上記のx86-timing.pdfを読んだとき、BSRはNetburst Pentiumでのみ低速です。PowerPCについては何も知りません。
timday 2009年

5
...よく調べてみると、「BSRはP3 / Pentium-M / Core2 x86でのみ高速である」ことがわかります。NetburstとAMDでは遅い。
timday 2009年

1
ただのヘッドアップ:あなたの最後の2つのリンクは死んでいます。
Baum mit Augen

2
@rlbond:ええと、P4 PrescottのBSRは2 uops、16サイクルのレイテンシ(!)、4cあたり1つのスループットです。しかし、以前のNetburstでは、4サイクルのレイテンシ(まだ2 uops)であり、2cあたり1つのスループットです。(ソース:agner.org/optimize)。ほとんどのCPUでは、gccが考慮しない出力にも依存します(入力がゼロの場合、実際の動作は宛先を変更しないことです)。これにより、stackoverflow.com / questions / 25078285 /…のような問題が発生する可能性があります。IDKがそれを修正するときにgccがBSRを見逃した理由。
Peter Cordes 2016

38

2 ^ NはN番目のビットセット(1 << N)のみを持つ整数であるため、最上位セットビットの位置(N)を見つけることは、その整数の2を底とする整数の対数です。

http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious

unsigned int v;
unsigned r = 0;

while (v >>= 1) {
    r++;
}

この「明白な」アルゴリズムは誰にとっても透過的ではないかもしれませんが、コードは左端のビットがシフトオフされるまで繰り返し1ビット右にシフトし(Cはゼロ以外の値をtrueとして扱うことに注意してください)、数値を返します。シフトの、それは完全に理にかなっています。これは、複数のビットが設定されている場合でも機能することを意味します。結果は常に最上位ビットに対するものです。

そのページを下にスクロールすると、より速く、より複雑なバリエーションがあります。ただし、先行ゼロが多い数値を処理していることがわかっている場合、Cではビットシフトがかなり高速であり、単純なアルゴリズムでは配列にインデックスを付ける必要がないため、素朴なアプローチで許容できる速度が得られる場合があります。

注: 64ビット値を使用する場合は、非常に賢いアルゴリズムの使用に非常に注意してください。それらの多くは、32ビット値に対してのみ正しく機能します。


2
@Johanデバッガーでステップスルーすると、ループが終了する理由を説明できます。基本的には、最後の1ビットが右にシフトされると、条件の式が0(falseとして扱われる)と評価されるためです。
Quinn Taylor

2
そのような最終結果を使用するのはいい考えです:)
Johan

6
注:符号なしである必要があります。符号付き整数の場合、負の数では右シフトに失敗します。
Xantix

2
Xantix:C / C ++でのシフトは論理的なシフトなので、うまく機能します。Java、JavaScript、またはDの場合、論理シフト演算子を使用する必要があります>>>。さらに、おそらくコンパレータ!= 0、および不特定の数の括弧。
追跡

8
@チェイス:いいえ、そうではありません。unsignedの論理的なシフトです。以下のために署名し、それは、またはしなくてもよい論理シフトすること(それは実際には、通常、算術'S)。
TimČas、2015

17

これは非常に速いはずです:

int msb(unsigned int v) {
  static const int pos[32] = {0, 1, 28, 2, 29, 14, 24, 3,
    30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
    16, 7, 26, 12, 18, 6, 11, 5, 10, 9};
  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  v |= v >> 8;
  v |= v >> 16;
  v = (v >> 1) + 1;
  return pos[(v * 0x077CB531UL) >> 27];
}

25
7ビットシフト、5または命令、多様性、および潜在的なキャッシュミス。:)あなたはそれをベンチマークしましたか、それとも生成されたアセンブラを見ましたか?それは可能性があり、コンパイラは排除することができますどのくらいのに応じて、非常に遅く終わります。
2009年

5
私はここに新しいです。反対票はもらえません。私は実際に機能するソースコードで唯一の回答を提供しました。
主人公

9
「キャッシュミスの可能性」は、このコードがルックアップテーブルにアクセスする必要があるためと考えられます。これが呼び出されたときにそのテーブルがキャッシュされていない場合、フェッチ中にストールが発生します。これにより、最悪の場合のパフォーマンスが、LUTを使用しないソリューションよりもはるかに悪くなる可能性があります。
アンワインド

13
本当にポイントではありません。必要以上に多くのデータキャッシュ(1つ以上のキャッシュライン)を使用し、必要以上に多くの命令キャッシュを使用します。関数を初めて呼び出したときに回避できたはずのキャッシュミスが発生する可能性が高く、必要以上にキャッシュを汚染するため、呼び出し、他のコードで必要以上のミスが発生する可能性があります。キャッシュミスはコストが高いため、LUTを使用しても問題はありません。しかし、それが「超高速」であると主張する前に、それをベンチマークにしたいものだとだけ言った。それは間違いなく問題です。
2007

6
テーブルには32のエントリがあり、すべての値は255(127)未満であるため、テーブルをタイプunsigned charとして定義すると、1つの32バイトL1キャッシュラインに収まります。そして、全体が2つのキャッシュラインに収まります。
ChuckCottrill、2015年

16

これは一種の整数ログを見つけるようなものです。ちょっとしたトリックがありますが、私はこれのために独自のツールを作りました。もちろん目標はスピードです。

私の認識は、CPUが整数を浮動小数点数に変換するために使用される自動ビット検出器を既に持っているということです!だからそれを使う。

double ff=(double)(v|1);
return ((*(1+(uint32_t *)&ff))>>20)-1023;  // assumes x86 endianness

このバージョンでは、値をdoubleにキャストしてから、指数を読み取ります。これにより、ビットがどこにあったかがわかります。派手なシフトと減算は、IEEE値から適切な部分を抽出することです。

floatを使用する方が少し高速ですが、floatは精度が低いため、最初の24ビットの位置しか得られません。


これを安全に行うには、C ++またはCで未定義の動作なしmemcpyに、ポインター型キャストの代わりに使用して、型パンニングを行います。コンパイラはそれを効率的にインライン化する方法を知っています。

// static_assert(sizeof(double) == 2 * sizeof(uint32_t), "double isn't 8-byte IEEE binary64");
// and also static_assert something about FLT_ENDIAN?

double ff=(double)(v|1);

uint32_t tmp;
memcpy(&tmp, ((const char*)&ff)+sizeof(uint32_t), sizeof(uint32_t));
return (tmp>>20)-1023;

または、C99以降では、union {double d; uint32_t u[2];};。ただし、C ++では、ユニオンタイプのパンニングは、一部のコンパイラでは拡張機能としてのみサポートされており、ISO C ++ではサポートされていないことに注意してください。


これは通常、先行ゼロカウント命令のプラットフォーム固有の組み込み関数よりも遅くなりますが、ポータブルISO Cにはそのような機能はありません。一部のCPUには先行ゼロのカウント命令もありませんが、一部のCPUは整数を効率的に変換できますdouble。ただし、FPビットパターンをタイプパンニングして整数に戻すと、速度が遅くなる可能性があります(たとえば、PowerPCではストア/リロードが必要であり、通常はロードヒットストアのストールを引き起こします)。

このアルゴリズムは、SIMDを実装するCPUが少ないため、SIMD実装に役立つ可能性がありますlzcnt。x86 はAVX512CDでそのような命令しか取得しませんでした


2
はい。また、型エイリアスの最適化により、gccはこのようなコードで-O2を使用して厄介なことを行います。
MSN

4
整数と浮動小数点の間のキャストは、x86 CPUで驚くほど高価になる可能性があります
jalf

1
はい、FPUのコストは高いです。しかし、実際の時間測定では、これが全ビット演算や特にループよりも速いことが示されました。それを試して、最速を取ることは常に最高のアドバイスです。私はこれでGCCと-O2に問題がありませんでした。
SPWorley 2009年

1
これは未定義の動作ではありませんか(互換性のない型のポインタを介して値を読み取る)。
dreamlax 2011

3
Hacker's Delightは、5-3カウンティングリーディング0の32ビットフロートのエラーを修正する方法を説明しています。以下は、匿名ユニオンを使用してasFloatとasIntをオーバーラップするコードです。k= k&〜(k >> 1); asFloat =(float)k + 0.5f; n = 158-(asInt >> 23); (はい、これは実装定義の動作に依存します)
D Coetzee

11

Kaz Kylhekuはこちら

63ビットを超えるこの数(gcc x86_64のlong long型)に対する2つのアプローチをベンチマークし、符号ビットから遠ざけました。

(たまたま、私は何かのためにこの「最高のビットを見つける」を必要とするでしょう。)

データ駆動のバイナリ検索を実装しました(上記の回答の1つにほぼ基づいています)。また、完全に展開されたディシジョンツリーを手動で実装しました。これは、直接のオペランドを持つコードです。ループもテーブルもありません。

決定木(highest_bit_unrolled)のベンチマークは69%速くなりましたが、バイナリ検索に明示的なテストがあるn = 0の場合は例外です。

バイナリ検索の0ケースに対する特別なテストは、特別なテストがない決定木よりも48%高速です。

コンパイラ、マシン:(GCC 4.5.2、-O3、x86-64、2867 Mhz Intel Core i5)。

int highest_bit_unrolled(long long n)
{
  if (n & 0x7FFFFFFF00000000) {
    if (n & 0x7FFF000000000000) {
      if (n & 0x7F00000000000000) {
        if (n & 0x7000000000000000) {
          if (n & 0x4000000000000000)
            return 63;
          else
            return (n & 0x2000000000000000) ? 62 : 61;
        } else {
          if (n & 0x0C00000000000000)
            return (n & 0x0800000000000000) ? 60 : 59;
          else
            return (n & 0x0200000000000000) ? 58 : 57;
        }
      } else {
        if (n & 0x00F0000000000000) {
          if (n & 0x00C0000000000000)
            return (n & 0x0080000000000000) ? 56 : 55;
          else
            return (n & 0x0020000000000000) ? 54 : 53;
        } else {
          if (n & 0x000C000000000000)
            return (n & 0x0008000000000000) ? 52 : 51;
          else
            return (n & 0x0002000000000000) ? 50 : 49;
        }
      }
    } else {
      if (n & 0x0000FF0000000000) {
        if (n & 0x0000F00000000000) {
          if (n & 0x0000C00000000000)
            return (n & 0x0000800000000000) ? 48 : 47;
          else
            return (n & 0x0000200000000000) ? 46 : 45;
        } else {
          if (n & 0x00000C0000000000)
            return (n & 0x0000080000000000) ? 44 : 43;
          else
            return (n & 0x0000020000000000) ? 42 : 41;
        }
      } else {
        if (n & 0x000000F000000000) {
          if (n & 0x000000C000000000)
            return (n & 0x0000008000000000) ? 40 : 39;
          else
            return (n & 0x0000002000000000) ? 38 : 37;
        } else {
          if (n & 0x0000000C00000000)
            return (n & 0x0000000800000000) ? 36 : 35;
          else
            return (n & 0x0000000200000000) ? 34 : 33;
        }
      }
    }
  } else {
    if (n & 0x00000000FFFF0000) {
      if (n & 0x00000000FF000000) {
        if (n & 0x00000000F0000000) {
          if (n & 0x00000000C0000000)
            return (n & 0x0000000080000000) ? 32 : 31;
          else
            return (n & 0x0000000020000000) ? 30 : 29;
        } else {
          if (n & 0x000000000C000000)
            return (n & 0x0000000008000000) ? 28 : 27;
          else
            return (n & 0x0000000002000000) ? 26 : 25;
        }
      } else {
        if (n & 0x0000000000F00000) {
          if (n & 0x0000000000C00000)
            return (n & 0x0000000000800000) ? 24 : 23;
          else
            return (n & 0x0000000000200000) ? 22 : 21;
        } else {
          if (n & 0x00000000000C0000)
            return (n & 0x0000000000080000) ? 20 : 19;
          else
            return (n & 0x0000000000020000) ? 18 : 17;
        }
      }
    } else {
      if (n & 0x000000000000FF00) {
        if (n & 0x000000000000F000) {
          if (n & 0x000000000000C000)
            return (n & 0x0000000000008000) ? 16 : 15;
          else
            return (n & 0x0000000000002000) ? 14 : 13;
        } else {
          if (n & 0x0000000000000C00)
            return (n & 0x0000000000000800) ? 12 : 11;
          else
            return (n & 0x0000000000000200) ? 10 : 9;
        }
      } else {
        if (n & 0x00000000000000F0) {
          if (n & 0x00000000000000C0)
            return (n & 0x0000000000000080) ? 8 : 7;
          else
            return (n & 0x0000000000000020) ? 6 : 5;
        } else {
          if (n & 0x000000000000000C)
            return (n & 0x0000000000000008) ? 4 : 3;
          else
            return (n & 0x0000000000000002) ? 2 : (n ? 1 : 0);
        }
      }
    }
  }
}

int highest_bit(long long n)
{
  const long long mask[] = {
    0x000000007FFFFFFF,
    0x000000000000FFFF,
    0x00000000000000FF,
    0x000000000000000F,
    0x0000000000000003,
    0x0000000000000001
  };
  int hi = 64;
  int lo = 0;
  int i = 0;

  if (n == 0)
    return 0;

  for (i = 0; i < sizeof mask / sizeof mask[0]; i++) {
    int mi = lo + (hi - lo) / 2;

    if ((n >> mi) != 0)
      lo = mi;
    else if ((n & (mask[i] << lo)) != 0)
      hi = mi;
  }

  return lo + 1;
}

迅速で汚れたテストプログラム:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int highest_bit_unrolled(long long n);
int highest_bit(long long n);

main(int argc, char **argv)
{
  long long n = strtoull(argv[1], NULL, 0);
  int b1, b2;
  long i;
  clock_t start = clock(), mid, end;

  for (i = 0; i < 1000000000; i++)
    b1 = highest_bit_unrolled(n);

  mid = clock();

  for (i = 0; i < 1000000000; i++)
    b2 = highest_bit(n);

  end = clock();

  printf("highest bit of 0x%llx/%lld = %d, %d\n", n, n, b1, b2);

  printf("time1 = %d\n", (int) (mid - start));
  printf("time2 = %d\n", (int) (end - mid));
  return 0;
}

-O2のみを使用すると、差は大きくなります。決定木はほぼ4倍高速です。

また、単純なビットシフトコードに対してベンチマークを行いました。

int highest_bit_shift(long long n)
{
  int i = 0;
  for (; n; n >>= 1, i++)
    ; /* empty */
  return i;
}

これは、予想されるように、少数の場合にのみ高速です。n == 1の最上位ビットが1であると判断する際に、80%以上速くベンチマークしました。ただし、63ビット空間でランダムに選択された数値の半分には、63番目のビットが設定されています。

入力0x3FFFFFFFFFFFFFFFでは、デシジョンツリーバージョンは1よりもかなり高速で、ビットシフターよりも1120%速い(12.2倍)ことを示しています。

また、GCCビルトインに対して決定木をベンチマークし、同じ数に対して繰り返すのではなく、入力の混合を試します。継続的な分岐予測が行われている可能性があり、繰り返しで人工的に高速化する非現実的なキャッシュシナリオがいくつかある可能性があります。


9
これが良くないと言っているわけではありませんが、ここでのテストプログラムは同じ数のみをテストします。2〜3回の反復の後、分岐予測子は最終位置に設定され、その後、完全な分岐予測が行われます。良いことは、完全にランダムな分布では、半分の数が完全な予測、つまりbit63に近いことです。
2014年


6
unsigned int
msb32(register unsigned int x)
{
        x |= (x >> 1);
        x |= (x >> 2);
        x |= (x >> 4);
        x |= (x >> 8);
        x |= (x >> 16);
        return(x & ~(x >> 1));
}

1レジスタ、13命令。信じられないかもしれませんが、これは通常、線形時間で動作する上記のBSR命令よりも高速です。これは対数時間です。

http://aggregate.org/MAGIC/#Most%20Significant%201%20Bitから


7
上記のコードは質問に答えません。xの最上位ビットがオンのままで、他のすべてのビットがオフになっている符号なし整数を返します。問題は、ビットの最も重要な位置を返すことでした。
主人公

3
次に、De Bruijnシーケンスアプローチを使用して、設定されているビットのインデックスを検索できます。:-)
R .. GitHub ICEのヘルプを停止する

5
@主人公、彼はコメントでどちらでも十分だと言いました。
rlbond 2011

これは(同じページから)必要なことを実行しますが、追加の機能が必要です。aggregate.org/MAGIC/#Log2%20of%20an%20Integer
Quinn Taylor

1
BSRは、少なくともCore2以降のIntel CPUで高速です。LZCNTはAMD CPUで高速であり、gccはそれが__builtin_clz有効になっている-march=nativeかどうかに使用します(それをサポートするすべてのCPUで高速であるため)。AMD BulldozerファミリのようなCPUでBSRが「遅い」場合でも、それほど遅くはありません。7m-opで4サイクルのレイテンシと4cあたり1つのスループット。Atomでは、BSRは非常に遅く、16サイクルです。Silvermontでは、10 uopsで10サイクルのレイテンシがあります。これは、SilvermontのBSRよりもわずかに低いレイテンシですが、IDKです。
Peter Cordes

6

以下は、このページで現在提供されているアルゴリズムの(簡単な)ベンチマークです...

アルゴリズムは、unsigned intのすべての入力に対してテストされていません。だから盲目的に何かを使う前に、まずそれをチェックしてください;)

私のマシンではclz(__builtin_clz)とasmが最適に動作します。asmはclzよりもさらに高速に見えますが、単純なベンチマークが原因である可能性があります...

//////// go.c ///////////////////////////////
// compile with:  gcc go.c -o go -lm
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/***************** math ********************/

#define POS_OF_HIGHESTBITmath(a) /* 0th position is the Least-Signif-Bit */    \
  ((unsigned) log2(a))         /* thus: do not use if a <= 0 */  

#define NUM_OF_HIGHESTBITmath(a) ((a)               \
                  ? (1U << POS_OF_HIGHESTBITmath(a))    \
                  : 0)



/***************** clz ********************/

unsigned NUM_BITS_U = ((sizeof(unsigned) << 3) - 1);
#define POS_OF_HIGHESTBITclz(a) (NUM_BITS_U - __builtin_clz(a)) /* only works for a != 0 */

#define NUM_OF_HIGHESTBITclz(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITclz(a))  \
                 : 0)


/***************** i2f ********************/

double FF;
#define POS_OF_HIGHESTBITi2f(a) (FF = (double)(ui|1), ((*(1+(unsigned*)&FF))>>20)-1023)


#define NUM_OF_HIGHESTBITi2f(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITi2f(a))  \
                 : 0)




/***************** asm ********************/

unsigned OUT;
#define POS_OF_HIGHESTBITasm(a) (({asm("bsrl %1,%0" : "=r"(OUT) : "r"(a));}), OUT)

#define NUM_OF_HIGHESTBITasm(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITasm(a))  \
                 : 0)




/***************** bitshift1 ********************/

#define NUM_OF_HIGHESTBITbitshift1(a) (({   \
  OUT = a;                  \
  OUT |= (OUT >> 1);                \
  OUT |= (OUT >> 2);                \
  OUT |= (OUT >> 4);                \
  OUT |= (OUT >> 8);                \
  OUT |= (OUT >> 16);               \
      }), (OUT & ~(OUT >> 1)))          \



/***************** bitshift2 ********************/
int POS[32] = {0, 1, 28, 2, 29, 14, 24, 3,
             30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
             16, 7, 26, 12, 18, 6, 11, 5, 10, 9};

#define POS_OF_HIGHESTBITbitshift2(a) (({   \
  OUT = a;                  \
  OUT |= OUT >> 1;              \
  OUT |= OUT >> 2;              \
  OUT |= OUT >> 4;              \
  OUT |= OUT >> 8;              \
  OUT |= OUT >> 16;             \
  OUT = (OUT >> 1) + 1;             \
      }), POS[(OUT * 0x077CB531UL) >> 27])

#define NUM_OF_HIGHESTBITbitshift2(a) ((a)              \
                       ? (1U << POS_OF_HIGHESTBITbitshift2(a)) \
                       : 0)



#define LOOPS 100000000U

int main()
{
  time_t start, end;
  unsigned ui;
  unsigned n;

  /********* Checking the first few unsigned values (you'll need to check all if you want to use an algorithm here) **************/
  printf("math\n");
  for (ui = 0U; ui < 18; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITmath(ui));

  printf("\n\n");

  printf("clz\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITclz(ui));

  printf("\n\n");

  printf("i2f\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITi2f(ui));

  printf("\n\n");

  printf("asm\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITasm(ui));
  }

  printf("\n\n");

  printf("bitshift1\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift1(ui));
  }

  printf("\n\n");

  printf("bitshift2\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift2(ui));
  }

  printf("\n\nPlease wait...\n\n");


  /************************* Simple clock() benchmark ******************/
  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITmath(ui);
  end = clock();
  printf("math:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITclz(ui);
  end = clock();
  printf("clz:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITi2f(ui);
  end = clock();
  printf("i2f:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITasm(ui);
  end = clock();
  printf("asm:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift1(ui);
  end = clock();
  printf("bitshift1:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift2(ui);
  end = clock();
  printf("bitshift2\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  printf("\nThe lower, the better. Take note that a negative exponent is good! ;)\n");

  return EXIT_SUCCESS;
}

6

おそらく、可能な限り最高のパフォーマンスが絶対に必要な場合(ビットボードを含むある種のボードゲームAIを作成する場合など)にのみこの方法を使用しますが、最も効率的なソリューションは、インラインASMを使用することです。説明付きのコードについては、このブログ投稿の最適化セクションを参照してください。

[...]、bsrlアセンブリ命令は最上位ビットの位置を計算します。したがって、次のasmステートメントを使用できます。

asm ("bsrl %1, %0" 
     : "=r" (position) 
     : "r" (number));

拡張するには:標準ループソリューション(左シフトしてMSBをチェックする)がおそらく最も読みやすいでしょう。ビットいじりを伴うすべての場合と同様に、ASMの速度を超えることはできませんが、必要でない限り、コードを混乱させる意味はありません。ハックはその中間の解決策です-いずれにせよ。
Noldorin 2009年

対数を取ることは完全に読み取り可能なソリューションになると思います(生成されたasmをチェックして、コンパイラーがこのasm命令を使用するように最適化できるかどうかを確認します)
jalf

CPUマイクロコードの実装によっては、インラインASMソリューションが遅くなる場合があります。
rlbond 2009年

5
@rlbound:間違いかもしれませんが、信じられません。すべての近代的なCPUの1が....それは、単一の命令に翻訳さになるだろうと思うだろうオン
Noldorin

3
@Noldorin少し遅いですが..定義上、単一の命令ですが、rlbondが示唆するようにマイクロコード化されている場合、単一の命令が内部で一連のµop全体にデコードできます。これは、AMDのマイクロアーキテクチャとIntel Atomに当てはまる傾向にありますが、通常のIntelマイクロアーキテクチャでは、それはずっと単一の操作です。
ハロルド

4

私はこれを行うためのルーチンが必要であり、ウェブを検索する前に(そしてこのページを見つける前に)バイナリ検索に基づいた独自のソリューションを思いつきました。誰かが前にこれをしたと確信していますが!それは一定の時間で実行され、投稿された「明白な」ソリューションよりも高速になる可能性があります。

int highest_bit(unsigned int a) {
  static const unsigned int maskv[] = { 0xffff, 0xff, 0xf, 0x3, 0x1 };
  const unsigned int *mask = maskv;
  int l, h;

  if (a == 0) return -1;

  l = 0;
  h = 32;

  do {
    int m = l + (h - l) / 2;

    if ((a >> m) != 0) l = m;
    else if ((a & (*mask << l)) != 0) h = m;

    mask++;
  } while (l < h - 1);

  return l;
}

4

それはある種の二分探索であり、あらゆる種類の(符号なし!)整数型で動作します

#include <climits>
#define UINT (unsigned int)
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int msb(UINT x)
{
    if(0 == x)
        return -1;

    int c = 0;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x >> i))
    {
        x >>= i;
        c |= i;
    }

    return c;
}

完全にするために:

#include <climits>
#define UINT unsigned int
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int lsb(UINT x)
{
    if(0 == x)
        return -1;

    int c = UINT_BIT-1;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x << i))
    {
        x <<= i;
        c ^= i;
    }

    return c;
}

4
typedefsまたは実際にはプリプロセッサマクロ以外にはALL_CAPSを使用しないことを検討してください。これは広く受け入れられている規約です。
underscore_d

4

ここにいくつかの過度に複雑な答えがあります。デブリンテクニックは、入力がすでに2の累乗である場合にのみ使用する必要があります。それ以外の場合は、より良い方法があります。入力が2のべき乗の場合、Debruinは、_BitScanReverse私がテストしたどのプロセッサよりも、絶対的に高速であり、さらに高速です。ただし、一般的なケースでは_BitScanReverse(またはコンパイラでコンパイラ組み込み関数が呼び出された場合)、最も高速です(ただし、特定のCPUではマイクロコード化できます)。

組み込み関数がオプションではない場合、一般的な入力を処理するための最適なソフトウェアソリューションを以下に示します。

u8  inline log2 (u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFu) { val >>= 16; k  = 16; }
    if (val > 0x000000FFu) { val >>= 8;  k |= 8;  }
    if (val > 0x0000000Fu) { val >>= 4;  k |= 4;  }
    if (val > 0x00000003u) { val >>= 2;  k |= 2;  }
    k |= (val & 2) >> 1;
    return k;
}

このバージョンは、他のほとんどの回答とは異なり、最後にDebruinルックアップを必要としないことに注意してください。所定の位置を計算します。

ただし、テーブルを何度も呼び出すと、キャッシュミスのリスクがテーブルの高速化によって損なわれる可能性があります。

u8 kTableLog2[256] = {
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};

u8 log2_table(u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFuL) { val >>= 16; k  = 16; }
    if (val > 0x000000FFuL) { val >>=  8; k |=  8; }
    k |= kTableLog2[val]; // precompute the Log2 of the low byte

    return k;
}

これにより、ここに示したソフトウェアの回答の中で最高のスループットが得られますが、たまにしか呼び出さない場合は、最初のスニペットのようなテーブルフリーソリューションをお勧めします。


1
一部の答えはブランチなしですが、これはおそらく条件付きブランチでコンパイルされます。同じ値を繰り返しベンチマークするだけですか、それとも単純なパターンか何かですか?ブランチの予測ミスは、パフォーマンスに大きな影響を与えます。 stackoverflow.com/questions/11227809/…–
Peter Cordes

3

上記の回答が指摘するように、最上位ビットを決定する方法はいくつかあります。ただし、指摘されたように、メソッドは32ビットまたは64ビットのレジスターに固有である可能性があります。stanford.edu bithacksページには、 32ビットおよび64ビット・コンピューティングの両方のためのソリューションは、仕事を提供します。少しの作業で、これらを組み合わせて、MSBを取得するための強固なクロスアーキテクチャアプローチを提供できます。私が64ビットおよび32ビットのコンピューターでコンパイル/動作したときに到達した解決策は次のとおりです。

#if defined(__LP64__) || defined(_LP64)
# define BUILD_64   1
#endif

#include <stdio.h>
#include <stdint.h>  /* for uint32_t */

/* CHAR_BIT  (or include limits.h) */
#ifndef CHAR_BIT
#define CHAR_BIT  8
#endif  /* CHAR_BIT */

/* 
 * Find the log base 2 of an integer with the MSB N set in O(N)
 * operations. (on 64bit & 32bit architectures)
 */
int
getmsb (uint32_t word)
{
    int r = 0;
    if (word < 1)
        return 0;
#ifdef BUILD_64
    union { uint32_t u[2]; double d; } t;  // temp
    t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] = 0x43300000;
    t.u[__FLOAT_WORD_ORDER!=LITTLE_ENDIAN] = word;
    t.d -= 4503599627370496.0;
    r = (t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] >> 20) - 0x3FF;
#else
    while (word >>= 1)
    {
        r++;
    }
#endif  /* BUILD_64 */
    return r;
}

int rではありませんでした。もともと#ifdef BUILD_64フラグの上に定義されていますか?その場合、条件内で再定義する必要はありません。
デビッドC.ランキン2014年

3

逐次近似を使用するCのバージョン:

unsigned int getMsb(unsigned int n)
{
  unsigned int msb  = sizeof(n) * 4;
  unsigned int step = msb;
  while (step > 1)
 {
    step /=2;
    if (n>>msb)
     msb += step;
   else
     msb -= step;
 }
  if (n>>msb)
    msb++;
  return (msb - 1);
}

利点:ループの数は常に同じであるため、実行時間は指定された数に関係なく一定です。(「unsigned int」を使用すると4ループ)


三項演算子(msb += (n>>msb) ? step : -step;)、より多くのコンパイラーがブランチなしのasmを作成する可能性が高く、すべてのステップでブランチの誤予測を回避します(stackoverflow.com/questions/11227809/…)。
Peter Cordes

3

私はこの質問が非常に古いことを知っていますが、自分でmsb()関数を実装しただけで、ここおよび他のWebサイトで提示されるほとんどのソリューションが必ずしも最も効率的ではないことがわかりました-少なくとも私の個人的な効率の定義では(以下の更新も参照) )。理由は次のとおりです。

ほとんどの解決策(特に、ある種のバイナリサーチスキームや、右から左に線形スキャンを行う単純なアプローチを採用しているもの)は、任意の2進数の場合、非常に長いシーケンスで始まるソリューションは多くないという事実を無視しているようです。ゼロ。実際、どのビット幅でも、すべての整数の半分は1で始まり、それらの4分の1は01で始まります。どこにいるのかわかりますか?私の主張は、最上位のビット位置から最下位(左から右)までの線形スキャンは、一見するとそれほど線形ではないということです。

1を示すことができます。どのビット幅でも、テストする必要のあるビットの平均数は最大で2です。これは、ビット数(!)に関してO(1)の償却時間の複雑さを意味します。 。

もちろん、最悪のケースはまだO(n)です、バイナリサーチのようなアプローチで得られるO(log(n))よりも悪いですが、最悪のケースは非常に少ないため、ほとんどのアプリケーションでは無視できます(Update:まったくない:数は少ないかもしれませんが、高い確率で発生する可能性があります-下記の更新を参照してください)。

ここに私が思いついた「ナイーブ」なアプローチがあります。これは少なくとも私のマシンでは他のほとんどのアプローチよりも優れています(32 ビット整数のバイナリ検索スキームは常にlog 2(32)= 5ステップを必要としますが、この愚かなアルゴリズムはそれほど必要ではありません)平均して2以上)-これはC ++であり、純粋なCではないため申し訳ありません:

template <typename T>
auto msb(T n) -> int
{
    static_assert(std::is_integral<T>::value && !std::is_signed<T>::value,
        "msb<T>(): T must be an unsigned integral type.");

    for (T i = std::numeric_limits<T>::digits - 1, mask = 1 << i; i >= 0; --i, mask >>= 1)
    {
        if ((n & mask) != 0)
            return i;
    }

    return 0;
}

アップデート:私がここに書いたことは完全に真実ですが)。この場合、私のソリューションは実際にはバイナリサーチアプローチよりもパフォーマンスが低下します。したがって、私のソリューションはすべての整数をループする方が高速ですが、後者がおそらく推奨されます。TL; DR:実際の整数はおそらく、この単純なアルゴリズムの最悪のケースにバイアスをかけます。任意の整数、ビットのすべての組み合わせは等しく可能性があります(私の速度テストはすべての 32ビット整数のMSBを決定するのにかかった時間を単に測定しました)、実際の整数、どのような関数が呼び出されるか、通常は別のパターンに従います。たとえば、私のコードでは、この関数を使用して、オブジェクトサイズが2の累乗であるかどうかを判断したり、オブジェクトサイズ。私の推測では、MSBを使用するほとんどのアプリケーションには、整数が表すことができる最大数よりもはるかに小さい数が含まれます(オブジェクトサイズは、 size_tの
本当に任意の整数ではO(1)が償却されるます。

1引数は次のようになります(下書き):nをビット数(ビット幅)とします。合計2 n個の整数があり、nビットで表すことができます。ある2 N -は1から始まる整数値1(第1残存固定されている、- 1 nは何もすることができビット)。これらの整数は、MSBを決定するためにループの1回の繰り返しのみを必要とします。さらに、01で始まる2 n-2の整数は2回の反復が必要であり、001で始まる2 n-3の整数は3回の反復が必要です。

すべての可能な整数に必要なすべての反復を合計し、それらを2 nで除算すると合計し、整数の総数であるで、nビット整数のMSBを決定するために必要な平均反復数が得られます。

(1 * 2 n-1 + 2 * 2 n-2 + 3 * 2 n-3 + ... + n)/ 2 n

この一連の平均反復は実際には収束しており、無限大に向かってnが2に制限されています

したがって、単純な左から右へのアルゴリズムは、実際には、任意の数のビットに対してO(1)の償却された一定時間の複雑さを持っています。


2
msb関数への入力が均等に分散される傾向があるというのは、必ずしも公正な仮定ではないと思います。実際には、これらの入力は、割り込みレジスタ、ビットボード、または値が不均一に分布しているその他のデータ構造になる傾向があります。公平なベンチマークの場合、出力ではなく(出力ではなく)均等に分散されると仮定する方が安全だと思います。
johnwbyrd 2017年

3

私たちに与えたlog2。これにより、log2このページに表示されるすべての特別なソース実装の必要がなくなります。次のlog2ような標準の実装を使用できます。

const auto n = 13UL;
const auto Index = (unsigned long)log2(n);

printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

のも同様に保護する必要がnあり0ULます。

-∞が返され、FE_DIVBYZEROが発生します

私は任意IndexULONG_MAXここに設定するそのチェックで例を書きました: https //ideone.com/u26vsi


当然の帰結ephemientのgccの唯一の答えは次のとおりです。

const auto n = 13UL;
unsigned long Index;

_BitScanReverse(&Index, n);
printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

_BitScanReverse状態に関するドキュメントIndexは次のとおりです。

見つかった最初のセットビット(1)のビット位置をロード

実際に私がいる場合は検出されませんでしたnある0ULことがIndexに設定され0UL、それがためになると同じように、n1UL。しかし、の場合は文書で保証唯一nのは、0ULリターンがあるということです。

セットビットが見つからなかった場合は0

したがって、log2上記の好ましい実装と同様に、Indexこの場合、フラグ付きの値に設定して戻り値を確認する必要があります。ULONG_MAXこのフラグ値の使用例をここに書き直しました:http : //rextester.com/GCU61409


いいえ、入力がの場合のみ_BitScanReverse 0を返します。これは、出力ではなく入力のみに基づいてZFを設定するx86の命令に似ています。興味深いことに、ビットが見つからない場合、MSはドキュメントを未設定のままにしておくと言います。これもx86 asmの動作と一致します。(宛先がSRC = 0に変更されていない登録が、Intelはちょうど彼らのCPUがリーブ・無修正動作を実装んが、未定義の出力を言う残しとして、AMDの文書、それを。)これはx86ののとは違っています、-が見つからないため。0BSRindex1bsrlzcnt32
Peter Cordes

@PeterCordes _BitScanReverseはゼロベースのインデックスを使用するため、n1の場合、設定されたビットのインデックスは実際にnは0になります。残念ながら、0の場合、出力も0になります:(これは、区別n私が通信しようとしていた何が1または0のあなたがこれを言うには良い方法があると思います。?
Jonathan Mee

あなたはそれがどのように設定されるかについて話していると思いますIndex。それは戻り値ではありません。入力がゼロの場合はfalseであるブール値を返します(これが、通常返されるのではなく、参照によってインデックスが渡される理由です)。 godbolt.org/g/gQKJdE。そして、私はチェックしました:MSのドキュメントの言葉遣いにもかかわらず、_BitScanReverseIndexを未設定のままにしないでn==0ください:たまたま使用したレジスターにある値を取得するだけです。(あなたの場合、おそらくそれがIndex後で使用したのと同じレジスタで、が表示されます0)。
Peter Cordes

この質問にはc ++のタグは付いていません。
テクノサウルス2018年

@technosaurusありがとう、忘れてしまいました。質問がCであることを考えると、実際にはlog2 C99以来です。
Jonathan Mee

2

ビット演算子を考えてください。

私はその質問を初めて誤解しました。左端のビットセット(その他はゼロ)を使用してintを生成する必要があります。cmpがその値に設定されていると仮定します。

position = sizeof(int)*8
while(!(n & cmp)){ 
   n <<=1;
   position--;
}

文字列に変換するとはどういう意味ですか?ffsの定義はintを取り、intを返します。変換はどこにありますか?そして、一言でビットを探す場合、変換はどのような目的に役立ちますか?
dreamlax 2009年

その機能を知りませんでした。
Vasil

する8必要がありますCHAR_BIT。同じ入力で繰り返し使用しない限り、ループの終了時に分岐の予測ミスが発生するため、これが最速の方法である可能性はほとんどありません。また、小さな入力(ゼロのロット)の場合は、たくさんループする必要があります。これは、単体テストで検証しやすいバージョンとして使用して、最適化されたバージョンと比較するフォールバックの方法に似ています。
Peter Cordes

2

Joshのベンチマークを拡張します...次のようにclzを改善できます

/***************** clz2 ********************/

#define NUM_OF_HIGHESTBITclz2(a) ((a)                              \
                  ? (((1U) << (sizeof(unsigned)*8-1)) >> __builtin_clz(a)) \
                  : 0)

asmについて:bsrとbsrlがあることに注意してください(これは「長い」バージョンです)。通常のものは少し速いかもしれません。


1

あなたがしようとしているのは整数の整数log2を計算することであることに注意してください、

#include <stdio.h>
#include <stdlib.h>

unsigned int
Log2(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1; int k=0;
    for( step = 1; step < bits; ) {
        n |= (n >> step);
        step *= 2; ++k;
    }
    //printf("%ld %ld\n",x, (x - (n >> 1)) );
    return(x - (n >> 1));
}

一度に複数のビットを検索できることを確認してください。

unsigned int
Log2_a(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1;
    int step2 = 0;
    //observe that you can move 8 bits at a time, and there is a pattern...
    //if( x>1<<step2+8 ) { step2+=8;
        //if( x>1<<step2+8 ) { step2+=8;
            //if( x>1<<step2+8 ) { step2+=8;
            //}
        //}
    //}
    for( step2=0; x>1L<<step2+8; ) {
        step2+=8;
    }
    //printf("step2 %d\n",step2);
    for( step = 0; x>1L<<(step+step2); ) {
        step+=1;
        //printf("step %d\n",step+step2);
    }
    printf("log2(%ld) %d\n",x,step+step2);
    return(step+step2);
}

このアプローチでは、バイナリ検索を使用します

unsigned int
Log2_b(unsigned long x)
{
    unsigned long n = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int hbit = bits-1;
    unsigned int lbit = 0;
    unsigned long guess = bits/2;
    int found = 0;

    while ( hbit-lbit>1 ) {
        //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        //when value between guess..lbit
        if( (x<=(1L<<guess)) ) {
           //printf("%ld < 1<<%d %ld\n",x,guess,1L<<guess);
            hbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
        //when value between hbit..guess
        //else
        if( (x>(1L<<guess)) ) {
            //printf("%ld > 1<<%d %ld\n",x,guess,1L<<guess);
            lbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
    }
    if( (x>(1L<<guess)) ) ++guess;
    printf("log2(x%ld)=r%d\n",x,guess);
    return(guess);
}

おそらくもっと読みやすい別の二分探索法

unsigned int
Log2_c(unsigned long x)
{
    unsigned long v = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int step = bits;
    unsigned int res = 0;
    for( step = bits/2; step>0; )
    {
        //printf("log2(%ld) v %d >> step %d = %ld\n",x,v,step,v>>step);
        while ( v>>step ) {
            v>>=step;
            res+=step;
            //printf("log2(%ld) step %d res %d v>>step %ld\n",x,step,res,v);
        }
        step /= 2;
    }
    if( (x>(1L<<res)) ) ++res;
    printf("log2(x%ld)=r%ld\n",x,res);
    return(res);
}

これらをテストしたいので、

int main()
{
    unsigned long int x = 3;
    for( x=2; x<1000000000; x*=2 ) {
        //printf("x %ld, x+1 %ld, log2(x+1) %d\n",x,x+1,Log2(x+1));
        printf("x %ld, x+1 %ld, log2_a(x+1) %d\n",x,x+1,Log2_a(x+1));
        printf("x %ld, x+1 %ld, log2_b(x+1) %d\n",x,x+1,Log2_b(x+1));
        printf("x %ld, x+1 %ld, log2_c(x+1) %d\n",x,x+1,Log2_c(x+1));
    }
    return(0);
}

1

これは「まだ別の」アプローチなので、これを入れることは、すでに与えられた他のものとは異なるようです。

戻り-1もしx==0そうでない場合は、floor( log2(x)) (MAX結果31)

32ビットから4ビットの問題に減らしてから、テーブルを使用します。おそらく洗練されていませんが、実用的です。

これは私が使いたくないときに使うものです __builtin_clz、移植性の問題でます。

よりコンパクトにするために、代わりにループを使用して削減し、毎回4をrに追加して、最大7回の反復を行うことができます。または、(64ビットの)などのハイブリッド:ループして8に削減し、テストして4に削減します。

int log2floor( unsigned x ){
   static const signed char wtab[16] = {-1,0,1,1, 2,2,2,2, 3,3,3,3,3,3,3,3};
   int r = 0;
   unsigned xk = x >> 16;
   if( xk != 0 ){
       r = 16;
       x = xk;
   }
   // x is 0 .. 0xFFFF
   xk = x >> 8;
   if( xk != 0){
       r += 8;
       x = xk;
   }
   // x is 0 .. 0xFF
   xk = x >> 4;
   if( xk != 0){
       r += 4;
       x = xk;
   }
   // now x is 0..15; x=0 only if originally zero.
   return r + wtab[x];
}

1

すごい、それは多くの答えでした。古い質問に答えて申し訳ありません。

int result = 0;//could be a char or int8_t instead
if(value){//this assumes the value is 64bit
    if(0xFFFFFFFF00000000&value){  value>>=(1<<5); result|=(1<<5);  }//if it is 32bit then remove this line
    if(0x00000000FFFF0000&value){  value>>=(1<<4); result|=(1<<4);  }//and remove the 32msb
    if(0x000000000000FF00&value){  value>>=(1<<3); result|=(1<<3);  }
    if(0x00000000000000F0&value){  value>>=(1<<2); result|=(1<<2);  }
    if(0x000000000000000C&value){  value>>=(1<<1); result|=(1<<1);  }
    if(0x0000000000000002&value){  result|=(1<<0);  }
}else{
  result=-1;
}

この答えは別の答えとかなり似ています...まあ。


シフト量を書くの1<<kはいい感じです。マスクはどうですか?(1 << (1<<k-1)-1<< (1<<k-1)?(most optimal?最上級を比較しますか?)
greybeard '26 / 10/26

@greybeardこの質問の編集を見ると、「最適な」部分を追加したことがわかります。回答を変更したので、削除するのを忘れました。また、なぜマスクについて話しているのかわかりませんか?(どのマスクですか?私はあなたをフォローしていません)
Harry Svensson 2017年

(bit)maskは、&およびで使用されるビットを選択/クリアするために使用される値です&~。)16進定数をのように置き換えることができ((type)1<<(1<<k))-1<<(1<<k)ます。
greybeard 2017年

ああ、マスクを使っているので、すっかり忘れていました。私はこれを2か月前に回答しました...-うーん、それはコンパイル時に評価されるので、16進数の値に相当すると言います。ただし、1つは不可解で、もう1つは16進数です。
Harry Svensson

0

コード:

    // x>=1;
    unsigned func(unsigned x) {
    double d = x ;
    int p= (*reinterpret_cast<long long*>(&d) >> 52) - 1023;
    printf( "The left-most non zero bit of %d is bit %d\n", x, p);
    }

または、Y = 1を設定して、FPU命令FYL2X(Y * Log2 X)の整数部分を取得します。


ええと。何?これはどのように機能しますか?それは何らかの形でポータブルですか?
underscore_d

ウィンドウ内のコードは移植可能です。関数FYL2X()はfpu命令ですが、移植され、一部のFPU /数学ライブラリで見つかる場合があります。
jemin

@underscore_dこれは、浮動小数点数が正規化されているために機能します...変換して仮数ビットを2桁シフトして先行ゼロを削除し、このコードは指数を抽出して調整して、シフトされたビット数を決定します。それは確かにアーキテクチャに依存していませんが、遭遇したどのマシンでも動作するでしょう。
ジムバルター

これはこの回答の代替バージョンです。パフォーマンスと移植性に関するコメントについては、そこを参照してください。(具体的には、型パンニングのためのポインタキャストの非移植性。)アドレス計算を使用して、の上位32ビットのみを再読み込みします。doubleこれは、他の方法で型パンするのではなく、実際に保存/再読み込みする場合に適しています。たとえば、movqあなたのような命令のx86上でここに来るかもしれません。
Peter Cordes

また、[その回答へのコメント]にも注意してください。この方法では、(少なくとも)範囲内の値に対してこのメ​​ソッドが誤った回答を与えるという悲惨な警告が表示されます[7FFFFFFFFFFFFE00 - 7FFFFFFFFFFFFFFF]
Glenn Slayden

0

別の投稿者は、バイト幅のルックアップを使用したルックアップテーブルを提供しました。ケースでは、(代わりにちょうど256のルックアップエントリのメモリの32Kのコストで)もう少しパフォーマンスをひねり出すしたいここに用いた溶液である15ビットのルックアップテーブルをして、C#7のための.NETが

興味深い部分は、テーブルの初期化です。これは、プロセスの存続期間に必要な比較的小さなブロックなので、を使用してアンマネージメモリを割り当てMarshal.AllocHGlobalます。ご覧のとおり、最大のパフォーマンスを得るために、例全体はネイティブとして記述されています。

readonly static byte[] msb_tab_15;

// Initialize a table of 32768 bytes with the bit position (counting from LSB=0)
// of the highest 'set' (non-zero) bit of its corresponding 16-bit index value.
// The table is compressed by half, so use (value >> 1) for indexing.
static MyStaticInit()
{
    var p = new byte[0x8000];

    for (byte n = 0; n < 16; n++)
        for (int c = (1 << n) >> 1, i = 0; i < c; i++)
            p[c + i] = n;

    msb_tab_15 = p;
}

上記のコードを使用して、テーブルを1回だけ初期化する必要があります。読み取り専用であるため、単一のグローバルコピーを共有して同時アクセスできます。このテーブルを使用すると、さまざまな整数幅(8、16、32、および64ビット)について、ここで探している整数log 2をすばやく検索できます。

0「最上位セットビット」の概念が定義されていない唯一の整数であるのテーブルエントリには、値が指定されていることに注意してください-1。この区別は、以下のコードで0値の上位ワードを適切に処理するために必要です。さらに苦労せずに、さまざまな整数プリミティブのそれぞれのコードを次に示します。

ulong(64ビット)バージョン

/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(this ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 0x40) - 1;      // handles cases v==0 and MSB==63

    int j = /**/ (int)((0xFFFFFFFFU - v /****/) >> 58) & 0x20;
    j |= /*****/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

uint(32ビット)バージョン

/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(uint v)
{
    if ((int)v <= 0)
        return (int)((v >> 26) & 0x20) - 1;     // handles cases v==0 and MSB==31

    int j = (int)((0x0000FFFFU - v) >> 27) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

上記のさまざまなオーバーロード

public static int HighestOne(long v) => HighestOne((ulong)v);
public static int HighestOne(int v) => HighestOne((uint)v);
public static int HighestOne(ushort v) => msb_tab_15[v >> 1];
public static int HighestOne(short v) => msb_tab_15[(ushort)v >> 1];
public static int HighestOne(char ch) => msb_tab_15[ch >> 1];
public static int HighestOne(sbyte v) => msb_tab_15[(byte)v >> 1];
public static int HighestOne(byte v) => msb_tab_15[v >> 1];

これは、専用のパフォーマンステストハーネスと比較した多くの代替案の.NET 4.7.2での最高のパフォーマンスを表す、完全に機能するソリューションです。これらのいくつかを以下に示します。テストパラメータは、すべての65ビット位置の均一密度、つまり0 ... 31/63プラス値0(結果-1を生成)でした。ターゲットインデックス位置ののビットはランダムに埋められました。テストはx64のみのリリースモードで、JIT最適化が有効になっています。




これで私の正式な回答は終わりです。以下は、上記のコードのパフォーマンスと正確さを検証するために実行したテストに関連する代替テスト候補のカジュアルなメモとソースコードへのリンクです。


上記で提供され、Tab16Aとしてコード化されたバージョンは、多くの実行で一貫した勝者でした。これらのさまざまな候補者は、アクティブな作業/スクラッチ形式で、ここここ、およびここにあります

 1候補。HighestOne_Tab16A622,496
 2候補。HighestOne_Tab16C628,234
 3つの候補者。HighestOne_Tab8A649,146
 4候補。HighestOne_Tab8B656,847
 5候補。HighestOne_Tab16B657,147
 6人の候補者。HighestOne_Tab16D659,650
 7 _highest_one_bit_UNMANAGED.HighestOne_U 702,900
 8 de_Bruijn.IndexOfMSB 709,672
 9 _old_2.HighestOne_Old2 715,810
10 _test_A.HighestOne8 757,188
11 _old_1.HighestOne_Old1 757,925
12 _test_A.HighestOne5(unsafe)760,387
13 _test_B.HighestOne8(危険)763,904
14 _test_A.HighestOne3(危険)766,433
15 _test_A.HighestOne1(危険)767,321
16 _test_A.HighestOne4(unsafe)771,702
17 _test_B.HighestOne2(危険)772,136
18 _test_B.HighestOne1(危険)772,527
19 _test_B.HighestOne3(unsafe)774,140
20 _test_A.HighestOne7(unsafe)774,581
21 _test_B.HighestOne7(unsafe)775,463
22 _test_A.HighestOne2(unsafe)776,865
23候補。HighestOne_NoTab777,698
24 _test_B.HighestOne6(unsafe)779,481
25 _test_A.HighestOne6(unsafe)781,553
26 _test_B.HighestOne4(unsafe)785,504
27 _test_B.HighestOne5(unsafe)789,797
28 _test_A.HighestOne0(unsafe)809,566
29 _test_B.HighestOne0(unsafe)814,990
30 _highest_one_bit.HighestOne 824,345
30 _bitarray_ext.RtlFindMostSignificantBit 894,069
31候補。HighestOne_Naive898,865

注目すべきは、ntdll.dll!RtlFindMostSignificantBitP / Invoke経由のパフォーマンスがひどいことです 。

[DllImport("ntdll.dll"), SuppressUnmanagedCodeSecurity, SecuritySafeCritical]
public static extern int RtlFindMostSignificantBit(ulong ul);

実際の機能全体を次に示しますので、これは本当に残念です。

    RtlFindMostSignificantBit:
        bsr rdx, rcx  
        mov eax,0FFFFFFFFh  
        movzx ecx, dl  
        cmovne      eax,ecx  
        ret

これらの5行に起因するパフォーマンスの低下は想像できません。そのため、マネージド/ネイティブトランジションのペナルティが原因である必要があります。また、テストでshort128バイト(および256バイト)byte(8ビット)のルックアップテーブルよりも32KB(および64KB)(16ビット)のダイレクトルックアップテーブルが実際に優先されたことにも驚きました。次の方が16ビットルックアップの方が競争力があると思いましたが、後者は常にこれを上回っていました。

public static int HighestOne_Tab8A(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    int j;
    j =  /**/ (int)((0xFFFFFFFFU - v) >> 58) & 32;
    j += /**/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 16;
    j += /**/ (int)((0x000000FFU - (v >> j)) >> 60) & 8;
    return j + msb_tab_8[v >> j];
}

最後に指摘するのは、deBruijnメソッドの方がうまくいかなかったことに非常にショックを受けたことです。これは、私が以前から広範に使用していた方法です。

const ulong N_bsf64 = 0x07EDD5E59A4E28C2,
            N_bsr64 = 0x03F79D71B4CB0A89;

readonly public static sbyte[]
bsf64 =
{
    63,  0, 58,  1, 59, 47, 53,  2, 60, 39, 48, 27, 54, 33, 42,  3,
    61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22,  4,
    62, 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21,
    56, 45, 25, 31, 35, 16,  9, 12, 44, 24, 15,  8, 23,  7,  6,  5,
},
bsr64 =
{
     0, 47,  1, 56, 48, 27,  2, 60, 57, 49, 41, 37, 28, 16,  3, 61,
    54, 58, 35, 52, 50, 42, 21, 44, 38, 32, 29, 23, 17, 11,  4, 62,
    46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, 31, 22, 10, 45,
    25, 39, 14, 33, 19, 30,  9, 24, 13, 18,  8, 12,  7,  6,  5, 63,
};

public static int IndexOfLSB(ulong v) =>
    v != 0 ? bsf64[((v & (ulong)-(long)v) * N_bsf64) >> 58] : -1;

public static int IndexOfMSB(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    v |= v >> 1; v |= v >> 2;  v |= v >> 4;   // does anybody know a better
    v |= v >> 8; v |= v >> 16; v |= v >> 32;  // way than these 12 ops?
    return bsr64[(v * N_bsr64) >> 58];
}

このSOの質問では、deBruijnメソッドの優れた優れた方法について多くの議論があり、私も同意する傾向がありました。私の推測では、deBruijnメソッドと直接ルックアップテーブルメソッド(私が最速であることがわかった)はどちらもテーブルルックアップを実行する必要があり、どちらも非常に最小限の分岐しかありませんが、deBruijnのみが64ビット乗算演算を持っています。IndexOfMSBここでは関数のみをテストしましたが、deBruijnはテストしていませんが、IndexOfLSB操作が非常に少ないため(上記を参照)、後者のほうがはるかに良い可能性があり、LSBでも引き続き使用する可能性があります。


1
最新のx86 CPUのL1Dキャッシュはわずか32kiBです。同じ値を繰り返し使用しない限り、大きなLUTは小さなLUTよりも悪くなる可能性があります。そうしないと、キャッシュミスが頻繁に発生します。
Peter Cordes

0

私の控えめな方法は非常に簡単です:

MSB(x)= INT [Log(x)/ Log(2)]

変換:xのMSBは、(Log of Base xをLog of Base 2で割った値)の整数値です。

これは、どのプログラミング言語にも簡単かつ迅速に適応できます。電卓で試して、動作することを確認してください。


興味があるのは開発者の効率だけです。ランタイム効率が必要な場合は、代替アルゴリズムが必要です。
ミッコランタライネン

これは丸め誤差のために失敗する可能性があります。例えば、CPythonの2及び3において、int(math.log((1 << 48) - 1) / math.log(2))48である
benrg

0

以下は、GCCClangで機能するCの高速ソリューションです。コピーして貼り付ける準備ができました。

#include <limits.h>

unsigned int fls(const unsigned int value)
{
    return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}

unsigned long flsl(const unsigned long value)
{
    return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}

unsigned long long flsll(const unsigned long long value)
{
    return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}

そしてC ++の少し改良されたバージョン。

#include <climits>

constexpr unsigned int fls(const unsigned int value)
{
    return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}

constexpr unsigned long fls(const unsigned long value)
{
    return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}

constexpr unsigned long long fls(const unsigned long long value)
{
    return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}

コードは、valueそうでないことを前提としています0。0を許可する場合は、変更する必要があります。


0

私はあなたの質問が整数(以下vと呼ばれる)であり、符号なし整数ではないことを想定しています。

int v = 612635685; // whatever value you wish

unsigned int get_msb(int v)
{
    int r = 31;                         // maximum number of iteration until integer has been totally left shifted out, considering that first bit is index 0. Also we could use (sizeof(int)) << 3 - 1 instead of 31 to make it work on any platform.

    while (!(v & 0x80000000) && r--) {   // mask of the highest bit
        v <<= 1;                        // multiply integer by 2.
    }
    return r;                           // will even return -1 if no bit was set, allowing error catch
}

記号を考慮せずに機能させる場合は、「v << = 1;」を追加できます。ループの前(それに応じてr値を30に変更)。何か忘れた場合はお知らせください。まだテストしていませんが、問題なく動作するはずです。


v <<= 1未定義の動作(UB)v < 0です。
chux-モニカを

0x8000000、多分あなたはそこに余分な0を意味します。
MM
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.