見出し画像

cで~(チルダ)を使ってみた

AyumiKatayamaさんの記事を拝読して、初めて知った演算子~(チルダ)の存在。早速使ってみて、なるほど便利なことを実感。
Ayumiさん、ありがとうございました!


~(チルダ)

US配列キーボードの、「1」のすぐ左のキーに設定されている~(チルダ)。

このキーは、Windowsで日本語入力切り替えにも利用されている。

用途がわからず、つい先日、Ayumiさんの記事で見つけるまで別の文字「€」を入力するようにキーボードの設定を変えていたほど謎の文字だった。

ネットでググって出てきた、cの演算子一覧表によると、「ビット反転」演算子として説明されている。~aだと、aをビットごとに反転した値となるとのこと。

何に使うのかなと思ったら、ビット演算子(&)とあわせて、特定のビットをオフすることができるらしい。


早速やってみた

まずは2進数でやってみる

2進数下4桁で「0b 1101」となっている値の右端、0ビット目の「1」
を「0」に変えたければ、「0b1110」とビット積すれば、目的の「0b1100」が得られる。積なので、各ビット桁ごとに「1」x「0」=「0」、「1」x「1」=「1」となる。

#include <stdio.h>

// べき乗
int power(int base, int exponent) {
    int p = 1;
    for (int i = 0; i < exponent; i++) {
	p *= base;
    }
	 
    return(p);
}

// 2進数表示用の関数
void printBinary(unsigned short int num) {
    int len = 16;
    static int bit[16];

    for (int i = len - 1; i >= 0; i--) {
	int p = power(2, i);

	bit[len - i -1] = num < p ? 0: 1;
	num = num < p ? num : num - p; 
    }

    for(int i = 0; i < len; i++) {
        printf("%d", bit[i]);
        if ((i + 1) % 4 == 0) printf(" ");
    }
}

void output(char *s, unsigned short num) {

    printf("%s ==> 10進数:%6u,  16進数:0x%04x  2進数:", s, num, num);
    printBinary(num);
    printf("\n");
}

int main(void) {
    unsigned short int num1;
    unsigned short int num2;

    //ビット積
    num1 = 0b1101;
    num2 = 0b1110;
    output("    num1", num1);
    output("    num2", num2);

    num1 &= num2;
    output("ビット積", num1);
    printf("\n");

    return 0;
}

実行結果(2進数の場合)

jm3nrhMac-mini-:c akio$ ./a.out
   num1 ==> 10進数:    13,  16進数:0x000d  2進数:0000 0000 0000 1101 
   num2 ==> 10進数:    14,  16進数:0x000e  2進数:0000 0000 0000 1110 
ビット積 ==> 10進数:    12,  16進数:0x000c  2進数:0000 0000 0000 1100 

うんうん、これは想定通りの結果。
num1のビット0(右端)が、「1」から「0」に変更された。

しかしここまでくるあいだに、2進数だとわかりやすいが、扱いづらいことに気づく。そもそも、printf()関数で2進数を表示したりする方法をわたしは見つけられなかったので、自前で関数printBinary()を作成することにした。


16進数でやってみる

変更したのは、初期値の設定方法のみ。16進数だと長い長い2進数をコンパクトに表示できるのはいいが、各ビットがどの状態なのかは分かりづらい。

    num1 = 0xfd;
    num2 = 0xfe;

実行結果(16進数の場合)、、、当然に結果は同じ。

jm3nrhMac-mini-:c akio$ ./a.out
   num1 ==> 10進数:   253,  16進数:0x00fd  2進数:0000 0000 1111 1101 
   num2 ==> 10進数:   254,  16進数:0x00fe  2進数:0000 0000 1111 1110 
ビット積 ==> 10進数:   252,  16進数:0x00fc  2進数:0000 0000 1111 1100 

でも、num1のビット0をオフにしたいと言う目的のためになぜ「0xfe」とビット積するのか、ぱっと見わかりずらい。ビット0以外のビットを全て「1」にした値(「0xfe」)とビット積するのだから、逆に言えばターゲットのビット0だけ「1」をセットした値(0x01)を与えて、~(チルダ)演算子でビットを反転させれば(「0xfe」)が自動的に得られる筈!


~(チルダ)を使ってみる

int main(void) {
    unsigned int num1;
    unsigned int num2;

    //ビット積
    num1 = 0xfd;
    num2 = ~0x01;
    output("    num1:", num1);
    output("    num2:", num2);

    num1 &= num2;
    output("ビット積:", num1);
    printf("\n");

    return 0;
}

実行結果(16進数の場合)、、、結果は同じだが、ちょっと変。

jm3nrhMac-mini-:c akio$ ./a.out
   num1 ==> 10進数:   253,  16進数:0x00fd  2進数:0000 0000 1111 1101 
   num2 ==> 10進数: 65534,  16進数:0xfffe  2進数:1111 1111 1111 1110 
ビット積 ==> 10進数:   252,  16進数:0x00fc  2進数:0000 0000 1111 1100

ビットを反転するのは、その変数の有効桁数(この場合は16ビット)全部を反転するみたい。ま〜そうなるわな。

確かに、num1のビット0はリセットされて「0」になったけど、、、
、、、num2が負の数(-2)として認識されている。num2を定義は、unsigned intとして定義した筈だがなぜだ?よくわからん。誰か、助けて!

(2024.10.20訂正)printf()関数で、10進数の出力に%dを指定していたのが不味かった。%uに変更することで解決。Ayumiさん、おおきに!


せっかくなので、ビット積以外のビット和とビット差を表示してみる

int main(void) {
    unsigned int num1;
    unsigned int num2;

    //ビット和
    num1 = 0xfd;
    num2 = 0x02;
    output("    num1", num1);
    output("    num2", num2);

    num1 |= num2;
    output("ビット和", num1);
    printf("\n");

    //ビット差
    num1 = 0xfd;
    num2 = 0x03;
    output("    num1:", num1);
    output("    num2:", num2);

    num1 ^= num2;
    output("ビット差:", num1);
    printf("\n");

    return 0;
}

実行結果はこちら

jm3nrhMac-mini-:c akio$ ./a.out
   num1 ==> 10進数:   253,  16進数:0x00fd  2進数:0000 0000 1111 1101 
   num2 ==> 10進数:     2,  16進数:0x0002  2進数:0000 0000 0000 0010 
ビット和 ==> 10進数:   255,  16進数:0x00ff  2進数:0000 0000 1111 1111 

   num1 ==> 10進数:   253,  16進数:0x00fd  2進数:0000 0000 1111 1101 
   num2 ==> 10進数:     3,  16進数:0x0003  2進数:0000 0000 0000 0011 
ビット差 ==> 10進数:   254,  16進数:0x00fe  2進数:0000 0000 1111 1110 

ビット和

ビット和(or条件)は、num1とnum2のいずれかで同じビットが「1」ならば結果は「1」となるので、この計算結果は正解。

特定のビット値だけをオン(「1」)にした値とビット和することで、目的のビット値をオン(「1」)にすることができる。この例題では、右から2番目のビット1をオンにした。

ビット差

ビット差(xor条件)は、num1とnum2の同じビットが一致していれば「0」、不一致ならば「1」と言う結果となる。

特定のビット値だけをオン(「1」)にした値とビット差することで、目的のビット値をフリップ、つまり「1」だったら「0」に、「0」だったら「1」にすることができる。この例題では、ビット0とビット1の値をそれぞれ変更した。


これらの操作においては、~(チルダ)の出番はなさそうだ。


後記

変数のビット操作方法がわかったが、はて、何に使うか?

前回、わたしがビット操作する必要に迫られたのは、40年ほど前に8ビットマイコンでセントロニクスインターフェイスのプリンタのドライバをアセンブラでコーディングしたとき。特定アドレスの1バイトの各ビットに意味を持たせることで、最大8つのステータスを保持できた。

今だったらどうなんだろう?ハードウェアの制御とか通信プロトコル、暗号化なんかに使うんだろうか?

今のわたしには、いずれも関係しそうにないなぁ。



久しぶりのC言語、楽しかった〜〜〜!
正直言って、ビット積/和/差を計算するよりも、数値を2進数表記するロジックを考える方が楽しかった。


、、、ところで、なぜUS配列キーボードの、とても打ちやすい場所にわざわざ「〜」が設定されているのだろう?
ひょっとして、USではスペイン語を使う人が少なからずおられるから、「ñ」を入力するの便利なのかななんて思って調べてみたけど、どうもこの文字を入力するのにこのキーは関係なさそう。

ひょっとしたら、Cプログラマが本記事で紹介した単項演算子として使うのに便利だからか???(じゃあ、同じキーで、shiftキーを使わずに入力できる「`」は、何のため?こんな単項演算子はないぞ!)

個人的には、「€」とか「¢」等の通貨記号を設定してもらった方が便利なんだけどなぁ。英文入力時に、これらの文字を入力するためのキーコンビネーションがパッと思い浮かばずに、仕方なしにわざわざ日本語入力モードに変えて「ユーロ」とか「セント」とタイプして変換させている。やれやれ。


ここまで読んでいただき、ありがとうございます。

いいなと思ったら応援しよう!

Akio van der Meer
これまでの収益は全て、それを必要としておられる方々へ、支援機関を通して寄付させていただきました。この活動は今後も継続したいと思っています。引き続きよろしくお願いいたします。

この記事が参加している募集