cで~(チルダ)を使ってみた
AyumiKatayamaさんの記事を拝読して、初めて知った演算子~(チルダ)の存在。早速使ってみて、なるほど便利なことを実感。
Ayumiさん、ありがとうございました!
~(チルダ)
US配列キーボードの、「1」のすぐ左のキーに設定されている~(チルダ)。
用途がわからず、つい先日、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キーを使わずに入力できる「`」は、何のため?こんな単項演算子はないぞ!)
個人的には、「€」とか「¢」等の通貨記号を設定してもらった方が便利なんだけどなぁ。英文入力時に、これらの文字を入力するためのキーコンビネーションがパッと思い浮かばずに、仕方なしにわざわざ日本語入力モードに変えて「ユーロ」とか「セント」とタイプして変換させている。やれやれ。
ここまで読んでいただき、ありがとうございます。