複数のフラグ(真偽値)を1つの整数値として扱う上でのメモ
プログラム内のある設定などに関する複数の「オン/オフ」形式の情報をまとめて扱えると便利なことがある。そうしたとき、真偽値型データの配列を用意したりするのではなく、2進数の性質を用いて1つの整数のみを用いてその中で複数の「オン/オフ」情報を出し入れすることができる。*1
本記事では色々な言語で共通した演算子を扱うため、その書式が特定の言語に依存するといったことはないのだが、便宜上Pythonを用いている。
$ python >>> (ここに式を入力してEnterするかCtrl+Dで抜ける)
以下、入力操作を行う中でEnterを押すところでは「[Enter]」と表記している。
(2013/6/16)記事中の各操作についての説明の追加や一部記述の調整などを行った。
2進数の桁数と同じ数のフラグを管理する
2進数では1つの桁で0(なし)か1(あり)の2つの状態だけを表現できる。例えば4桁の2進数では
- 1(20)の位
- 2(21)の位
- 4(22)の位
- 8(23)の位
のそれぞれに独立した真偽値/フラグがあるとみることができ、例として2の位と8の位の桁が「1」の状態は「1010」と表現され、整数としての(10進表記の)値は1x23+0x22+1x21+0x20=1x8+0x4+1x2+0x1=10となる。
桁とフラグを関連付ける
2進数のそれぞれの桁をフラグとして関連付けるために、位ごとの値を事前に準備しておく。
(1の位/1桁目) >>> FLAG_A = 1 [Enter] (2の位/2桁目) >>> FLAG_B = 2 [Enter] (4の位/3桁目) >>> FLAG_C = 4 [Enter] (8の位/4桁目) >>> FLAG_D = 8 [Enter]
上では桁ごとの値をそのまま用いているが、ビット(桁)単位で左にシフト演算*2する「<<」演算子により
(2の0乗の位/1桁目) >>> FLAG_A = 1 << 0 [Enter] (2の1乗の位/2桁目) >>> FLAG_B = 1 << 1 [Enter] (2の2乗の位/3桁目) >>> FLAG_C = 1 << 2 [Enter] (2の3乗の位/4桁目) >>> FLAG_D = 1 << 3 [Enter]
と書くこともできる。何番目の桁かの値から1を引いたものを「1 <<」の右に付ける形となっており、これより大きい値でも
(2の4乗の位/5桁目) >>> 1 << 4 [Enter] 16 (2の5乗の位/6桁目) >>> 1 << 5 [Enter] 32 (2の6乗の位/7桁目) >>> 1 << 6 [Enter] 64 (2の7乗の位/8桁目) >>> 1 << 7 [Enter] 128
このように同様となっている(「FLAG_E = 1 << 4」のようにして書ける)。
特定のフラグをオンにする
まずは状態を保持する変数を用意しておく。名前は「f」で初期値は「0」(全てオフ)とする。
(全てオフの初期値の変数を用意) >>> f = 0 [Enter] (生の値を確認) >>> f [Enter] 0
ここで「FLAG_B」のフラグだけをオンにするには「|=」演算子を用いて
(FLAG_Bのみをオンにする) >>> f |= FLAG_B [Enter]
とする。これは
(FLAG_Bのみをオンにする) >>> f = f | FLAG_B [Enter]
と同じ。
フラグをオンにする操作は何度行っても値は変わらない。
(生の値を確認) >>> f [Enter] 2 (FLAG_Bのみをオンにする操作を再度実行) >>> f = f | FLAG_B [Enter] (生の値を再度確認) >>> f [Enter] 2
各フラグの状態を取得する
各フラグの状態を取得するには変数値とフラグ値とを「&」で演算した値を用いる。
(FLAG_Aの状態を取得) >>> f & FLAG_A [Enter] 0 (FLAG_Bの状態を取得) >>> f & FLAG_B [Enter] 2 (FLAG_Cの状態を取得) >>> f & FLAG_C [Enter] 0 (FLAG_Dの状態を取得) >>> f & FLAG_D [Enter] 0
先ほど有効にしたフラグ(FLAG_B)以外は「0」となっている。ここでFLAG_Dをオンにすると
(FLAG_Dのみをオンにする) >>> f |= FLAG_D [Enter]
FLAG_Dの状態を示す値は
(FLAG_Dの状態を取得) >>> f & FLAG_D [Enter] 8
0ではなくなる。
条件として用いる際には値が0であるかどうかを調べる。
(真偽値としての取得/FLAG_Dについて確認) >>> f & FLAG_D != 0 [Enter] True (if文を用いた例/FLAG_Dについて確認) >>> if f & FLAG_D != 0: [Enter] ... print "D:ON" [Enter] ... else: [Enter] ... print "D:OFF" [Enter] ... [Enter] D:ON
フラグをオフにする
上の「状態を示す値」を現在の値から引くと
- 状態がオンの場合はオフになる
- 状態がオフだった場合は「0」を引くので値が変化しない
となり、状態に関わらず該当フラグのみをオフにする効果があり、他の影響はない。
FLAG_Bだけをオフにする操作は
(FLAG_Bのみをオフにする) >>> f -= f & FLAG_B [Enter]
となり、変更後は
(FLAG_Aの状態を取得) >>> f & FLAG_A [Enter] 0 (FLAG_Bの状態を取得) >>> f & FLAG_B [Enter] 0 (FLAG_Cの状態を取得) >>> f & FLAG_C [Enter] 0 (FLAG_Dの状態を取得) >>> f & FLAG_D [Enter] 8
と、FLAG_Dだけがオンの状態になる。
全てのフラグがONかをチェック
全てのフラグがONかどうかをチェックするにはそれぞれの位の値を合計した値との比較を行う。4桁目までを用いて4つのフラグを管理する場合は20+21+22+23=1+2+4+8=15という値かどうかをチェックする。
オフのフラグが1つでもあると
(真偽値としての取得) >>> f == FLAG_A + FLAG_B + FLAG_C + FLAG_D [Enter] False (if文を用いた例) >>> if f == FLAG_A + FLAG_B + FLAG_C + FLAG_D: [Enter] ... print "OK" [Enter] ... else: [Enter] ... print "NG" [Enter] ... [Enter] NG
条件は偽となり、残った全てをオンにすると
(FLAG_Aのみをオンにする) >>> f |= FLAG_A [Enter] (FLAG_Bのみをオンにする) >>> f |= FLAG_B [Enter] (FLAG_Cのみをオンにする) >>> f |= FLAG_C [Enter]
下のように条件が真になる。
>>> f == FLAG_A + FLAG_B + FLAG_C + FLAG_D [Enter] True >>> if f == FLAG_A + FLAG_B + FLAG_C + FLAG_D: [Enter] ... print "OK" [Enter] ... else: [Enter] ... print "NG" [Enter] ... [Enter] OK
(2009/9/10)id:nelnal_memo氏からコメントを頂き
~ [フラグ変数] & (FLAG_A + FLAG_B + ... + [最後のフラグのビット値]) == 0
のように
- フラグ変数の全てのビットを反転(「~」を付ける)
- 反転した値を各フラグの値の合計とで&演算して、フラグと関係のないビットの値を外す
とすることで、0と1の意味が逆転(1がOFF、0がONになる)して後述の「全てのフラグがOFFかをチェック」と要領が同様になることが分かった。この場合は処理が少し増える。
(1つでもオフがある場合) >>> ~ f & (FLAG_A + FLAG_B + FLAG_C + FLAG_D) == 0 False (全てオンの場合) >>> ~ f & (FLAG_A + FLAG_B + FLAG_C + FLAG_D) == 0 True
全てのフラグがOFFかをチェック
「全てオフ」のチェックは変数の生の値が0かどうかをみればよく、楽にできる(チェック作業は省略)。