web-dev-qa-db-ja.com

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

answer から 別の質問 で使用されている興味深いテクニックを見つけたので、もう少しよく理解したいと思います。

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

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

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

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

.で示されるビットの値は気にせず、保存する必要はありません。

ソリューション は、不要なビットをマスクし、結果に0x2040810204081を掛けることでした。結局のところ、これはトリックを実行します。

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

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

297
NPE

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

シングルバイトを操作する簡単な例を見てみましょう。簡単にするために、符号なし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(oooh-すべてに対する答え!)が必要だと思うでしょう。結果:

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個のゼロ、右側にn-m個のゼロが必要です。特に、ビットが元の番号と同じ順序になっていない場合は、並べ替え後の順序になるため、これは元の基準に対する重要な改善です。これは、たとえば、16ビットのWord

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が終了する場合nの位置mで、左にm-1個のゼロ、右にnm個のゼロが必要です。

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

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

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

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

ABCDEFGHIJKLMNOP

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

これまで、abcde(位置ABCDE)以下は何でもかまわないと考えていましたが、実際には@Ternaryが指摘したように、b=1, c=1, d=1ならば(b+c)Gは、ビットを位置Fに運びます。つまり、位置F(d+1)は、ビットをEに運び、結果は台無しになります。乗算により最下位ビットの0からのパディングが発生するため、対象の最下位ビット(この例ではc)の右側のスペースは重要ではないことに注意してください。

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

(nm)基準を満たすビット数だけでなく、(n-m + 1)などにあるビット数も調べる必要があります。その数をQ0(正確にはn-mからnextビット)、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つ増やします。

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

232
Floris

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

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

この文が実際に定理である場合、定数 'mask'および 'multiplicand'の一部の値がこのプロパティを満たすことは事実です。定理証明者が理解できるもの、つまり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を検索すると、開始できます。

152
Syzygy

乗算器の1ビットごとに、ビットの1つを正しい位置にコピーします。

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

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

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

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

88
starblue

(これまで見たことがない。このトリックは素晴らしい!)

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

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

説明のためにいくつかの例を示します。

...a..b...c...は機能します(aの後の2ビット、bの前と後のビット、およびcの前の2ビットには誰もいません):

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

...a.b....c...は、baの後の2ビットにあるため失敗します(そして、aをシフトすると、他の誰かの場所に引き込まれます):

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

...a...b.c...は、bcの前の2ビットにあるため失敗します(cをシフトすると、他の誰かの場所にプッシュされます):

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

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

  a000bc000d
+ 0bc000d000
+ 000d000000
= abcd000000

しかし、問題があります。n-iの代わりにn-1を使用すると、次のシナリオが発生する可能性があります。最後にマスクするものがありますが、そのキャリービットは重要なマスクされていない範囲で干渉することになりますか? (注:n-1要件は、ithビットをシフトするときに、マスクされていない範囲の後のi-1ビットがクリアであることを確認することにより、これが起こらないようにします。

...a...b..c...d...キャリービットの潜在的な障害、cbの後のn-1にありますが、n-i基準を満たしています。

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

では、なぜ「n-1ビットのスペース」要件に戻らないのですか? より良くできるから

...a....b..c...d..Failedn-1ビットのスペース」テスト、ただしworksビット抽出のトリック:

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

ないが重要なビット間にn-1スペースを持っているが、それでもそうであるこれらのフィールドを特徴付ける良い方法を思い付くことができません私たちの操作のために働きます。ただし、事前に知っている興味のあるビットなので、フィルターをチェックしてキャリービットの衝突が発生しないことを確認できます。

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

ビットを抽出するマジックシフト/乗算は、2つが等しい場合にのみ機能します。

29
Ternary

この非常に興味深い質問に対するすでに優れた答えに加えて、このビットごとの乗算のトリックは、2007年以降、コンピューターチェスコミュニティで知られており、 Magic BitBoards .

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

魔法の乗算を使用して、これらの分散Kビットを64ビット整数の下位Kビットにマッピングできます。これらの下位Kビットを使用して、Originスクエアのピースが実際に移動できる許可されたスクエアを表す事前に計算されたビットボードのテーブルにインデックスを付けることができます(ピースをブロックするなど)

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

これらの魔法の乗数を見つけるには、 exhaustive search (早期カットオフで最適化)または trial and erorr (例えば、たくさんのランダムな64ビット整数を試す)。動きの生成中に、マジック定数が見つからないビットパターンは使用されていません。ただし、マップ対象のビットに(ほぼ)隣接するインデックスがある場合、通常、ビット単位のキャリー効果が必要です。

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

12
TemplateRex