仕事や研究に使えるビット演算

少し前にビット演算について AND, OR, XOR, NOT を使う例を記事にしましたが、自分で読み返していても不親切な気がしています。

なぜなら、それはテクニックを紹介しただけで、実際にどういう使い方をするのかということがおざなりになっているからです。

というわけで、実際に仕事や研究でビット演算を使う場合はこういう使い方をするのだという実例を紹介します。

まずは Fig. 1 に示すようなサンプルアプリを作成しました。ウインドウ下部のステータスバーのところで LED が点灯/消灯しているイメージです。

Windows Forms でボタンをたくさん貼り付けているので、本記事のコードのコピーペーストではうまくビルドできないと思います。サンプルソースを作成したので、このページの後半のところからダウンロードして動作をお確かめください。

Fig. 1 デジタル出力を模擬したサンプルアプリの外観

本記事ではすべてのビットに LED を接続しているイメージですが、実際の仕事では、LED以外に、エア電磁弁、リレー、パワーFET、そういうものをすべて混ぜこんで使うと思います。

その場合は、下記に示すような感じで、ビットに接続されているデバイスがすぐにわかるようなナイスなネーミングで定義してください。その値は 1, 2, 4, 8, 16, 32, 64, 128 のようにビット独立する値で定義しなくてはなりません。

const uint BIT_VALVE_B        = 0x80; // (128) エア電磁弁B. (this is MSB).
const uint BIT_VALVE_A        = 0x40; // (64) エア電磁弁A.
const uint BIT_RESERVED_05    = 0x20; // (32) 予約.
const uint BIT_RELAY_AC200V   = 0x10; // (16) 交流200Vリレー.

const uint BIT_FET_SW         = 0x08; // (8) スイッチングFET.
const uint BIT_RESERVED_02    = 0x04; // (4) 予約.
const uint BIT_LED_GREEN      = 0x02; // (2) 緑色LED.
const uint BIT_LED_BLUE       = 0x01; // (1) 青色LED. (this is LSB).

さて、いまから本記事のために作ったサンプルアプリに沿って記事を進めます。

クラス内 public で符号なし32bitの変数 uint Value というものを宣言します。それの下位8bitに LED が接続されているものとします。( Fig. 2 )

Fig. 2 32bit変数の下位8bitにLEDが接続されているイメージ

しかし、実際の仕事や研究の現場では Value は、符号なし8bitの変数 byte で扱うことがほとんどですので覚えておいてください。( Fig. 3 )

Fig.3 8bit変数そのままにLEDが接続されているイメージ

なぜ、ここで現実の Fig. 3 ではなくて Fig. 2 で説明しようとしているのかは、byte 型をつかってビット演算を説明しようとすると、キャストが混じった妙な記法をしなければならず、読者の皆さんの理解の妨げになる可能性が高いからです。

これについてはマイクロソフト社の C# 技術情報に明記されています。

上記のマイクロソフト社のウェブページの核心だけをまとめると、こんな感じです。

以下の演算子では、整数型または char 型のオペランドに対してビットごとの演算またはシフト演算が実行されます。

・単項 (ビットごとの補数) 演算子
・2項 (左シフト) および (右シフト) シフト演算子
・2項 (論理 AND)、 (論理 OR)、および (論理排他的 OR) 演算子

これらの演算子は、int、uint、long、ulong 型に対して定義されています。
両方のオペランドが他の整数型 (sbyte、byte、short、ushort、char) の場合、
それらの値は int 型に変換され、演算の結果もその型になります。

では下記のコードを使って解説を始めます。

19~27行目までが、それぞれのビットに接続されているデバイスを定義しているところです。

42~45行目において、Value の初期状態を作っています。

以降はボタンを使って、それぞれのビットの ON/OFF を制御しています。

98行目の Value |= mask; というところが、特定ビットだけを強制的にたてる記述です。
125行目の Value &= ~mask; というところが、特定ビットだけを強制的におとす記述です。

Value のそれぞれのビットの状態を調べているのは、171~181行目のところです。

if (( value & mask ) == mask )

というお決まりのコードを使って、検査しています。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace aaa
{
	public partial class Form1 : Form
	{

		// ほんとうは実際の仕事でも使えそうな const byte にしたサンプルにしたかったが、
		// ビット演算のときにキャストが必要なのでしぶしぶ uint で定義した.

		const uint BIT_LED07 = 0x80; // MSB.
		const uint BIT_LED06 = 0x40;
		const uint BIT_LED05 = 0x20;
		const uint BIT_LED04 = 0x10;

		const uint BIT_LED03 = 0x08;
		const uint BIT_LED02 = 0x04;
		const uint BIT_LED01 = 0x02;
		const uint BIT_LED00 = 0x01; // LSB.

		///////////////////////////////////////////
		///////////////////////////////////////////
		///////////////////////////////////////////

		uint Value;

		public Form1()
		{

			InitializeComponent();

			Value = 0x00;

			Value |= BIT_LED06;
			Value |= BIT_LED04;
			Value |= BIT_LED02;
			Value |= BIT_LED00;

		}

		private void Form1_Load( object sender, EventArgs e )
		{

			this.buttonOn07.Click += new System.EventHandler(this.buttonOnNn_Click);
			this.buttonOn06.Click += new System.EventHandler(this.buttonOnNn_Click);
			this.buttonOn05.Click += new System.EventHandler(this.buttonOnNn_Click);
			this.buttonOn04.Click += new System.EventHandler(this.buttonOnNn_Click);
			this.buttonOn03.Click += new System.EventHandler(this.buttonOnNn_Click);
			this.buttonOn02.Click += new System.EventHandler(this.buttonOnNn_Click);
			this.buttonOn01.Click += new System.EventHandler(this.buttonOnNn_Click);
			this.buttonOn00.Click += new System.EventHandler(this.buttonOnNn_Click);

			this.buttonOff07.Click += new System.EventHandler(this.buttonOffNn_Click);
			this.buttonOff06.Click += new System.EventHandler(this.buttonOffNn_Click);
			this.buttonOff05.Click += new System.EventHandler(this.buttonOffNn_Click);
			this.buttonOff04.Click += new System.EventHandler(this.buttonOffNn_Click);
			this.buttonOff03.Click += new System.EventHandler(this.buttonOffNn_Click);
			this.buttonOff02.Click += new System.EventHandler(this.buttonOffNn_Click);
			this.buttonOff01.Click += new System.EventHandler(this.buttonOffNn_Click);
			this.buttonOff00.Click += new System.EventHandler(this.buttonOffNn_Click);

			statusStrip1.ForeColor = Color.Lime;
			statusStrip1.BackColor = Color.Black;

			this.Text = "GazoYaro";

		}

		private void menuApplicationQuit_Click( object sender, EventArgs e )
		{
			this.Close();
		}

		private void buttonOnNn_Click( object sender, EventArgs e )
		{

			uint mask;

			if      ( sender.Equals( buttonOn07 )) { mask = BIT_LED07; }
			else if ( sender.Equals( buttonOn06 )) { mask = BIT_LED06; }
			else if ( sender.Equals( buttonOn05 )) { mask = BIT_LED05; }
			else if ( sender.Equals( buttonOn04 )) { mask = BIT_LED04; }
			else if ( sender.Equals( buttonOn03 )) { mask = BIT_LED03; }
			else if ( sender.Equals( buttonOn02 )) { mask = BIT_LED02; }
			else if ( sender.Equals( buttonOn01 )) { mask = BIT_LED01; }
			else if ( sender.Equals( buttonOn00 )) { mask = BIT_LED00; }
			else                                   { return; }

			// 特定のビットをたてる.
			Value |= mask;

			// 上記の記法がわかりにくかったらこういうのでどうぞ.
			// Value = Value | mask;

			// 再描画をうながす( Form1_Paint がコールされる ).
			this.Invalidate();

		}


		private void buttonOffNn_Click( object sender, EventArgs e )
		{

			uint mask;

			if      ( sender.Equals( buttonOff07 )) { mask = BIT_LED07; }
			else if ( sender.Equals( buttonOff06 )) { mask = BIT_LED06; }
			else if ( sender.Equals( buttonOff05 )) { mask = BIT_LED05; }
			else if ( sender.Equals( buttonOff04 )) { mask = BIT_LED04; }
			else if ( sender.Equals( buttonOff03 )) { mask = BIT_LED03; }
			else if ( sender.Equals( buttonOff02 )) { mask = BIT_LED02; }
			else if ( sender.Equals( buttonOff01 )) { mask = BIT_LED01; }
			else if ( sender.Equals( buttonOff00 )) { mask = BIT_LED00; }
			else                                    { return; }

			// 特定のビットをおとす.
			Value &= ~mask;

			// 上記の記法がわかりにくかったらこういうのでどうぞ.
			// uint mask_off = ~mask;
			// Value = Value & mask_off;

			// 再描画をうながす( Form1_Paint がコールされる ).
			this.Invalidate();

		}

		private void buttonAllOn_Click( object sender, EventArgs e )
		{

			Value = 0x00;

			Value |= BIT_LED07;
			Value |= BIT_LED06;
			Value |= BIT_LED05;
			Value |= BIT_LED04;

			Value |= BIT_LED03;
			Value |= BIT_LED02;
			Value |= BIT_LED01;
			Value |= BIT_LED00;

			this.Invalidate();

		}

		private void buttonAllOff_Click( object sender, EventArgs e )
		{

			Value = 0x00;

			this.Invalidate();

		}

		private bool get_value_string( ref String want, uint value )
		{

			uint mask;

			want = String.Format( "0x{0:x2}: ", value );

			mask = BIT_LED07; if (( value & mask ) == mask ) { want += "●"; } else { want += "〇"; }
			mask = BIT_LED06; if (( value & mask ) == mask ) { want += "●"; } else { want += "〇"; }
			mask = BIT_LED05; if (( value & mask ) == mask ) { want += "●"; } else { want += "〇"; }
			mask = BIT_LED04; if (( value & mask ) == mask ) { want += "●"; } else { want += "〇"; }

			want += " - ";

			mask = BIT_LED03; if (( value & mask ) == mask ) { want += "●"; } else { want += "〇"; }
			mask = BIT_LED02; if (( value & mask ) == mask ) { want += "●"; } else { want += "〇"; }
			mask = BIT_LED01; if (( value & mask ) == mask ) { want += "●"; } else { want += "〇"; }
			mask = BIT_LED00; if (( value & mask ) == mask ) { want += "●"; } else { want += "〇"; }

			return true;

		}

		private void Form1_Paint( object sender, PaintEventArgs e )
		{
			String str = "";
			get_value_string( ref str, Value );
			toolStripStatusLabel1.Text = str;
		}

	}
}

実際の仕事や研究では Value の変数の型は byte だということを述べましたが、その場合は下記のような記述になります。キャストが入って記述が煩雑になりますが、しかたありません。

// 特定のビットをたてる.
Value = (byte)( Value | mask );

// 特定のビットをおとす.
Value = (byte)( Value & ~mask );

また、下記にデジタル入出力ボードの有名メーカーの出力メソッドを示します。実際は byte 型でデータを扱うということがわかると思います。

// コンテック社 SDK の出力メソッドの定義.
[DllImport("cdio.dll")]
static extern int DioOutByte(
					short Id,
					short PortNo,
					byte Data // ここが 8bits 変数です.
					);

// インターフェース社 SDK の出力メソッドの定義.
[DllImport("fbidio.dll")]
public static extern uint DioOutputByte(
							IntPtr hDeviceHandle,
							int nNo,
							byte bValue // ここが 8bits 変数です.
							);

// アドバンテック社の SDK の出力メソッドの定義.
[DllImport("BioDaq.dll", CharSet = CharSet.Auto), SuppressUnmanagedCodeSecurity]
public unsafe static extern ErrorCode AdxDoWritePorts(
										IntPtr dioHandle,
										int portStart,
										int portCount,
										byte* buffer // ここが 8bits 変数へのポインタです.
										);

冒頭の Fig. 1 に示したサンプルアプリケーションのソースコードです。ソースコードだけですので、お手持ちの VisualStudio でビルドしてから実行して動作を確認してください。