C#のビット演算の方法を解説|ビットフラグのメリットや例も紹介
- システム
エンジニア - C#の<<について、詳しく教えていただけませんか。
- プロジェクト
マネージャー - わかりました。それでは、C#の<<について解説いたしましょう。
C#のビット演算とは
C#のビット演算とは、何なのでしょうか?
私たちが計算するときは、ほぼ全ての人が10進数で考えます。0~9で数を表し、10になれば次の桁を使います。
ところが、コンピューターは2進数しか理解できません。0と1だけで数を表すので、1010011…のように表現します。この2進数を使って計算することをビット演算と言います。
C#等の高級言語では、2進数だけでは人間が理解しにくいので10進数や16進数も扱えるように、言語側で変換してくれています。
そのため、ビット演算について理解していなくてもC#のプログラムを作ることはできてしまいます。しかし、コンピューターは2進数で動作しているので2進数で計算しやすい処理にしてあげることで処理が高速になったり、メモリを節約したりすることができるようになります。
ビット演算の種類
C#のビット演算には、大きく分けて2つの種類があります。
・シフト演算
・論理演算
それぞれの演算について、詳しく解説していきます。
また、C#におけるビット演算の実用性についても説明します。
シフト演算
シフト演算とはビット列全体を右、または左へ移動させる演算のことです。
C#のシフト演算には
・左シフト演算(演算子:<<�) ・右シフト演算(演算子:>>)
があります。
シフト演算で注意しなければならないのは『範囲外に出るビットの扱い』と『範囲内に繰り入れられるビットの値』です。
C#では、ビット演算をしようとしている変数の型によって、『範囲外に出るビットの扱い』と『範囲内に繰り入れられるビットの値』が変わりますので、注意してください。
論理演算
論理演算とはビット列同士の足し算や掛け算のことです。
C#の論理演算には
・論理AND演算(論理積とも呼ばれる)(演算子:&)
・論理OR演算(論理和とも呼ばれる)(演算子:|)
・論理XOR演算(排他的論理和とも呼ばれる)(演算子:^)
・補数演算(NOT演算・反転とも呼ばれる)(演算子:~)
があります。
C#におけるビット演算の実用性
<<�をはじめとするビット演算子について紹介しましたが、実際にどのように役に立つのでしょうか。先に述べたように演算の高速化やメモリの節約に役立ちます。 しかし今のコンピュータではビット演算による高速化や節約は効果が少なく、それほど意味がありません。昔のコンピュータはメモリが少なく、少しでも高速化やメモリを節約する必要があったので、このようなビット演算が使われていたのです。
C#のビット演算に関わる演算子の使い方6つ
ここからはC#の6つのビット演算について、以下の順番で紹介していきます。
・左シフト演算(演算子:<<�) ・右シフト演算(演算子:>>)
・論理AND演算(論理積とも呼ばれる)(演算子:&)
・論理OR演算(論理和とも呼ばれる)(演算子:|)
・論理XOR演算(排他的論理和とも呼ばれる)(演算子:^)
・補数演算(NOT演算・反転とも呼ばれる)(演算子:~)
1:左シフト演算子について
左シフト演算子はビット列全体を左へ移動させる演算で、C#での演算子は『<<�』です。
まずは、以下のC#のサンプルプログラムをご覧ください。
1
2
3
4
|
uint i = 0b0001;
i = i << 1;
System.Console.WriteLine("10進数で表現:"+i);
System.Console.WriteLine("2進数で表現:"+Convert.ToString(i, 2));
|
実行結果は次のようになります。
1
2
|
10進数で表現:2
2進数で表現:10
|
サンプルプログラムについて、上から順に解説していきます。
まず『uint』とは『unsigned int』のことで、正の数の整数型です。2進数ではこの型を使うとわかりやすくなります。
次に、C#では先頭に『0b』を付けることで2進数を表現します。『0b0001』は2進数の「0001」、つまり1ということになります。
次の『i = i << 1;』で、iを左に1ビットシフトします。iは「0001」でしたので、1ビット左にシフトすると「0010」になります。『i = i << 1;』は『i <<= 1』と書き換えることもできます。 次の『System.Console.WriteLine("10進数で表現:"+i);』で結果を表示します。iをそのまま出力すると10進数で表示されます。 iを2進数で表示したい場合は『System.Console.WriteLine("2進数で表現:"+Convert.ToString(i, 2));』としてください。
2:右シフト演算子について
右シフト演算子はビット列全体を右へ移動させる演算で、C#での演算子は『>>』です。
以下のC#サンプルをご覧ください。
1
2
3
4
|
uint i = 0b0100;
i = i >> 1;
System.Console.WriteLine("10進数で表現:"+i);
System.Console.WriteLine("2進数で表現:"+Convert.ToString(i, 2));
|
1
2
|
10進数で表現:2
2進数で表現:10
|
今度は、iが「0100」なので、1ビット右にシフトすると「0010」になります。左シフトと同様に『i = i >> 1;』は『i >>= 1;』と書き換えることもできます。
3:論理AND演算子について
論理AND演算子はビットごとの論理積を求める演算で、C#での演算子は『&』です。
以下のC#サンプルをご覧ください。
1
2
3
|
uint i1 = 0b0101;
uint i2 = 0b1100;
System.Console.WriteLine(Convert.ToString(i1 & i2, 2));
|
実行結果は、
1
|
100
|
となります。つまり0100ですね。
&は2進数の各桁で掛け算を行います。
このサンプルの場合、上の桁から
1
2
3
4
|
0×1=0
1×1=1
0×0=0
1×0=0
|
により、0100となるのです。
4:論理OR演算子について
論理OR演算子はビットごとの論理和を求める演算で、C#での演算子は『|』です。
以下のC#サンプルをご覧ください。先ほどのC#サンプルの&を、|に変えました。
1
2
3
|
uint i1 = 0b0101;
uint i2 = 0b1100;
System.Console.WriteLine(Convert.ToString(i1 | i2, 2));
|
実行結果は、
1
|
1101
|
になります。
|は各桁の加算を行います。ただし1と1を加算した場合は1のままです。
このサンプルの場合、上の桁から
1
2
3
4
|
0+1=1
1+1=1
0+0=0
1+0=1
|
と演算され、1101となりました。
5:論理XOR演算子について
論理XOR演算子は各桁を比較し違う値であれば1、同じであれば0にする演算で、C#での演算子は『^』です。
以下のC#のサンプルプログラムをご覧ください。
1
2
3
|
uint i1 = 0b0101;
uint i2 = 0b1100;
System.Console.WriteLine(Convert.ToString(i1 ^ i2, 2));
|
実行結果は
1
|
1001
|
になります。
^は各桁を比較し違う値であれば1、同じであれば0にする演算なので、このサンプルの場合、上の桁から
1
2
3
4
|
0と1で「違う値」=1
1と1で「同じ値」=0
0と0で「同じ値」=0
1と0で「違う値」=1
|
と演算され、1001となりました。
6:補数演算子について
補数演算子はビットを反転させる演算で、C#での演算子は『~』です。
以下のC#のサンプルプログラムをご覧ください。
1
2
|
uint i = 0b0101;
System.Console.WriteLine(Convert.ToString(~i, 2));
|
実行結果は
1
|
11111111111111111111111111111010
|
になります。
~は1なら0、0なら1のように各桁のビットを反転させる機能があります。5桁目以上が反転して1になっていますが、4桁目までは1010となって0101が反転していることがわかります。
演算子の優先順位
ここまで紹介した演算子の優先順位はどうなっているのでしょうか。以下は優先度の高い順になっています。
補数演算子 ~
シフト演算子 <<�と>>
論理AND演算子 &
論理排他的OR演算子 ^
論理OR演算子 |
例えば、以下の計算をすると
1
2
3
|
uint i1 = 0b0100;
uint i2 = 0b0010;
System.Console.WriteLine(Convert.ToString(i1 & i2 << 1, 2));
|
実行結果は
1
|
100
|
になります。
i2が先に左シフトされて0100になるので、その後に&でi1と掛け算をして0100となるわけです。
ビット演算によるフラグ管理とは
ここまで、C#でのビット演算とその優先順位について解説してきました。では、実際にC#でビット演算を使うのはどのような場合でしょうか。その多くが『ビットフラグ』と呼ばれるフラグの操作や管理をする場合です。
『ビットフラグ』とはTrue or Falseのみを扱いたい情報が複数個あるときに、いくつかのフラグの情報をひとまとめにして、変数に持たせたもののことです。
ビットフラグのメリットは?
例えば、家の電灯のON/OFF情報を管理するシステムをイメージしてください。ビットフラグを使わずbool型でフラグ管理をする場合、以下のように沢山の変数が必要になります。
1
2
3
|
bool キッチンの電灯;
bool リビングの電灯;
bool 寝室の電灯;
|
これをビットフラグで表すと
1
|
uint 家の電灯;
|
だけで良いのです。
ビットフラグのメリットは、1つの変数で、桁の数だけのフラグを使えるということです。
それだけではありません。例えば、上記のシステムに『家中の電灯を一度に消す機能』を追加した場合、ビットフラグを使わない場合は、フラグを1つずつOFFにしなければなりません。しかしビットフラグであれば、一度に全てOFFにすることができます。
さらに、家に子供部屋を追加した場合、ビットフラグを使わない場合は、新規に
1
|
bool 子供部屋の電灯;
|
を追加するので、プログラム全体に修正が入ることになります。『家中の電灯を一度に消す機能』の修正が漏れてしまうことも考えられます。
ビットフラグにしておけば『家中の電灯を一度に消す機能』の修正は必要ありません。このように、機能追加があった際に、修正箇所をグッと減らすこともできるのです。
ビット演算によるフラグ管理例
ビット演算の方法がわかったところで、フラグを一括で管理する方法について解説します。
1つの変数で複数のフラグを管理する方法は、ビット単位で考えます。例えば4ビットの数の1桁目が1の時はAの状態がTrueであるというようになります。
例:
フラグが101の時
1桁目が1なので、状態Aはtrue
2桁目は0なので、状態Bはfalse
3桁目は1なので、状態Cはtrue
上記のような考え方です。これをコード上でチェックするには、論理積を使って確認します。実際の例を見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
using System;
class Program
{
//説明の便宜上定数を取ります
const int FLAG_A = 1; // 001(=1)
const int FLAG_B = 2; // 010(=2)
const int FLAG_C = 4; // 100(=4)
public static void Main()
{
byte status = 5; // 101(=5)
Console.WriteLine("Aの状態は" + ((status & FLAG_A) != 0) + "です。");
Console.WriteLine("Bの状態は" + ((status & FLAG_B) != 0) + "です。");
Console.WriteLine("Cの状態は" + ((status & FLAG_C) != 0) + "です。");
}
}
|
1
2
3
|
Aの状態はTrueです。
Bの状態はFalseです。
Cの状態はTrueです。
|
また、調べたい桁が0だと、すべての桁が0になるので『(status & FLAG_A) != 0』という書き方でフラグの真偽が確認できます。
なお、確認したい桁だけ1として他の桁を0にした値をマスクビットと言い、このマスクビットと比較して値を取り出すことを「マスクする」と言うこともあります。確認したい桁だけ調べて他の値は必ず0にして目を向けない、必要な桁以外は覆い隠す、という意味から「マスク」と言われています。
ここで、もう1つフラグ管理の例を挙げてみます。上記の例のようにフラグ管理をするとき「AとBのどちらかがTrueの時」という判定をしたい場合も出てきます。こういう場合は、論理和が使えます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
using System;
class Program
{
const int FLAG_A = 1; // 001(=1)
const int FLAG_B = 2; // 010(=2)
const int FLAG_AB = FLAG_A | FLAG_B; // 001 OR 010 -> 011: 論理和を使ったフラグができる
public static void Main()
{
byte status = 5; // 101(=5)
Console.WriteLine("A, BどちらかはTrue? : " + ((status & FLAG_AB) != 0));
}
}
|
1
|
A, BどちらかはTrue? : True
|
- システム
エンジニア - 複数・シフト・論理AND・論理排他的OR・論理OR演算子の優先順位とビット演算の実用性についてわかりました。
- プロジェクト
マネージャー - 実際にコードを書いて覚えていきましょう。
C#におけるビット演算のやり方を覚えよう
C#のビット演算の方法や、ビットフラグのメリットについてご紹介しました。
C#での開発を行うにあたって、ビット演算は必須スキルではないものの知っていると非常に便利であることがご理解頂けましたでしょうか。
「2進数」や「ビット」と聞いただけで『よくわからない!』と毛嫌いしてしまうのは非常にもったいないのでぜひ当サイトを熟読して、C#のビット演算のやり方を身に付けてください。
FEnet.NETナビ・.NETコラムは株式会社オープンアップシステムが運営しています。
株式会社オープンアップシステムはこんな会社です
秋葉原オフィスには株式会社オープンアップシステムをはじめグループのIT企業が集結!
数多くのエンジニアが集まります。
-
スマホアプリから業務系システムまで
スマホアプリから業務系システムまで開発案件多数。システムエンジニア・プログラマーとしての多彩なキャリアパスがあります。
-
充実した研修制度
毎年、IT技術のトレンドや社員の要望に合わせて、カリキュラムを刷新し展開しています。社内講師の丁寧なサポートを受けながら、自分のペースで学ぶことができます。
-
資格取得を応援
スキルアップしたい社員を応援するために資格取得一時金制度を設けています。受験料(実費)と合わせて資格レベルに合わせた最大10万円の一時金も支給しています。
-
東証プライム上場企業グループ
オープンアップシステムは東証プライム上場「株式会社オープンアップグループ」のグループ企業です。
安定した経営基盤とグループ間のスムーズな連携でコロナ禍でも安定した雇用を実現させています。
株式会社オープンアップシステムに興味を持った方へ
株式会社オープンアップシステムでは、開発系エンジニア・プログラマを募集しています。
年収をアップしたい!スキルアップしたい!大手の上流案件にチャレンジしたい!
まずは話だけでも聞いてみたい場合もOK。お気軽にご登録ください。
C#新着案件New Job
-
システム開発/東京都新宿区/【WEB面談可/C#経験者/20代前半の方活躍中/経験1年以上の方活躍中】/在宅勤務
月給29万~34万円東京都新宿区(新宿駅) -
システム開発/東京都新宿区/【WEB面談可/C#経験者/20代後半~40代の方活躍中/経験年数不問】/在宅勤務
月給41万~50万円東京都新宿区(新宿駅) -
デバック、テスト項目の作成/神奈川県横浜市/【WEB面談可/C#経験者/20代前半の方活躍中/経験1年以上の方活躍中】/在宅勤務
月給29万~34万円神奈川県横浜市(桜木町駅) -
デバック、テスト項目の作成/神奈川県横浜市/【WEB面談可/C#経験者/20代後半~40代の方活躍中/経験年数不問】/在宅勤務
月給41万~50万円神奈川県横浜市(桜木町駅) -
基幹システム開発導入/東京都新宿区/【WEB面談可/C#経験者/20代前半の方活躍中/経験1年以上の方活躍中】/在宅勤務
月給29万~34万円東京都新宿区(西新宿駅) -
基幹システム開発導入/東京都新宿区/【WEB面談可/C#経験者/20代後半~40代の方活躍中/経験年数不問】/在宅勤務
月給41万~50万円東京都新宿区(西新宿駅)