これは何?
Qiita に C言語の 論理値で遊ぶ記事があったんだけど、消滅したようなので 私なりの内容で書いてみる。
そもそも
そもそも、C言語に論理型はあるようなないような。
C99 より古い C言語には bool も _Bool もない。
コンパイラやフレームワークを作っている人たちが BOOL や HOGE_BOOL のような型を各自がバラバラに定義して、衝突したりしなかったり、めんどくさい世界だった。
そこで。
C99 で _Bool という型が入った。「下線+大文字始まりはユーザーが勝手に定義ちゃいけないよ」というルールだったので、互換性の問題は存在しない。存在するとしたら、前述のルールを無視しているひとが悪い。という感じ。
しかし _Bool という名前はみっともないと思ったのか、stdbool.h を include すると bool というシンボルが使えるようになる。
ということで、言語そのものには bool はないけど、標準ライブラリにはある。
それと。ちょっと思いがけなかったんだけど
#define bool _Bool
となっている。 typedef ではない。
これはたぶん、
typedef int bool
#ifndef bool
#define bool int
#endif
のようなライブラリとの戦いに備えてのことだと思う。
C++ との違い
C++ は、たぶん(自信ない)当初から bool true false を予約語にしていた。
C言語は C99 で _Bool が入っている。そのあたりもあり、色々違う。
| C言語 | C++ | |
|---|---|---|
a==b などの型 |
int |
bool |
bool の正体 |
_Bool になるマクロ (in stdbool.h C99以降) |
予約語 |
true の正体 |
1 になるマクロ (in stdbool.h C99以降) |
予約語 |
false の正体 |
0 になるマクロ (in stdbool.h C99以降) |
予約語 |
C++ に馴染んでいると、 a==b なんかが _Bool ではなく int なのがわりと思いがけない。_Generic を使うと罠となるので要注意。
_Bool の正体
_Bool の正体は、0 または 1 の値を取ることができる整数型。
_Bool a=2;
printf( "a=%d\n", (int)a ); //=> a=1 となる。
たぶん、悪いキャストなんかをしない限り、どうやっても 0 か 1 しか入らないと思う。ちょっと自信がないけど。
変換
数値型の値 a から _Bool への変換は
_Bool b = (a==0 ? 1 : 0);
という計算になる。
ここが 単なる 1bit 符号なし整数とは振る舞いが異なるので要注意ポイント。
具体的には
-
_Boolの 1 を++で増やしても 1 のまま -
0.0000001を_Boolにすると 1 になる
など。
_Bool から 他の数値型への変換は 1 または 0 になるのでまあ普通。
二項演算など、なんか演算子に与えるとすぐに int に昇格するので要注意だけど、それは char なんかと同じなので C言語に慣れている人なら違和感ないよね。
遊ぶ
不等号の結果の引き算
よくやるのは
int sign = (0<a) - (a<0);
というやつ。正零負を、 1 0 -1 に変換したいときによく書く。
あるいは L と R という値があって、
- x < L なら -1
- L ≦ x ≦ R なら 0
- R < x なら 1
のようにしたい場合も同様に
int r = - (x<L) + (R<x);
と書ける。便利。
いずれも測ってないけど、多くの CPU でわりとよいコードになりやすいと思う(思うだけ)。
不等号で論理演算
A と B を入力として 下表のような結果を得たい場合。
そして A と B が 0 と 1 のいずれかになるとわかっている場合。
| A\B | true | false |
|---|---|---|
| true | true | false |
| false | true | true |
普通は !A || B と書くけど、 短絡評価しても意味がないのであれば A<=B と書いてもいい。
書かないけど。
パターンを埋める
例えば 0, 1, 2 の三通りの値を取る m があり。
なんか数値の y があって。
〈y が T 未満か T 以上か〉×〈m の三通り〉 で 6通りの分類をしたいとき。
int pattern_number = (y<T)*3 + m;
のようにすることができる。もちろん逆に
int pattern_number = (y<T) + m*2;
でもいい。たまにこういうことをする。
特定の条件でゼロにする
元の記事リスペクトで。
unsigned int の変数 u があり。「f(u) が 真 なら u をゼロにする」と書きたい。
普通は
if (f(u)){ u=0; }
とするけど、
u &= 0u - !f(u);
と書いてもいい。
&= 版の方が分岐ペナルティがない分速いかもしれないけど、普通に書いても必要ならコンパイラが頑張るだろうから実用的な価値はなさそうだと思う(思うだけ。未調査)。
0 または 1 という判断
例えば float の値 f が 0 または 1 であるという判断は、普通
if (f==0 || f==1){略}
のように書くけど、f が有限だとわかっているのであれば
if (f==(_Bool)f){略}
と書いても同じ結果になる。
書かないけど。
真の数が ちょうど 2個
a〜e は論理演算の結果が入っているとして。
「a〜e のうち、真 のものがちょうど 2個なら」という条件を書きたいとする。
この条件は
if (2==a+b+c+d+e){略}
と書ける。私なら仕事でもこう書くかなぁ。
普通どう書くんだろう。
上記の例は「ちょうど2個」だけど「3個以下」とかでも簡単に書ける
不等号の連鎖
a 〜 c は論理演算の結果が入っているとして。
普通なら
if ((a || !b) && c){略}
と書くような場面で、短絡評価が気にならないのであれば
if (a < b < c){略}
と書いてもいい(よくない)。具体的には
_Bool is_leap = !(year % 400) < !(year % 100) < !(year % 4);
こんな感じ。
念のために書いておくと、こう書くべき理由はなにもないと思う。
ということで
ということで。
_Bool と数値型の比較・演算・変換 で楽しいことが色々できて、そのうちのいくつかは実際に役に立つ。
関連記事