この記事はビット演算テクニック Advent Calendar 2016
の三日目の記事です。
ビット列の部分列を入れ替える操作を効率的に行えるDelta swapを紹介します。xorが好きなのでDelta swapも好きなテクニックの一つです。
行える操作
マスクとずらす量を自由に指定できます。
abcdefghijklmnop 元のビット列 0000011000011100 マスク 00000fg0000lmn00 マスクを取ったビット列 00fg0000lmn00000 3ビット左にずらしたビット列 0011000011100000 3ビット左にずらしたマスク 00cd0000ijk00000 マスクを取ったビット列 00000cd0000ijk00 3ビット右にずらしたビット列 ab00e00h000000op 入れ替えない残りのビット列 abfgecdhlmnijkop 結果のビット列
実装
まずは素朴な実装をしてみます。
uint32_t swap_bits_32(uint32_t bits, uint32_t mask, int delta) { return ((bits & mask) << delta) | ((bits >> delta) & mask) | (bits & ~(mask | (mask << delta))); }
それぞれの部分列、そしてそれ以外の場所をそれぞれマスクを取り、シフトしてORで一つにまとめます。(最適化してシフトしてからマスク取ってたりしますがまあいいでしょう)
それがDelta swapでは次のように書けます。
uint32_t delta_swap_32(uint32_t bits, uint32_t mask, int delta) { uint32_t x = (bits ^ (bits >> delta)) & mask; return bits ^ x ^ (x << delta); }
だいぶシンプルになりましたね。 ビット演算の演算回数も10回(andnotがあれば9回)から6回に減りました。
応用
Delta swapを複数回行ってビット列を任意に並べ替えることができます。
もちろん1ビットずつ入れ替えれば任意に並べ替えることができますが、 実際にはより効率的に並べ替えることができます。 32ビットなら次のようにdeltaは固定でマスクをうまく設定することで9回のDelta swapで任意の並べ替えを実現できます。(2nビットなら2n-1回(n>0))
bits = delta_swap_32(bits, mask1, 1); bits = delta_swap_32(bits, mask2, 2); bits = delta_swap_32(bits, mask3, 4); bits = delta_swap_32(bits, mask4, 8); bits = delta_swap_32(bits, mask5, 16); bits = delta_swap_32(bits, mask6, 8); bits = delta_swap_32(bits, mask7, 4); bits = delta_swap_32(bits, mask8, 2); bits = delta_swap_32(bits, mask9, 1);
証明は省略しますが、偶数ビット目と奇数ビット目に分けて再帰的に入れ替えを行って最後に偶奇を合わせる感じです。
bit matrix
16bit, 64bitのビット列をそれぞれ4x4、8x8のビットの行列(bit matrix)と思うと二次元のデータを効率的に扱えることがあります。
さて、Delta swapを使うと効率的にbit matrixを反転・回転させることができます。 たとえば対角線を軸とした反転は次のように書けます。
uint64_t flipDiagA8H1(uint64_t bits) { bits = delta_swap(bits, 0x00000000F0F0F0F0, 28); bits = delta_swap(bits, 0x0000CCCC0000CCCC, 14); return delta_swap(bits, 0x00AA00AA00AA00AA, 7); }
他の例はhttps://chessprogramming.wikispaces.com/Flipping+Mirroring+and+Rotatingにあります。
参考文献
- https://chessprogramming.wikispaces.com/General+Setwise+Operations#SwappingBits
- "The Art of Computer Programming volume 4" chess programming wiki曰く、Delta swapによる任意の入れ替えの証明が載っているそうです。