単一の乗算でビットを抽出する


301

別の質問への回答で使用されている興味深いテクニックを見て、少しよく理解したいと思います。

符号なし64ビット整数が与えられ、次のビットに関心があります。

1.......2.......3.......4.......5.......6.......7.......8.......

具体的には、次のように上位8つの位置に移動します。

12345678........................................................

で示されるビットの値は.関係ありません。それらを保持する必要はありません。

溶液は、不要なビットをマスクし、によって結果を乗算することでした0x2040810204081。結局のところ、これでうまくいきます。

この方法は一般的ですか?この手法を使用してビットのサブセットを抽出できますか?そうでない場合、方法が特定のビットのセットに対して機能するかどうかをどのようにして理解しますか?

最後に、与えられたビットを抽出するために(a?)正しい乗数をどのように見つけますか?


29
あなたはその1つの興味深いを見つけた場合は、このリストを見てみましょう:graphics.stanford.edu/~seander/bithacks.htmlそれらのAロット(AB)は、興味深い結果を達成するために、より広い整数乗算/除算を使用します。(「4つの操作でバイトのビットを反転する」の部分は、十分なスペースがなく、マスク/乗算を2回行う必要がある場合にビットシフト/乗算のトリックを処理する方法を示しています)
viraptor

@viraptor:すばらしい点。この方法の制限を理解していれば、ビット操作に関して多くのことを実現するために乗算を実際に使用できます。
Expedito 2013年

9
だから、興味深いことに、正確にあなたが記述の操作を行います(悲しいことにまだ利用できません)AVX2の命令があります:software.intel.com/sites/products/documentation/studio/composer/...
JPvdMerwe

3
巧妙なビットトゥウィドルアルゴリズムを探す別の場所は、MIT HAKMEM
Barmar、

1
Um livro queconheçosobre o assunto(e gosto bastante)éo "Hacker's Delight" link
Salles

回答:


235

非常に興味深い質問であり、巧妙なトリックです。

シングルバイトを操作する簡単な例を見てみましょう。簡単にするために、符号なし8ビットを使用します。あなたの番号がxxaxxbxxあなたが欲しいと想像してみてくださいab000000

このソリューションは、ビットマスキングとそれに続く乗算という2つのステップで構成されていました。ビットマスクは、興味のないビットをゼロに変換する単純なAND演算です。上記の場合、マスクは00100100結果になり00a00b00ます。

ここで難しいのは、それをに変えることab......です。

乗算は、シフトおよび加算演算の集まりです。重要なのは、オーバーフローが不要なビットを「シフト」して、必要なビットを適切な場所に配置できるようにすることです。

4(00000100)で乗算すると、すべてが2だけ左にシフトされ、次のようになりますa00b0000。を上bに移動するには、1(aを正しい位置に保つ)+ 4(bを上に移動する)を掛ける必要があります。この合計は5で、以前の4と組み合わせると、マジックナンバー20またはを取得し00010100ます。オリジナルは00a00b00マスキング後のものです。乗算は以下を与えます:

000000a00b000000
00000000a00b0000 +
----------------
000000a0ab0b0000
xxxxxxxxab......

このアプローチから、より大きな数とより多くのビットに拡張できます。

あなたが尋ねた質問の1つは、「これは任意のビット数で実行できますか?」でした。複数のマスキング操作または乗算を許可しない限り、答えは「いいえ」だと思います。問題は「衝突」の問題です。たとえば、上記の問題の「迷走b」などです。これをのような数にする必要があると想像してくださいxaxxbxxcx。以前のアプローチに従って、{x 2、x {1 + 4 + 16}} = x 42が必要だと思うでしょう(ああ、すべてに対する答えです!)。結果:

00000000a00b00c00
000000a00b00c0000
0000a00b00c000000
-----------------
0000a0ababcbc0c00
xxxxxxxxabc......

ご覧のとおり、それでも機能しますが、「ただ」だけです。ここで重要なのは、必要なビットの間に「十分なスペース」があり、すべてを絞り込めることです。cの直後に4番目のビットdを追加することはできませんでした。c+ dを取得するインスタンスが取得され、ビットが運ぶ可能性があるためです...

正式な証明がなければ、私はあなたの質問のより興味深い部分に次のように答えます:「いいえ、これはどのビット数でも機能しません。Nビットを抽出するには、目的のビットの間に(N-1)スペースが必要です抽出するか、追加のマスク乗算ス​​テップを実行してください。」

「ビット間に(N-1)ゼロがなければならない」ルールについて私が考えることができる唯一の例外はこれです:オリジナルで互いに隣接している2つのビットを抽出し、かつそれらを同じ順序で、その後もそれを行うことができます。そして、(N-1)ルールの目的のために、それらは2ビットとしてカウントされます。

別の洞察があります-以下の@Ternaryの回答に触発されました(そこでの私のコメントを参照してください)。興味深いビットごとに、そこに行く必要があるビットのためのスペースが必要なだけ、その右側にゼロが必要です。ただし、左に結果ビットがあるのと同じだけ左にビットが必要です。したがって、ビットbがnの位置mで終了する場合、その左側にm-1個のゼロがあり、その右側にnm個のゼロがある必要があります。特に、ビットが元の番号と同じ順序になっていない場合は、並べ替え後のようになるため、これは元の基準に対する重要な改善です。これは、たとえば、16ビットワード

a...e.b...d..c..

にシフトすることができます

abcde...........

eとbの間にはスペースが1つしかなく、dとcの間には2つ、他のスペースの間には3つしかありません。N-1に何が起こった?? この場合は、a...e「1ブロック」になります。1を掛けると、正しい場所に到着するので、「無料でeが手に入りました」。同じことがbとdにも当てはまります(bは右側に3つのスペースが必要で、dは左側に同じ3つのスペースが必要です)。したがって、マジックナンバーを計算すると、重複があることがわかります。

a: << 0  ( x 1    )
b: << 5  ( x 32   )
c: << 11 ( x 2048 )
d: << 5  ( x 32   )  !! duplicate
e: << 0  ( x 1    )  !! duplicate

明らかに、これらの番号を別の順序で使用したい場合は、さらに間隔を空ける必要があります。(N-1)ルールを再定式化できます。「ビット間に少なくとも(N-1)のスペースがある場合、または最終結果のビットの順序がわかっている場合、ビットbがmの位置に到達すると、常に機能します。 n、左にm-1個のゼロ、右にnm個のゼロが必要です。」

@Ternaryは、「ターゲット領域のすぐ右」にビットからキャリーが追加される可能性があるため、つまり、探しているビットがすべて1の場合、このルールはうまく機能しないことを指摘しました。上記の例を続けて、16ビットワードの5つの密にパックされたビットを使用します。

a...e.b...d..c..

簡単にするために、ビット位置に名前を付けます ABCDEFGHIJKLMNOP

私たちがやろうとしている数学は

ABCDEFGHIJKLMNOP

a000e0b000d00c00
0b000d00c0000000
000d00c000000000
00c0000000000000 +
----------------
abcded(b+c)0c0d00c00

これまでは、以下abcde(positions ABCDE)は問題ではないと考えていましたが、実際には、@ Ternaryが指摘したようb=1, c=1, d=1(b+c)、その位置Gにあるとビットがposition Fに移動します。つまり(d+1)、位置FにあるビットがE-結果は台無しです。c乗算は最下位ビットのbeyoneからのゼロのパディングを引き起こすため、対象の最下位ビット(この例では)の右側のスペースは重要ではないことに注意してください。

したがって、(m-1)/(nm)ルールを変更する必要があります。「正確に(nm)未使用のビットが右側にある(上記の例では「c」であるパターンの最後のビットを数えない)複数のビットがある場合、ルールを強化する必要があります。繰り返します!

(nm)基準を満たすビット数だけでなく、(n-m + 1)などのビット数も調べる必要があります。それらの数をQ0(n-m次のビットまで)、Q1( n-m + 1)、最大Q(N-1)(n-1)。次に、キャリーのリスクがあります

Q0 > 1
Q0 == 1 && Q1 >= 2
Q0 == 0 && Q1 >= 4
Q0 == 1 && Q1 > 1 && Q2 >=2
... 

これを見てみると、簡単な数式を書けば

W = N * Q0 + (N - 1) * Q1 + ... + Q(N-1)

結果はW > 2 * Nであり、RHS基準を1ビットだけ増やす必要があります(n-m+1)。この時点で、操作は安全W < 4です。それでもうまくいかない場合は、基準をもう1つ増やします。

上記に従うことはあなたの答えへの長い道のりを得ると思います...


1
すごい。もう1つの微妙な問題:m-1 / nmテストはキャリービットが原因で時々失敗します。a ... b..c ... dを試してください。5番目のビットでb + cが表示され、両方が1の場合、キャリービットが作成されてd(!)
Ternary

1
upshot:n-1ビットのスペースは機能するはずの構成を禁止し(iea ... b..c ... d)、m-1 / nmは機能しない構成を許可します(a ... b..c ... d)。機能する機能と機能しない機能を簡単に特徴付ける方法を思いつきませんでした。
Ternary 2013年

あなたは上手い!キャリーの問題は、「保護」として各ビットの右側に少し多くのスペースが必要であることを意味します。一見すると、最小nmが右にあるビットが少なくとも2つある場合は、スペースを1だけ増やす必要があります。より一般的には、そのようなビットがPの場合、log2(P)ビットを追加する必要があります。最小(mn)を持つものの権利。あなたに合っていると思いますか?
Floris 2013年

さて、その最後のコメントは単純すぎました。私が最近編集した答えは、log2(P)が正しいアプローチではないことを示していると思います。@Ternary自身の回答(下記)は、保証された解決策がない場合に特定のビットの組み合わせをどのように判断できるかをエレガントに示しています-上記の作業は、それについてさらに詳しく説明していると思います。
フローリス2013年

1
偶然かもしれませんが、この回答は賛成票の数が127に達したときに承認されました。ここまで読んだら、私と一緒に笑顔になります...
Floris

154

とても興味深い質問です。私は私の2セントでうそをついています。つまり、ビットベクトル理論の上の1次論理の観点からこのような問題を管理することができれば、定理証明者はあなたの友人であり、潜在的に非常に高速に提供できます。あなたの質問への答え。求められている問題を定理として再度述べましょう。

「いくつかの64ビット定数「マスク」と「被乗数」が存在するため、すべての64ビットビットベクトルxについて、式y =(x&mask)*被乗数では、y.63 == x.63になります。 、y.62 == x.55、y.61 == x.47など。」

この文が実際に定理である場合、定数「マスク」および「被乗数」の一部の値がこのプロパティを満たしていることは事実です。それでは、定理の証明者が理解できるもの、つまりSMT-LIB 2入力についてこれを言いましょう。

(set-logic BV)

(declare-const mask         (_ BitVec 64))
(declare-const multiplicand (_ BitVec 64))

(assert
  (forall ((x (_ BitVec 64)))
    (let ((y (bvmul (bvand mask x) multiplicand)))
      (and
        (= ((_ extract 63 63) x) ((_ extract 63 63) y))
        (= ((_ extract 55 55) x) ((_ extract 62 62) y))
        (= ((_ extract 47 47) x) ((_ extract 61 61) y))
        (= ((_ extract 39 39) x) ((_ extract 60 60) y))
        (= ((_ extract 31 31) x) ((_ extract 59 59) y))
        (= ((_ extract 23 23) x) ((_ extract 58 58) y))
        (= ((_ extract 15 15) x) ((_ extract 57 57) y))
        (= ((_ extract  7  7) x) ((_ extract 56 56) y))
      )
    )
  )
)

(check-sat)
(get-model)

そして、定理証明者Z3にこれが定理であるかどうかを聞いてみましょう。

z3.exe /m /smt2 ExtractBitsThroughAndWithMultiplication.smt2

結果は次のとおりです。

sat
(model
  (define-fun mask () (_ BitVec 64)
    #x8080808080808080)
  (define-fun multiplicand () (_ BitVec 64)
    #x0002040810204081)
)

ビンゴ!元の投稿の結果を0.06秒で再現します。

これをより一般的な観点から見ると、これは一次プログラム合成問題のインスタンスであると見なすことができます。の検索で"program synthesis" filetype:pdf始められます。


2
私は感銘を受けて!「ビットベクトル理論上の一次論理」が人々が研究した本当の主題であることさえ知らなかった-それがそのような興味深い結果を与えることができることは言うまでもない。これを共有してくれてありがとう。
フローリス2013年

@AndrewBacker:いわゆる「仕事としてのSO」ということのどこに、誰かが私を照らしてくれませんか?つまり、何も支払いません。SO担当者だけで生活することはできません。多分それはあなたにインタビューでいくつかのポイントを与えることができます。多分。職場がSO担当者の価値を認めるのに十分であり、それが与えられていない場合...
モニカを

3
承知しました。SOはまた、多くの人々のためのゲーム(ポイントのあるものはすべて)です。最初のコメントを投稿してカルマを取得できるように、/ r / newでの狩猟のような人間の性質だけです。答えがまだ良い限り、それについて悪いことは何もありません。誰かが実際にそうしたことに気づく可能性が高いときに、誰かの時間と労力に賛成票を投じることができて、私はただ幸せです。励ましは良いことです:)そして...それは本当に古いコメントでした、そしてまだ本当です。はっきりしないのかわかりません。
Andrew Backer 2013

88

乗算器のすべての1ビットは、ビットの1つを正しい位置にコピーするために使用されます。

  • 1はすでに正しい位置にあるため、を掛け0x0000000000000001ます。
  • 2左に7ビット位置シフトする必要があるため、乗算します0x0000000000000080(ビット7が設定されています)。
  • 3左に14ビット位置シフトする必要があるため、乗算します0x0000000000000400(ビット14が設定されています)。
  • などまで
  • 8左に49ビット位置シフトする必要があるため、乗算します0x0002000000000000(ビット49が設定されています)。

乗数は、個々のビットの乗数の合計です。

これが機能するのは、収集するビットが近すぎないためです。そのため、このスキームで一緒に属さないビットの乗算は、64ビットを超えるか、下位のドントケア部分になります。

元の数の他のビットはでなければならないことに注意してください0。これは、AND演算でマスクすることで実現できます。


2
素晴らしい説明!あなたの短い答えは、「マジックナンバー」の価値を素早く見つけることを可能にしました。
Expedito 2013年

4
これは本当に最良の答えですが、@ florisの答えを最初に(前半)読まないと、それほど役に立ちませんでした。
Andrew Backer 2013年

29

(今まで見たことがありません。このトリックは素晴らしいです!)

nビットを抽出するとき、n-1連続しないビットの間にスペースが必要であるというフロリスの主張を少し拡張します。

私の最初の考え(これがどのようにうまく機能しないかはすぐにわかります)は、あなたがもっと上手くできると思いました:nビットを抽出したい場合、i誰かがいる場合にビットを抽出/シフトすると衝突が発生します(非-ビットと連続ii-1先行n-iビットまたは後続ビット

いくつかの例を挙げて説明します。

...a..b...c...動作します(の後の2ビットa、前と後のビットにb誰もいない、そして前の2ビットに誰もいないc):

  a00b000c
+ 0b000c00
+ 00c00000
= abc.....

...a.b....c...b2ビット後にあるため失敗しますa(シフトすると他の誰かのスポットに引っ張られますa):

  a0b0000c
+ 0b0000c0
+ 00c00000
= abX.....

...a...b.c...bは前の2ビットにあるため失敗しますc(シフトすると他の人のスポットにプッシュされますc)。

  a000b0c0
+ 0b0c0000
+ b0c00000
= Xbc.....

...a...bc...d... 連続するビットが一緒にシフトするため機能します:

  a000bc000d
+ 0bc000d000
+ 000d000000
= abcd000000

しかし、問題があります。n-i代わりにを使用する場合n-1、次のシナリオが考えられます。気になる部分の外側で衝突が発生した場合、最後にマスクされますが、キャリービットが重要なマスクされていない範囲で干渉します。 ?(および注意:thビットをシフトするときに、マスクn-1されていないi-1範囲の後のビットがクリアであることを確認することで、これが起こらないようにする要件iです)

...a...b..c...d...キャリービットの潜在的な失敗cはのn-1bですが、n-i基準を満たします:

  a000b00c000d
+ 0b00c000d000
+ 00c000d00000
+ 000d00000000
= abcdX.......

それでは、なぜ「n-1スペースのビット」要件に戻るのではないでしょうか。 私たちはより良いことができるので

...a....b..c...d.. n-1ビットの空間」テストは失敗しますが、ビット抽出のトリックでは機能します。

+ a0000b00c000d00
+ 0b00c000d000000
+ 00c000d00000000
+ 000d00000000000
= abcd...0X......

私は、これらのフィールド特徴付けるために良い方法を考え出すことはできませんしていない持っているn-1重要なビットとの間のスペースを、まだ私たちの操作のために働くだろう。ただし、どのビットに関心があるかは事前にわかっているので、キャリービットの衝突が発生しないようにフィルターをチェックできます。

(-1 AND mask) * shift期待されるすべて1の結果と比較します-1 << (64-n)(64ビット符号なしの場合)

ビットを抽出するための魔法のシ​​フト/乗算は、2つが等しい場合にのみ機能します。


私はそれが好きです-あなたは正しいです、あなたはビットごとに、そこに行く必要のあるビットのためのスペースが必要なだけ、その右側にゼロだけ必要です。しかし、それはまた、それが左に結果ビットを持っているのと同じくらい多くのビットを左に必要とします。したがって、ビットbがの位置mに到達する場合、左側にゼロ、右側にゼロnが必要です。特に、ビットが元の番号と同じ順序で並べ替えられていない場合、これは元の基準に対する重要な改善です。これは楽しいです。m-1n-m-1
フローリス

13

この非常に興味深い質問に対するすでに優れた回答に加えて、このビットごとの乗算のトリックは、2007年からコンピューターチェスコミュニティでMagic BitBoardsという名前で知られていることを知っておくと役立つでしょう。

多くのコンピューターチェスエンジンは、いくつかの64ビット整数(ビットボードと呼ばれる)を使用して、さまざまなピースセット(占有された正方形ごとに1ビット)を表します。Kブロッキングピースが存在しない場合、特定の原点の正方形のスライドピース(ルーク、ビショップ、クイーン)が最大で正方形に移動できると仮定します。K占有された正方形のビットボードでこれらの分散ビットのビット単位のANDを使用するとK、64ビット整数内に埋め込まれた特定のビットワードが得られます。

マジック乗算を使用しKて、これらの分散ビットをK64ビット整数の下位ビットにマップできます。K次に、これらの下位ビットを使用して、元の正方形のピースが実際に移動できる許可された正方形を表す、事前に計算されたビットボードのテーブルにインデックスを付けることができます(ブロックピースなどに注意してください)。

このアプローチを使用する典型的なチェスエンジンには、このような事前計算された結果を含む2つのテーブル(ルーク用に1つ、ビショップ用に1つ、両方の組み合わせを使用するクイーン用)の64エントリ(原点の正方形ごとに1つ)があります。現在、最高評価のクローズドソース(Houdini)とオープンソースチェスエンジン(Stockfish)の両方が、非常に高いパフォーマンスのためにこのアプローチを使用しています。

これらの魔法の乗数の検索は徹底的な検索(早期のカットオフで最適化)または試行錯誤(たとえば、ランダムな64ビット整数をたくさん試す)を使用して行われます。魔法の定数が見つからなかった移動パターン生成中に使用されたビットパターンはありませんでした。ただし、ビット単位のキャリー効果は通常、マップ対象のビットに(ほぼ)隣接するインデックスがある場合に必要です。

@Syzygyによる非常に一般的なSATソルバーのアプローチであるAFAIKは、コンピューターチェスでは使用されておらず、そのような魔法定数の存在と一意性に関する正式な理論もないようです。


私は、CSの正式なバックグラウンドを本格的に持っている人なら誰でも、この問題を見てすぐにSATのアプローチに飛びついたと思いました。おそらくCSの人々はチェスに興味がないと思いますか?:(
モニカを復活させる

@KubaOberそれはほとんど逆です。コンピュータチェスは、Cまたはアセンブリでプログラミングし、あらゆる種類の抽象化(C ++、テンプレート、OO)を嫌うビットいじり屋に支配されています。それは本当のCSの人を怖がらせると思います:-)
TemplateRex
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.