web-dev-qa-db-ja.com

ビット演算の実用的なアプリケーション

  1. ビット演算を何のために使用しましたか?
  2. なぜそんなに便利なのですか?
  3. 誰か非常に簡単なチュートリアルをお勧めできますか?

誰もがフラグのユースケースに夢中になっているようですが、ビットワイズ演算子の適用はそれだけではありません(おそらく最も一般的ですが)。また、C#は他の技術はおそらくほとんど使用されないほど十分に高いレベルの言語ですが、それらを知る価値はまだあります。ここに私が考えることができるものがあります:


<<および>>演算子は、2の累乗ですばやく乗算できます。もちろん、.NET JITオプティマイザーはおそらくこれを行います(他の言語の適切なコンパイラーも)。あなたは本当にマイクロ秒ごとに動揺しています、あなたは確かにこれを書くかもしれません。

これらの演算子のもう1つの一般的な使用法は、2つの16ビット整数を1つの32ビット整数に詰め込むことです。お気に入り:

int Result = (shortIntA << 16 ) | shortIntB;

これは、レガシーの理由でこのトリックを使用することがあるWin32関数との直接のインターフェースでは一般的です。

そしてもちろん、これらの演算子は、宿題の質問に答えを提供するときなど、経験の浅い人を混乱させたいときに役立ちます。 :)

ただし、実際のコードでは、代わりに乗算を使用する方がはるかに優れています。これは、読みやすさがはるかに優れており、JITがそれをshlおよびshr命令に最適化するため、パフォーマンスの低下がありません。


^演算子(XOR)を扱うかなり奇妙なトリックがいくつかあります。これは、次のプロパティがあるため、実際には非常に強力な演算子です。

  • A^B == B^A
  • A^B^A == B
  • A^Bを知っている場合、ABが何であるかを知ることはできませんが、一方を知っていれば、もう一方を計算できます。
  • 演算子は、乗算/除算/加算/減算などのオーバーフローの影響を受けません。

この演算子を使用して見たいくつかのトリック:

中間変数なしで2つの整数変数を交換する:

A = A^B // A is now XOR of A and B
B = A^B // B is now the original A
A = A^B // A is now the original B

アイテムごとに1つの追加変数のみを持つ二重リンクリスト。これはC#ではほとんど使用されませんが、すべてのバイトがカウントされる組み込みシステムの低レベルプログラミングに役立つ場合があります。

アイデアは、最初のアイテムのポインターを追跡することです。最後のアイテムへのポインタ。すべてのアイテムについて、pointer_to_previous ^ pointer_to_nextを追跡します。この方法では、どちらの端からでもリストを走査できますが、オーバーヘッドは従来のリンクリストの半分に過ぎません。トラバース用のC++コードは次のとおりです。

ItemStruct *CurrentItem = FirstItem, *PreviousItem=NULL;
while (  CurrentItem != NULL )
{
    // Work with CurrentItem->Data

    ItemStruct *NextItem = CurrentItem->XorPointers ^ PreviousItem;
    PreviousItem = CurrentItem;
    CurrentItem = NextItem;
}

最後から移動するには、最初の行をFirstItemからLastItemに変更するだけです。それは別のメモリ節約です。

C#で^演算子を定期的に使用する別の場所は、複合型である型のHashCodeを計算する必要がある場合です。お気に入り:

class Person
{
    string FirstName;
    string LastName;
    int Age;

    public int override GetHashCode()
    {
        return (FirstName == null ? 0 : FirstName.GetHashCode()) ^
            (LastName == null ? 0 : LastName.GetHashCode()) ^
            Age.GetHashCode();
    }
}
77
Vilx-

私は、アプリケーションのセキュリティのためにビット演算子を使用しています。異なるレベルをEnum内に保存します。

[Flags]
public enum SecurityLevel
{
    User = 1, // 0001
    SuperUser = 2, // 0010
    QuestionAdmin = 4, // 0100
    AnswerAdmin = 8 // 1000
}

そして、ユーザーにレベルを割り当てます。

// Set User Permissions to 1010
//
//   0010
// | 1000
//   ----
//   1010
User.Permissions = SecurityLevel.SuperUser | SecurityLevel.AnswerAdmin;

そして、実行されているアクションの許可を確認します。

// Check if the user has the required permission group
//
//   1010
// & 1000
//   ----
//   1000
if( (User.Permissions & SecurityLevel.AnswerAdmin) == SecurityLevel.AnswerAdmin )
{
    // Allowed
}
67
Justin Niessner

あなたがそうであると思われる数独を解くのがどれほど実用的かはわかりませんが、それがそうであると仮定しましょう。

ボードを表示し、自分でパズルを解くことができるが、動きが合法であることを保証する数独ソルバーまたは単純なプログラムを作成したいと想像してください。

ボード自体は、おそらく次のような2次元配列で表されます。

uint [, ] theBoard = new uint[9, 9];

0は、セルがまだ空であり、範囲[1u、9u]の値がボードの実際の値であることを意味します。

ここで、何らかの動きが合法かどうかを確認したいと想像してください。明らかに、いくつかのループでそれを行うことができますが、ビットマスクを使用すると、はるかに高速に処理できます。ルールが守られていることを確認するだけの単純なプログラムでは、それは重要ではありませんが、ソルバーでは可能です。

各行、各列a、各3x3ボックスに既に挿入されている数値に関する情報を格納するビットマスクの配列を維持できます。

uint [] maskForNumbersSetInRow = new uint[9];

uint [] maskForNumbersSetInCol = new uint[9];

uint [, ] maskForNumbersSetInBox = new uint[3, 3];

番号からビットパターンへのマッピングは、その番号セットに対応する1ビットで、非常に簡単です

1 -> 00000000 00000000 00000000 00000001
2 -> 00000000 00000000 00000000 00000010
3 -> 00000000 00000000 00000000 00000100
...
9 -> 00000000 00000000 00000001 00000000

C#では、この方法でビットパターンを計算できます(valueuintです):

uint bitpattern = 1u << (int)(value - 1u);

上の行では、ビットパターンに対応する1u00000000 00000000 00000000 00000001value - 1だけ左にシフトされます。たとえば、value == 5の場合、

00000000 00000000 00000000 00010000

最初は、各行、列、およびボックスのマスクは0です。ボードに数字を入力するたびにマスクを更新するため、新しい値に対応するビットが設定されます。

行3に値5を挿入するとします(行と列には0から番号が付けられます)。行3のマスクはmaskForNumbersSetInRow[3]に保存されます。また、挿入の前に、行3にすでに番号{1, 2, 4, 7, 9}があったと仮定します。マスクmaskForNumbersSetInRow[3]のビットパターンは次のようになります。

00000000 00000000 00000001 01001011
bits above correspond to:9  7  4 21

目標は、このマスクの値5に対応するビットを設定することです。ビット単位のOR演算子(|)を使用して実行できます。まず、値5に対応するビットパターンを作成します

uint bitpattern = 1u << 4; // 1u << (int)(value - 1u)

operator |を使用して、マスクのビットを設定しますmaskForNumbersSetInRow[3]

maskForNumbersSetInRow[3] = maskForNumbersSetInRow[3] | bitpattern;

または短い形式を使用して

maskForNumbersSetInRow[3] |= bitpattern;

00000000 00000000 00000001 01001011
                 |
00000000 00000000 00000000 00010000
                 =
00000000 00000000 00000001 01011011

これで、マスクはこの行(行3)に{1, 2, 4, 5, 7, 9}値があることを示します。

確認したい場合、行に値がある場合、operator &を使用して、対応するビットがマスクに設定されているかどうかを確認できます。その演算子の結果がマスクに適用され、その値に対応するビットパターンがゼロでない場合、値はすでに行にあります。結果が0の場合、値は行にありません。

たとえば、値3が行にあるかどうかを確認する場合は、次の方法で実行できます。

uint bitpattern = 1u << 2; // 1u << (int)(value - 1u)
bool value3IsInRow = ((maskForNumbersSetInRow[3] & bitpattern) != 0);

00000000 00000000 00000001 01001011 // the mask
                 |
00000000 00000000 00000000 00000100 // bitpattern for the value 3
                 =
00000000 00000000 00000000 00000000 // the result is 0. value 3 is not in the row.

以下は、ボードに新しい値を設定し、適切なビットマスクを最新に維持し、移動が合法かどうかを確認する方法です。

public void insertNewValue(int row, int col, uint value)
{

    if(!isMoveLegal(row, col, value))
        throw ...

    theBoard[row, col] = value;

    uint bitpattern = 1u << (int)(value - 1u);

    maskForNumbersSetInRow[row] |= bitpattern;

    maskForNumbersSetInCol[col] |= bitpattern;

    int boxRowNumber = row / 3;
    int boxColNumber = col / 3;

    maskForNumbersSetInBox[boxRowNumber, boxColNumber] |= bitpattern;

}

マスクがあると、次のように移動が合法かどうかを確認できます。

public bool isMoveLegal(int row, int col, uint value)
{

    uint bitpattern = 1u << (int)(value - 1u);

    int boxRowNumber = row / 3;
    int boxColNumber = col / 3;

    uint combinedMask = maskForNumbersSetInRow[row] | maskForNumbersSetInCol[col]
                        | maskForNumbersSetInBox[boxRowNumber, boxColNumber];

    return ((theBoard[row, col] == 0) && ((combinedMask & bitpattern) == 0u);
}
16
Maciej Hehl

ここで数十のビットをいじる例

コードはCですが、C#に簡単に適合させることができます

4
Thomas Levesque

ハードウェアと通信する必要がある場合は、ある時点で少し調整する必要があります。

ピクセル値のRGB値を抽出します。

たくさんのこと

3
James

これらは、さまざまなアプリケーションの全負荷に使用できます。ビット単位操作を使用する以前にここに投稿した質問は次のとおりです。

ビット単位のAND、ビット単位の包括的OR質問、Javaの場合

他の例については、(たとえば)フラグ付き列挙をご覧ください。

私の例では、ビット単位の演算を使用して、2進数の範囲を-128 ... 127から0..255に変更しました(その表現を符号付きから符号なしに変更)。

mSNの記事はこちら->

http://msdn.Microsoft.com/en-us/library/6a71f45d%28VS.71%29.aspx

便利です。

そして、このリンクは:

http://weblogs.asp.net/alessandro/archive/2007/10/02/bitwise-operators-in-c-or-xor-and-amp-amp-not.aspx

非常に技術的で、すべてをカバーしています。

HTH

2
Dave

アイテムの組み合わせで1つ以上のオプションがある場合、通常はビット単位で簡単に修正できます。

いくつかの例には、セキュリティビット(ジャスティンのサンプルで待機中)、日数のスケジュールなどが含まれます。

2
NotMe

最も一般的な用途の1つは、ビットフィールドを変更してデータを圧縮することです。これは、パケットで経済的にしようとするプログラムでほとんど見られます。

ビットフィールドを使用したネットワーク圧縮 の例

2
Greg Buehler

C#で最も頻繁に使用するものの1つは、ハッシュコードの作成です。それらを使用する合理的に優れたハッシュ方法がいくつかあります。例えば。私が使用する可能性のあるintであったXとYを持つ座標クラスの場合:

public override int GetHashCode()
{
  return x ^ ((y << 16) | y >> 16);
}

これにより、等しいオブジェクトによって生成された場合に等しいことが保証された数値が迅速に生成されます(等しいとは、比較される両方のオブジェクトでXとYの両方のパラメーターが同じであると仮定します)ほとんどのアプリケーションで最も一般的です)。

別の方法は、フラグ列挙を組み合わせることです。例えば。 RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase

.NETのようなフレームワークに対してコーディングする場合、通常は必要ない低レベルの操作がいくつかあります(たとえば、C#ではUTF-8をUTF-16に変換するコードを書く必要はありません。フレームワーク)、しかしもちろん誰かがそのコードを書かなければなりませんでした。

最も近い2進数に切り上げる(たとえば、1010から10000に切り上げる)など、いくつかのビット調整テクニックがあります。

        unchecked
        {
            --x;
            x |= (x >> 1);
            x |= (x >> 2);
            x |= (x >> 4);
            x |= (x >> 8);
            x |= (x >> 16);
            return ++x;
        }

これらは必要なときに役立ちますが、あまり一般的ではない傾向があります。

最後に、<< 1の代わりに* 2などの数学をマイクロ最適化するために使用することもできますが、実際のコードの意図を隠して、パフォーマンスには何もありません。また、微妙なバグを隠すこともできます。

2
Jon Hanna

これらはさまざまな理由で使用します。

  • メモリ効率の良い方法でオプションフラグを保存(そしてチェック!)
  • 計算プログラミングを行う場合、数学的演算子の代わりにビット演算を使用して、一部の演算を最適化することを検討してください(副作用に注意)
  • グレーコード
  • 列挙値の作成

私はあなたが他の人のことを考えることができると確信しています。

そうは言っても、時には自問する必要があります。努力するだけの価値があるメモリとパフォーマンスの向上です。そのようなコードを書いた後、しばらく休ませてから戻ってみましょう。あなたがそれに苦労しているなら、より保守可能なコードで書き直してください。

一方、ビット単位の操作を使用することが完全に理にかなっている場合もあります(暗号化を考えてください)。

さらに良いことに、それを他の誰かに読んでもらい、広範囲に文書化します。

1
haylem
  1. これらは、1つの制限されたサイズの変数を介して関数に多くの引数を渡すために使用できます。
  2. 利点は、低いメモリオーバーヘッドまたは低いメモリコストです。したがって、パフォーマンスが向上します。
  3. 私はその場でチュートリアルを書くことはできませんが、彼らはそこにいると確信しています。
1
C Johnson

バイナリソート。実装がビットシフト演算子の代わりに除算演算子を使用している問題がありました。これにより、コレクションが10,000,000を超えるサイズになった後にBSが失敗しました。

1
Woot4Moo

ゲーム!

昔、私はリバーシのプレイヤーのピースを表すためにそれを使用していました。 8X8なので、longタイプが必要でした。たとえば、ボード上のすべてのピースがどこにあるかを知りたい場合-両方のプレーヤーのピースがorです。
プレーヤーのすべての可能なステップが必要な場合は、右に言います-あなたは>>プレイヤーのピースを1で表現し、ANDで相手のピースと一緒になり、共通の1が存在するかどうかを確認します(つまり、右側に敵のピースがあることを意味します)。その後、あなたはそれを続けます。あなたがあなた自身の部分に戻ったら-動きはありません。あなたが明確なビットに到達した場合-あなたはそこに移動し、途中ですべての部分をキャプチャすることができます。
(この手法は、チェスを含む多くの種類のボードゲームで広く使用されています)

1
Oren A