hinekure.net が http://hspdev-wiki.net/ から自動クローリングした結果を表示しています。画像やリソースなどのリンクが切れています。予めご了承ください。
小ワザ/ビット操作 - HSP開発wiki
トップ    編集凍結 差分バックアップ添付複製名前変更リロード   新規一覧単語検索最終更新   最終更新のRSS

小ワザ

 HSP2.61 

ビット操作

ビット操作を覚えると効率良くスクリプトが書けるようになったりします。

事前知識

$で始まる数値->16進数
    $00は8bitで0
    $0001は16bitで1
    $000000FFは32bitで255
いずれも内部表現は32bitに変わりない。
%で始まる数値->2進数
    %1000は4bitで8
    %10000000は8bitで128
    %%11111111111111111111111111111111は32bitで-1
いずれも内部表現は32bitに変わりない。

10進数で表現するよりも分かりやすい場合や、数値が何バイト(何bit)なのかを強調したい時などに使ったりします。

論理積(and)

  1100 0011
&)0000 1111
-----------
  0000 0011
  • 活用例
    ほとんどの人が最初の頃から実はやっていることです。
      1
      2
      3
      4
    
     
     
     
     
    
        if key & 1 : x-
        if key & 2 : y-
        if key & 4 : x+
        if key & 8 : y+
    このkey & ○○とは何でしょう?見せます
      0000 1111    0000 1111    0000 1111    0000 1111
    &)0000 0001  &)0000 0010  &)0000 0100  &)0000 1000
    -----------  -----------  -----------  -----------
      0000 0001    0000 0010    0000 0100    0000 1000
    特定のビットを抜き出す*1時に使ったりします。
    ほら、銀行強盗が目と口に穴の開いたマスクをすると、そこだけ見えるでしょ?
    ビットの立っている所が穴で数値にマスクを被せると、穴の開いたところだけが浮き出てくるって考えると 分かりやすいかな?

論理和(or)

  1100 0011
|)0000 1111
-----------
  1100 1111
  • 活用例
    ほとんどの人が最初の頃から実はやっていることです。
      1
    
     
    
        stick key,1+2+4+8
    この1,2,4,8とは何でしょう?見せます。
      0000 0001
    |)0000 0010
    |)0000 0100
    |)0000 1000
    -----------
      0000 1111
    特定のビットをたてる*2時に使ったりします。

排他的論理和(xor)

  1100 0011
^)0000 1111
-----------
  1100 1100
^)0000 1111
-----------
  1100 0011

ビットシフト(<<.>>)

value>>n    ;数値のビットを右にnビットずらす
value<<n    ;数値のビットを左にnビットずらす
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 
 
 
 
 
 
 
 
 
 
 
 
 
    bit=1
 
    repeat 8
        mes bit<<cnt
    loop
 
    bit=%10000000
 
    repeat 8
        mes bit>>cnt
    loop
 
    stop

[hsp3]

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
;for HSP3
#module
;値bitsをw桁の2進表記の文字列に変換したものを返すモジュール。
#defcfunc strbit int bits,int w
    if (w<1)|(w>32) : w=32
    s=""
    repeat w
        if bits&(1<<cnt) : s="1"+s : else : s="0"+s
    loop
    return s
#global
 
;サンプル
    mes "2進表記  10進表記"
    bit=1
 
    repeat 8
        a = bit<<cnt
        mes strbit(a,8) + " = " + a
    loop
 
    bit=%10000000
 
    repeat 8
        a = bit>>cnt
        mes strbit(a,8) + " = " + a
    loop
 
    stop


関連ページ

ビットの補完

x座標、y座標を1つの変数で管理

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
    ;----------------------------------------------------------
    ;座標を1つの変数で管理する
    ;----------------------------------------------------------
    x=100 : y=100    ;初期位置
        mes "(x,y)=("+x+","+y+")"
    p=y<<16 | x      ;yを左に16bitシフトさせ、xとの論理和をとる
        mes "p="+p
    x=8161 : y=8277  ;元データを壊す
    x=p & $FFFF      ;下位16bitにマスクをかけてx座標を取り出す
 
/*
 *      y=np>>16 
 *      ビットシフトでシフト前の位置にONbitがくるか
 *      OFFbitがくるかは環境依存(Cの話?)なので念の為に
 *      下位16bitにマスクをかけると確実に下位16bitが取り出せる
 */
 
    y=p>>16 & $FFFF  ;pを右に16bitシフトさせ下位16bitに
                     ;マスクをかけてy座標を取り出す
 
        mes "(x,y)=("+x+","+y+")"
    stop

色のR,G,B要素も1つの変数で管理

APIを使うようになると色関係の値はRGB単独で返ってくるわけではなく、1つの32ビット数値として返ってくるものが多い。
32ビット数値には次に示す

 1〜 8ビットにR要素
 9〜16ビットにG要素
17〜24ビットにB要素

というように各要素が格納されている。
これを取り出す方法として一番簡単なのはビットシフト(>>)とビットマスク(&)を使うことだと思う。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
    #module
    #deffunc color32 int
        mref clr,0
 
        ;32ビットの色情報をRGB要素に分解する
        r=clr & $FF : g=clr>> 8 & $FF : b=clr>>16 & $FF
 
        color r,g,b
        return
    #global
 
    color32 $00000000
    boxf 0,0,winx,winy
    wait 200
 
    color32 $430000FF
    boxf 0,0,winx,winy
    wait 200
 
    color32 $3D00FF00
    boxf 0,0,winx,winy
    wait 200
 
    color32 $D3FF0000
    boxf 0,0,winx,winy
    stop

HSP予約のcolor命令はRGB要素を独立して指定しますので、ここでは色を表す32ビット数値を指定して現在色として設定するモジュール仕立てにしてみました。
命令名もcolor32。いい感じですか?(Win32 APIの32に合わせて・・・)
実行すると『黒→赤→緑(黄緑)→青』と2秒置きに背景が変わります。
色情報に使用されるのは下位24ビットなので、上位8ビットはどんな値でも影響ありません。

逆にHSPからAPIに色情報を与えたい場合でもAPIは色情報を受け取る引数を1つしか用意していないものもあります。
そういった時は上の取り出す手順の逆を行います。

  1
  2
  3
 
 
 
    clr=rval|clr
    clr=gval<<8|clr
    clr=bval<<16|clr

のようにします。or演算子は+演算子でもいいと思います。

ここでRGB要素に分解する時には『& $FF』があったものが、32ビット値にまとめる時にはand演算子がなくなっています。
その理由は・・・ビットの補完を見てください。

特定のビットをオフ(0)にする

できました。 -- kz3
ソースを載せるにあたっていろいろ調整していてUPが遅れてしまいました。
特定のビットをONにするのにはor演算子を使いますが、特定のビットをオフにするのにxor演算子は使えません。
対象となるビット列をtarget、オフにするビットを表したビット列をptn(patternの略)とした時、ptnを用いてtargetの特定ビットをオフにする式は

(target^(ptn^$FFFFFFFF))&ptn|(ptn^$FFFFFFFF)^(target^(ptn^$FFFFFFFF))

となります。(自分で考えついた方法。他にもっと短い式があるだろうか?) → もっと短い式は後半に掲載してあります。
分かりやすくしたソースを載せます。また、ビット列表示モジュールも作ってみました。

+  bitoff[sp].as

既にOFFになっているビットはOFFのままです。
これがxor演算子を使うと、0xor1=1になってしまいビットが立ってしまいます。
ファイル名から分かるとおり、このファイルはタブスペコンバーター?でインデントを整形しています^^(話が横に反れました。)
bitoffモジュール内の処理を図にしてみました。

+  処理内容

もっと効率のいいアルゴリズムがあったら是非載せてください。

  • こういうことでしょうか? -- GENKI? 2005-07-10 22:16:13 (日)
    方針的には
    c = a and b xor a
    aが対象値、bでoffにする場所を指定する。
    HSPでの書式は次の通り。
    c = a&b^a
+  サンプルスクリプト
  • こういう方法もあります。 -- ANTARES 2007-10-01
result = pattern ^ $FFFFFFFF & target

$FFFFFFFFとxorをとることによってpatternを反転し、targetと andをとれば、patternが1のビットのみクリアされます。
Cのようにビット反転演算子が使えれば、さらに簡単になるのですが……。

値の交換

xor演算子は潜在的に暗号化・複合化として機能しますが、これを利用して2つの値を交換するというスワップに使えます。

  1
  2
  3
  4
  5
  6
  7
 
 
 
 
 
 
 
    a = 5: b = 10
 
    a ^= b ; a = a^b	;暗号化→a(aをキーbで暗号化、またはbをキーaで暗号化)
    b ^= a ; b = b^a	;暗号(a)をキーbで復号→元のa→b
    a ^= b ; a = a^b	;暗号(a)を元のキーa(現在のb)で復号→元のb→a
 
    mes a: mes b

3行目はaをbというキーで暗号化したと捕らえられますが、同時にbはaというキーで暗号化したとも捕らえられます。
つまりこの演算から得られる値は両者の復号データと見なせます。
この復号データに暗号化時のキーをxorすると元データが得られます。(排他的論理和参照
つまりbをキーとすればaが得られ、aをキーとすればbが得られるというわけです。

wpokeで書き込んだ負の値を復元する

  1
  2
  3
  4
  5
 
 
 
 
 
    s="**"
    wpoke s,0,-15
    wpeek n,s,0
    mes n
    mes ((n<<16)>>16)
  -15 = 11111111 11111111 11111111 11110001
wpoke                     11111111 11110001 = 65521
wpeek                     11111111 11110001
<<16    11111111 11110001 00000000 00000000
>>16    11111111 11111111 11111111 11110001 = -15

wpokeで書き込んだ値をwpeekで読み込むと負の値を書き込もうが 0 〜 65535 が値として戻ってきますが、
シフト演算子を使うと -32768 〜 32767 までの値を取り出すことが出来ます。
>>16をすると元の値に戻りそうですが符号付の場合は最上位ビットがコピーされます。
これを、算術シフトと言うそうです。

コメント

  • 短かーーーーー!!!なるほど・・・ -- kz3 2005-07-10 22:26:38 (日)
  • そうか・・・&演算で対象のビットの立っている所を抽出して・・・そこにxorすれば立っている所が1xor1=0となるのか・・・。 -- kz3 2005-07-10 22:30:44 (日)
  • 任意ビットのオフは、これ書いた時期のちょっと前に必要に迫られてたまたま作ったばかりの頃でした。今この話題出てきてたら作ってた事自体を忘れてただろうな… [worried2]~ -- GENKI? 2005-09-04 01:17:30 (日)
  • しかし…コメントをこの位置にしたのはまずかったかな -- GENKI? 2005-09-04 01:20:00 (日)
  • 1+2+3=6…っておもちゃってた。1+4=6すね。 [tere] -- GENKI? 2005-09-05 02:13:44 (月)
  • ん、GENKIさんっ2+4=6ですよ^^; -- kz3 2005-09-05 08:40:03 (月)
  • Σ( ̄Д ̄;)とうとう足し算もろくに出来なくなってしまったか_| ̄|○
    ちなみに1,2,3はかけても足しても6になるとゆー不思議な並び、というのが頭にあってつい出てきてしまったんだけど、1 + 4 ? 5じゃん [tere]
    恥の上塗りだ…。 -- GENKI? 2005-09-05 14:41:02 (月)
  • ビットの補完に何を書くつもりだったんだろう・・・自分;; -- kz3 2005-11-10 (木) 15:47:56

URL B I U SIZE Black Maroon Green Olive Navy Purple Teal Gray Silver Red Lime Yellow Blue Fuchsia Aqua White

if文での実践

では実際に使ってみましょう。ということで。

  1
  2
  3
 
 
 
    n = 5
    if n&4 : mes "ok"
    stop

これを実行すると

ok

と表示されました。

n&4

は、条件式ではないのに…なぜでしょう?

ちなみに 5は、

(4×1) + (2×0) + (1×1) = 5

です。この辺の詳しいことはビット操作をご覧下さい。

if文での条件式の扱い

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 
 
 
 
-
|
|
-
|
|
!
 
 
 
    #define NUMBER 5
    n = NUMBER
    mes "条件式 → 判定結果"
    repeat 11,-5
        if cnt {
            ;条件式が満たされている場合(真の場合)
            mes ""+cnt+" → TRUE"
        } else {
            ;条件式が満たされていない場合(偽の場合)
            mes ""+cnt+" → FALSE"
        }
    loop
 
    stop

どうやら ifでは条件式の結果が 0でないときは条件が満たされたと判断し、 0のときは条件が満たされたものと判断するようです。

条件式

条件式には以下のようなものがあります。

記号意味記号意味
=同じ==同じ
<より小さい<=以下
>より大きい>=以上
!同じでない!=同じでない

if文の条件式にはこの他にも論理演算子などが使われることがあります。
ではこの辺でこれらの条件式の演算子を使用するとどうなるか確認しておきます。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
    #define POSY 60
    #define NUMBER 5
    n = NUMBER
    mes "n = " + n
 
    moji = ""
    moji = n    : mes "  n → "+moji
 
    ;---------------
    ; 条件式 1
    ;---------------
    pos 0, POSY
    mes "\n条件式1"
    moji = n=5    : mes "n=5 → "+moji
    moji = n>5    : mes "n>5 → "+moji
    moji = n>0    : mes "n>0 → "+moji
    moji = n<5    : mes "n<5 → "+moji
    moji = n<0    : mes "n<0 → "+moji
    moji = n!5    : mes "n!5 → "+moji
    moji = n!0    : mes "n!0 → "+moji
 
    ;---------------
    ; 条件式 2
    ;---------------
    pos 100, POSY
    mes "\n条件式2"
    moji = n==5    : mes "n==5 → "+moji
    moji = n>=5    : mes "n>=5 → "+moji
    moji = n>=0    : mes "n>=0 → "+moji
    moji = n<=5    : mes "n<=5 → "+moji
    moji = n<=0    : mes "n<=0 → "+moji
    moji = n!=5    : mes "n!=5 → "+moji
    moji = n!=0    : mes "n!=0 → "+moji
 
    ;---------------
    ;論理演算子
    ;---------------
    pos 250, POSY
    mes "\n論理演算子"
    moji = n&4    : mes "n&4 → "+moji
    moji = n&2    : mes "n&2 → "+moji
    moji = n&1    : mes "n&1 → "+moji
    mes ""
    moji = n&4=4    : mes "n&4=4 → "+moji
    moji = n&2=2    : mes "n&2=2 → "+moji
    moji = n&1=1    : mes "n&1=1 → "+moji
 
stop
  • 条件式
    結果を見ても分かるとおり、「条件式」は下記のような結果を返すことが分かります。
    条件式が式が満たされるとき(真)1
    条件式が式が満たされないとき(偽)0
     
  • 論理演算子
    n&4
    は 3番目のステートを取り出しているだけです。
    n&4=4
    は、3番目のステートを取り出し、それが 4であるか?を調べています。

では、はじめに戻って…

  1
  2
  3
 
 
 
    n = 5
    if n&4 : mes "ok"
    stop

マニュアルではifのパラメータはp1は条件式となっているので、ここは

n&4=4

とするほうが正式な方法なのだと思われます。ただ、

n&4

と記述してもn=5のとき 0以外(この場合4)が返され真(式を満たす)と判断されるため問題はありません。
もしn=9であった場合 0が返されるため、偽(式を満たさない)と判断されます。

  • ANTARES 追記:「if n&4!=0」の方が実際の判定に近いし、4が他の値の場合でも同じという意味で汎用性があります。2007-10-01 (月) 10:50:01

というわけでこのような記述ができるというわけです。

a=1 : b=1
if (a=1)&(b=1) : mes "ok"
stop

このような場合も実際にやっているのは論理積ですので、同様の手順で考えることが出来ます。

注意 ANTARES 2007-10-01 (月) 11:06:39

ここでやっているのは、というより、HSPの論理演算は、ビット単位の論理演算であり、
論理演算とは区別する考え方が支配的です。
このあたりを理解しないで

if (key&4)&(enemy=1)

としてしまい、なぜ動かないのかと掲示板に投稿する例がときどき見られます。
昔のBASICでは、真を意味する値が1ではなく-1だったので、この判定でうまくいっていたのですが……。

JavaやC#など、真・偽を数値として処理することを許さないのが最近の傾向のようです。
CにもHSPにも昔のBASICにも真の定義が2つあるので、ある意味便利だけれど、
逆に混乱の原因にもなっているという反省から生まれた傾向でしょう。

stick命令の値の取り扱い

HSPの論理演算を正しく理解していないと陥ってしまいやすいミスのパターンがあります。
stick命令を例にとってミスの例と解決策を見てみましょう。
[hsp3]

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
;
;	stick命令で取得した値の使い方でよく陥るミス
;	for HSP3
;
title "キーボードのカーソルキーを押してください。"
button gosub "テスト", *btn
fg = 0
*main
    redraw 1 : await 16 : redraw 0 : color 255, 255, 255 : boxf : color : pos 0,0
 
    stick key, 31
 
    pos 0, 30
 
    ;基本的な使い方
    mes "その1 key&1"
    m = ""
    if key&1 : m += "←"
    if key&2 : m += "↑"
    if key&4 : m += "→"
    if key&8 : m += "↓"
    mes m
 
    ;よくあるミス
    mes "その2 (key&1) & (fg=1)"
    m = ""
    if (key&1) & (fg=1) : m += "←"
    if (key&2) & (fg=1) : m += "↑"
    if (key&4) & (fg=1) : m += "→"
    if (key&8) & (fg=1) : m += "↓"
    mes m
 
    ;正しいやり方
    mes "その3 ((key&1)!0) & (fg=1)"
    m = ""
    if ((key&1)!0) & (fg=1) : m += "←"
    if ((key&2)!0) & (fg=1) : m += "↑"
    if ((key&4)!0) & (fg=1) : m += "→"
    if ((key&8)!0) & (fg=1) : m += "↓"
    mes m
 
    ;計算途中を見てみます。
    mes "fg = " + fg
    mes "(fg=1) = " + (fg=1)
    mes "key = " + key
    mes "key&8 = " + (key&8)
    mes "(key&8)!0 = " + ((key&8)!0)
    mes "((key&8) & (fg=1)) = " + ((key&8) & (fg=1))
    mes "(((key&8)!0) & (fg=1)) = " + (((key&8)!0) & (fg=1))
 
    goto *main
 
*btn
    fg = fg^1
    return
 

*1 マスクする
*2 ONにする
添付ファイル:
filebitoff[sp].as
616件 [詳細]
トップ    編集凍結 差分バックアップ添付複製名前変更リロード   新規一覧単語検索最終更新   最終更新のRSS
Last-modified: 2009-06-27 (土) 02:11:27 (1625d)