忍者ブログ
  • 2024.04
  • 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
  • 2024.06
[PR]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

【2024/05/04 01:06 】 |
DBIにおける32ビット演算に関してメモ
私は32bitDIBクラスを作成して使用しているのだが、今回、エフェクトを作りたいと考えるようになった。
その上で、私は飽和加算および飽和減算を行う必要性があることに気がついた。
なぜなら、個別にビットを切り演算すると言うことは著しい速度低下が行われるからだ。

既存の方法
//32BitDIBは EERRGGBB(EEは使用しない)として使用されているので、分解しRGBを受け取る
void CDIBGL::GetRGBfor32DIB(LPCOLORREF bgr,LPBYTE r,LPBYTE g,LPBYTE b){
    *r = (BYTE)((*bgr & 0x00FF0000)>>16);
    *g = (BYTE)((*bgr & 0x0000FF00)>>8);
    *b = (BYTE)((*bgr & 0x000000FF));
    //printf("RGB :%.8X R:%.2X G:%.2X B:%2X\n",bgr,*r,*g,*b);//debug用
}

BYTE CDIBGL::GetBYTE(int i){
    if( (i>>31)&0x00000001 ){//負数ならば i<0
        return 0x00;
    }
    if( (i>>8) > 0){//iが負数でないとき255より上のビットがあれば i>255
        return 0xFF;
    }
    return (BYTE)i;
}

まあ、最初はこの二つのメソッドを使用してこのように値を取り出して加算していたわけだ。
…… まあ、いろいろなサイトを見てこの方法に行き着いたし、速度面を除けば不満はない。
速度を考えなければな!

ちなみに、二つのメソッドをそのまま使用する関数にコピペすると約七割~九割程度の時間短縮が可能だ。
これは実測で分ったことだが、見栄えが悪いし時間がかかりすぎる。

改善するにあたって参考にしたサイト
まず、このサイトを見て欲しい。
http://shakenbu.org/yanagi/misc/bitop.html
非常に有益な方法が書いてあることが一目でわかる、いいサイトだなって思う。マジおすすめ。

でも俺は頭が良くない!
だが、よくわからん。ビット演算はよくわからんな、直感的に理解できない。
じゃ、どうしようって俺は考えた。

そうだ! 値をみて考えよう!

ということで以下のようなソースファイルを作って演算や値を見て考えることにした。
飽和加算の実験ソースコード
////////////////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#define DWORD unsigned int
#define COLORREF DWORD
#define WORD unsigned short
#define BYTE unsigned char

#define RGB(r,g,b)  ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))

char *print_bin( unsigned int x ){
static char  out[100];
    unsigned int n, i, j;
    n = 1<<31;
    j = 0;
    for( i=0; i<32; i++ ){
        if( x&(n>>i) )
            out[j]='1';
        else
            out[j]='0';
            j++;
        if( i%4==3 ){
            out[j]=',';
            j++;
        }
    }
    out[j-1] = '\0';
    return out;
}


int main(void){
    char str[100];
    DWORD A,B,tmp,mask,result;
   
    A=RGB( 12,123,255);//012とすると8進数と間違えられる恐れあり
    B=RGB(111,222,148);
   
    printf("tmp = ((A&B)+((A^B)>>1)&0x7f7f7f7f)&0x80808080\n");
   
    printf( "A         :(%s)_2 = (%.8X)_16\n",print_bin( A ), A );
    printf( "B         :(%s)_2 = (%.8X)_16\n",print_bin( B ), B );
   
    tmp = A&B;
    printf( "A&B       :(%s)_2 = (%.8X)_16\n",print_bin( tmp ), tmp );
   
    tmp = A^B;//^はXOR!知ってると思うけど!
    printf( "A^B       :(%s)_2 = (%.8X)_16\n",print_bin( tmp ), tmp );
   
    tmp = (A^B)>>1;
    printf( "A^B>>1    :(%s)_2 = (%.8X)_16\n",print_bin( tmp ), tmp );
   
    printf( "0x7f7f7f7f:(%s)_2 = (%.8X)_16\n",print_bin( 0x7f7f7f7f ), 0x7f7f7f7f );
   
    tmp = ((A^B)>>1)&0x7f7f7f7f;
    printf( "((A^B)>>1)&0x7f7f7f7f:\n           (%s)_2 = (%.8X)_16\n",print_bin( tmp ), tmp );
   
    tmp =  (A&B) + (((A^B)>>1)&0x7f7f7f7f);
    printf( "(A&B)+((A^B)>>1)&0x7f7f7f7f:\n           (%s)_2 = (%.8X)_16\n",print_bin( tmp ), tmp );
   
    printf( "0x7f7f7f7f:(%s)_2 = (%.8X)_16\n",print_bin( 0x80808080 ), 0x80808080 );

    tmp =  (A&B) + (((A^B)>>1)&0x7f7f7f7f)&0x80808080;
    printf( "((A&B)+((A^B)>>1)&0x7f7f7f7f)&0x80808080:\n           (%s)_2 = (%.8X)_16\n",print_bin( tmp ), tmp );
   
   
    printf("\nmask = (tmp << 1) - (tmp >> 7)\n");
    mask = tmp<<1;
    printf( "tmp<<1:    (%s)_2 = (%.8X)_16\n",print_bin( mask ), mask );
   
    mask = tmp>>7;
    printf( "tmp>>7:    (%s)_2 = (%.8X)_16\n",print_bin( mask ), mask );

    mask = (tmp<<1) - (tmp>>7);
    printf( "(tmp<<1)-(tmp>>7):\n           (%s)_2 = (%.8X)_16\n",print_bin( mask ), mask );
   
   
    printf("\nresult = (( A + B ) - mask) | mask\n");
    result =  (A+B)-mask;
    printf( "(A+B)-mask:(%s)_2 = (%.8X)_16\n",print_bin( result ), result );
   
    result =  ((A+B)-mask)|mask;
    printf( "((A+B)-mask)|mask:\n           (%s)_2 = (%.8X)_16\n",print_bin( result ), result );
   
    return 0;
}

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

まあ、こんな感じ。わざわざブログにソースコードを載せる必要なんて無いんだけどね。
ちなみに
char *print_bin( unsigned int x )
は32bitの変数を二進数で表示する関数。printfで使用している。
*実はどこかで拾ったのだがどこで拾ったのか思い出せん。
確か元々16ビットのを二進数にする関数だった覚えがある。でも32ビット欲しかったから少し改変して32ビットに対応させた。うーん、どこだったっけ……見つけたら追記しておく。作者の人ごめんね。

で、実際の演算結果は下のようになった。
ソースコードの演算結果

tmp = ((A&B)+((A^B)>>1)&0x7f7f7f7f)&0x80808080
A         :(0000,0000,1111,1111,0111,1011,0000,1010)_2 = (00FF7B0A)_16
B         :(0000,0000,1001,0100,1101,1110,0110,1111)_2 = (0094DE6F)_16
A&B       :(0000,0000,1001,0100,0101,1010,0000,1010)_2 = (00945A0A)_16
A^B       :(0000,0000,0110,1011,1010,0101,0110,0101)_2 = (006BA565)_16
A^B>>1    :(0000,0000,0011,0101,1101,0010,1011,0010)_2 = (0035D2B2)_16
0x7f7f7f7f:(0111,1111,0111,1111,0111,1111,0111,1111)_2 = (7F7F7F7F)_16
((A^B)>>1)&0x7f7f7f7f:
           (0000,0000,0011,0101,0101,0010,0011,0010)_2 = (00355232)_16
(A&B)+(((A^B)>>1)&0x7f7f7f7f):
           (0000,0000,1100,1001,1010,1100,0011,1100)_2 = (00C9AC3C)_16
0x80808080:(1000,0000,1000,0000,1000,0000,1000,0000)_2 = (80808080)_16
((A&B)+((A^B)>>1)&0x7f7f7f7f)&0x80808080:
           (0000,0000,1000,0000,1000,0000,0000,0000)_2 = (00808000)_16

mask = (tmp << 1) - (tmp >> 7)
tmp<<1:    (0000,0001,0000,0001,0000,0000,0000,0000)_2 = (01010000)_16
tmp>>7:    (0000,0000,0000,0001,0000,0001,0000,0000)_2 = (00010100)_16
(tmp<<1)-(tmp>>7):
           (0000,0000,1111,1111,1111,1111,0000,0000)_2 = (00FFFF00)_16
result = (( A + B ) - mask) | mask
(A+B)-mask:
           (0000,0000,1001,0100,0101,1010,0111,1001)_2 = (00945A79)_16
((A+B)-mask)|mask:
           (0000,0000,1111,1111,1111,1111,0111,1001)_2 = (00FFFF79)_16

よくわかるような気がする解説。
引用したサイトを見て分る人は読む必要が全くないのだが、一応解説。

まず前提として理解して欲しいことはtmpでやりたいことだ。
これは演算結果からも明白なようにmaskを作るための演算で、実際の今回の演算結果を見ると、
((A&B)+((A^B)>>1)&0x7f7f7f7f)&0x80808080:
    (0000,0000,1000,0000,1000,0000,0000,0000)_2 = (00808000)_16
mask = (tmp << 1) - (tmp >> 7)
    (0000,0000,1111,1111,1111,1111,0000,0000)_2 = (00FFFF00)_16
もうやりたいことが分ったと思う。つまりtmpではmaskを作るためにその8ビットの先頭を1にすることが目的なのだ。

次に理解して欲しいことは0x7f7f7f7fの取り扱いで、これは
0111,1111,0111,1111と続いていく。
これが何を表しているのかというと、他の値へ繰り上がるビットをマスクして除去している。
なんで0111,0111としていないかは当然わかるよね?
(ヒント:一色は8ビット……いや、ヒント要らなかったか)

更にA^B>>1は何かと言えばxorしているものを1ビットずらしているんだ。
(^はxor。xorはわかる……よね?)
これは8ビットの最初のビットが片方1かを確認しているだけだ。他のビットには一切考えなくてもいい。
(だって、&0x7f7f7f7fでマスクするんだから!)
で、右にずらす意味がわからんだろう? それはA&Bを加算したときに理解できると思う。

勘のいい人はもう分ってるね。お疲れ様でした。
(もちろん、勘のいい人というのは情報工出身者を指すぞ!)

まあ、典型的な繰り上げ加算処理だから分ってる人は飛ばして貰っていいんだが、
このあと、A&Bと加算するとあら不思議、飽和する8ビットの先頭が1に!!!!

これはA&Bで8ビットの先頭が1ならそのまま1に
(A&B)+(A^B)>>1との加算で8ビットの二番目のビットを繰り上げるわけだ。
なんでXORだったか分るよね?仮にそのままandしていたら今頃8ビットの先頭は1+1で繰り上がって0になってしまう。

あとは簡単だから説明しない。
流石にこれ以降で分らない部分は流石に無いと思うので。
(分らんとこは演算結果を見ればすんなり理解出来ないと思う)

感想
記事……長!!!!ひでぇ!!こんな記事誰も読まない気がする!!!!!
熱心に書いたらすごく長くなってしまった。


あ、ちなみにmainのソースコードや演算結果の表示が少し汚いけどそこはスルーで。
(元々、記事にするつもりは無く独習のつもりだったので勘弁してくれ!)

飽和減算の記事も書きたいな。また、時間があったら書こう。
PR
【2010/08/16 00:01 】 | ビット演算 | コメント(0) | トラックバック()
<<LUAが動かねぇよぉ! | ホーム | VC++においてのコンソールの削除方法について>>
コメント
コメント投稿














虎カムバック
トラックバックURL

<<前ページ | ホーム | 次ページ>>