ビットフラグとは何ぞや?
ビットフラグというのは何でしょう?
CPU の構造に詳しい方ならステータスレジスタを想像してもらえば良いかと思います。
AutoCAD のオブジェクト情報やDXFデータに詳しい方なら、グループコード70と言えば理解して頂けるかと。
先ず、数値を2進数の数値と見立てます。
例えば、10進数の4なら、2進数の100、10進数の7なら2進数の111、という具合です。
そして、2進数化した時の各桁を、何らかのフラグと見立てて使用します。
32桁の2進数 ≒ 32bitの2進数であれば、32個のフラグが格納できます。
32bit の2進数という事は、Long型の変数1つで、32個のフラフが管理できるわけです。
Bool型を32個用意するより簡単です。
ビットフラグを用いる時は、各桁が何のフラグかを、定義してやる必要があります。
例えば、AutoCAD のポリラインオブジェクト情報の、グループコード70のビットフラグの各桁の内容は、下記のようになっています。
2進数桁位置 | 内容 |
---|---|
1桁目(10進数の1、2進数の0000-0001) | 閉じフラグ |
2桁目(10進数の2、2進数の0000-0010) | カーブ フィット頂点追加フラグ |
3桁目(10進数の4、2進数の0000-0100) | スプライン フィット頂点追加フラグ |
4桁目(10進数の8、2進数の0000-1000) | 3Dポリラインフラグ |
5桁目(10進数の16、2進数の0001-0000) | 3Dポリゴンメッシュフラグ |
6桁目(10進数の32、2進数の0010-0000) | ポリゴンメッシュN方向閉じフラグ |
7桁目(10進数の64、2進数の0100-0000) | ポリ メッシュフラグ |
8桁目(10進数の128、2進数の1000-0000) | 線種生成フラグ |
例えば、1桁目に1を立てた場合は、始点と終点が結ばれた閉じたポリラインに、0の場合は始点と終点が結ばれない開いたポリラインになります(始終点が同一座標の場合は、このフラグに関わらず見た目が閉じたポリラインになります)。
また、8桁目に1を立てた場合は、各頂点で線種パターンが断絶せずに、頂点を超えて線種パターンが連続的に生成されますが、0の場合は、各頂点で線種パターンがリセットされます。
実際に自身でビットフラグを設計するよりも、他のルーチンやAPIに渡す引数としてビットフラグを生成しなければならないというシチュエーションの方が多いと思います。
2進数から10進数を生成できるか?
ビットフラグを2進数で記述して、Long型の変数に放り込めれば、ビットフラグの記述が楽になりますよね?
Excel を使っているならWorksheetFunction.Bin2Dec
が使えます。
しかし、引数に指定できるのは2進数10桁の10進数数値。
今時10桁のビットフラグなんて使うのかい?
WorksheetFunction.Bin2Dec
は2進数の10桁目に1を立てると、補数として負数を返しますが8桁、16桁、32桁の場合が多いので、10桁で負数を出されても・・・、という状態。
8bitの場合、型はByte型(0~255)になると思うので、8桁目に1が立っても正数なのでWorksheetFunction.Bin2Dec
をそのまま使用できます。
しかし、16bit や 32bit の場合、Integer型やLong型なので、最上位ビットに1が立つと負数になるため、16bit なら下6桁と上10桁に分けて、上10桁をWorksheetFunction.Bin2Dec
した結果を6ビットシフトし、下6桁をWorksheetFunction.Bin2Dec
した結果と or 演算すればよいのでまだマシですが、32bit の場合、下から 4桁 - 9桁 - 9桁 - 10桁 に分けて・・・なんて面倒な事したくありませんよね(途中を9桁にしたのは、途中に負数が入って、それをビットシフトして、さらに or演算したモノが正しいかどうかが不安だったため)。
また、VBA では &B
のリテラルは使用できません。
そのため、ビットフラグを2進数で記載してLong型等の数値型変数に放り込む、簡単な手段はありません。
ビットシフトを行うための計算式
ビットフラグを扱う際に、ビットシフトを行いたいケースが多々あると思います。
多くの言語にはビットシフト演算子が用意されています。
例えば C# では、4ビット左にシフトしたい場合、uint y = x << 4;
と記載します。
しかし、例のごとく VBA にはビットシフトの演算子や関数はありません。
ビットシフトは下記の式で行う事が出来ます。
数値Xを$n$ビット左にシフトしたい場合
$Y=X\times2^n$
数値Xを$n$ビット右にシフトしたい場合
$Y=⌊X/2^n⌋$
右シフトをVBAで記述する場合は X \ (2 ^ n)
で行けます(カッコは不要かも)。
便利ですね、\
演算子。
ビットフラグのフラグを立てる
ビットフラグにビットを立てる際に、下記の様に各ビットの0または1を立てて、ビットシフト演算とor
演算でフラグを立てていくと思います。
'テストルーチン
Public Sub SetFlag()
Dim bytFlg(0 To 31) As Byte
Dim lngBitFlg As Long
'フラグをセットする
bytFlg(0) = 0
bytFlg(1) = 0
bytFlg(2) = 0
bytFlg(3) = 1
bytFlg(4) = 0
bytFlg(5) = 1
bytFlg(6) = 0
bytFlg(7) = 1
bytFlg(8) = 0
bytFlg(9) = 0
bytFlg(10) = 1
bytFlg(11) = 1
bytFlg(12) = 0
bytFlg(13) = 0
bytFlg(14) = 0
bytFlg(15) = 1
bytFlg(16) = 1
bytFlg(17) = 1
bytFlg(18) = 1
bytFlg(19) = 0
bytFlg(20) = 0
bytFlg(21) = 0
bytFlg(22) = 0
bytFlg(23) = 0
bytFlg(24) = 0
bytFlg(25) = 1
bytFlg(26) = 0
bytFlg(27) = 1
bytFlg(28) = 0
bytFlg(29) = 0
bytFlg(30) = 1
bytFlg(31) = 1
lngBitFlg = makeBitFlag32(bytFlg)
End Sub
'32ビットのビットフラグを生成する
Private Function makeBitFlag32(ByRef bytFlg() As Byte) As Long
Dim i As Byte, lngRet As Long
makeBitFlag32 = 0
lngRet = 0
If 31 < UBound(bytFlg) - LBound(bytFlg) Then
'32ビットより大きい
Exit Function
End If
For i = LBound(bytFlg) To UBound(bytFlg)
lngRet = lngRet Or (bytFlg(i) * (2 ^ i))
Next i
makeBitFlag32 = lngRet
End Function
各フラグを所定の桁までビットシフトして、それを加算またはOR演算(論理和)してやれば良いです。
加算、OR演算(論理和)、どちらを使っても問題が無いでしょう。
フラグという特性上あり得ないと思いますが、同一桁で何度も演算する場合、加算は使用しないでください
ただしこのサンプルはエラーになります。
32ビットフラグの最上位フラグを立てる際の注意
前述のサンプルコードがエラーになるのは、32ビット目に1を立てると、Long型の数字範囲を超えてしまうからです。
Long型は32ビット数値ですが、負数を補数表現する為、符号なし32ビット数値で表現できる正数より小さい値になるため、単純に2進数の10000000000000000000000000000000
(10進数で2,147,483,648)を加算(or演算)すると、オーバーフローのエラーになります。
別の数値型を使えばよいじゃないか、というかもしれませんが、API に渡す等で型がLongで固定されている場合は、そういう逃げが出来ません。
補数を得る
最上位ビットが1になるという事は、負数であるという事です。符号なし32ビットの正数$X$から、符号付32ビットの負数$Y$を得るには、下記のように計算します。
$Y=-1\times(4,294,967,295-X+1)$
なお、4,294,967,295
は符号なし32ビット正数の最大数です。
そのため、前述の makeBitFlag32
のコードを下記の様に変更します。
'32ビットのビットフラグを生成する
Private Function makeBitFlag32(ByRef bytFlg() As Byte) As Long
Dim i As Byte, lngRet As Long
Dim decVal As Currency
makeBitFlag32 = 0
lngRet = 0
If 31 < UBound(bytFlg) - LBound(bytFlg) Then
'32ビットより大きい
Exit Function
End If
For i = LBound(bytFlg) To UBound(bytFlg)
If bytFlg(i) = 0 Then
'0の場合、何もしない
ElseIf i - LBound(bytFlg) = 31 Then
'32ビット目の場合
decVal = bytFlg(i) * (2 ^ i)
decVal = -1 * (4294967296@ - decVal)
lngRet = lngRet Or CLng(decVal)
Else
'31ビット目以下の場合
lngRet = lngRet Or (bytFlg(i) * (2 ^ i))
End If
Next i
makeBitFlag32 = lngRet
End Function
Long型以上の数値が扱えるCurrency型等の型の変数に放り込んでから、先の式にて負数を取得するという寸法です。
Double型もLong型より大きな数値が扱えますが、桁数が多い数値は指数表記になり下の桁の情報が欠損する為、注意してください。
数値の桁数をしらべるには
先に上げたサンプルの様に、1ビットずつセットするような場合は良いのですが、場合によっては 「最上位3ビットが0~7までの数値を表すフラグ」 という場合もあり得ます。
そういう場合に、32ビット目が1かどうかを調べるのに、手っ取り早く「桁数」を調べたい場合があります。
桁数を調べる手法は、思いつくだけでも
- 文字列にして文字数を調べる
等が思い当たります。
しかし、文字列にして調べるのは何か負けた気がしますよね?
数学的に求めるべきではないかと、算数もロクに出来ない小生は思う訳です。
n進数の桁数を得るには
もったいぶらずに書いてしまうと、$n$進数の数値$X$の桁数$Y$とすると
$Y=⌊log_nX⌋+1$
です。
注意
$X$ は1以上の正の整数のみです
1以上の小数がある数値でも、整数部分の桁数を算出していると思われますが・・・
nを底とする対数を得るには
多くの言語では、2進数やネイピア数、10を底とした対数関数がそれぞれ用意されていたり、底を指定できる対数関数が用意されています。
例えば Python のmath
モジュールには
-
log
→ 第二引数を底とする対数関数、省略時はネイピア数$e$を底とする対数関数、いわゆる自然対数 -
log10
→ $10$を底とする対数関数、いわゆる常用対数 -
log2
→ $2$を底とする対数関数
が用意されています。
しかし、VBA や AutoLISP 等、自然対数のlog
関数しか用意されていない言語も存在します。
そういった言語で任意の数$X$に対して、任意の底$n$の対数$Y$を得るには
$Y=log_eX/log_en$
という式で求められます。
底はネイピア数でなくても、$Y=log_{10}X/log_{10}n$ のように常用対数でも良いわけですから、底が不明な言語系で底を任意の値にするために、この式を用いてもよさそうです。
とかくVBAは不便
他の言語だと簡単にできる事が、このように手間がかかるのですが、ある意味良い勉強になります。
仕事でやる分には、勘弁ですが・・・
注意
もしかして数学用語とか、数学的な記述内容が正しくない可能性があります。
何分算数も分かってるのか分かってないのかという人間が書いておりますので、不備がある場合はご指摘、ご鞭撻のほどよろしくお願い致します。