5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C言語の 論理値で遊ぶ

Last updated at Posted at 2023-08-13

これは何?

Qiita に C言語の 論理値で遊ぶ記事があったんだけど、消滅したようなので 私なりの内容で書いてみる。

そもそも

そもそも、C言語に論理型はあるようなないような。

C99 より古い C言語には bool_Bool もない。
コンパイラやフレームワークを作っている人たちが BOOLHOGE_BOOL のような型を各自がバラバラに定義して、衝突したりしなかったり、めんどくさい世界だった。

そこで。
C99 で _Bool という型が入った。「下線+大文字始まりはユーザーが勝手に定義ちゃいけないよ」というルールだったので、互換性の問題は存在しない。存在するとしたら、前述のルールを無視しているひとが悪い。という感じ。

しかし _Bool という名前はみっともないと思ったのか、stdbool.hinclude すると bool というシンボルが使えるようになる。

ということで、言語そのものには bool はないけど、標準ライブラリにはある。

それと。ちょっと思いがけなかったんだけど

stdbool.h
#define bool _Bool

となっている。 typedef ではない。
これはたぶん、

古いライブラリA
typedef int bool
古いライブラリB
#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 の値を取ることができる整数型。

C99
_Bool a=2;
printf( "a=%d\n", (int)a ); //=> a=1 となる。

たぶん、悪いキャストなんかをしない限り、どうやっても 0 か 1 しか入らないと思う。ちょっと自信がないけど。

変換

数値型の値 a から _Bool への変換は

C99
_Bool b = (a==0 ? 1 : 0);

という計算になる。
ここが 単なる 1bit 符号なし整数とは振る舞いが異なるので要注意ポイント。

具体的には

  • _Bool の 1 を ++ で増やしても 1 のまま
  • 0.0000001_Bool にすると 1 になる
    など。

_Bool から 他の数値型への変換は 1 または 0 になるのでまあ普通。
二項演算など、なんか演算子に与えるとすぐに int に昇格するので要注意だけど、それは char なんかと同じなので C言語に慣れている人なら違和感ないよね。

遊ぶ

不等号の結果の引き算

よくやるのは

C
int sign = (0<a) - (a<0);

というやつ。正零負を、 1 0 -1 に変換したいときによく書く。

あるいは L と R という値があって、

  • x < L なら -1
  • L ≦ x ≦ R なら 0
  • R < x なら 1

のようにしたい場合も同様に

C
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通りの分類をしたいとき。

C99
int pattern_number = (y<T)*3 + m;

のようにすることができる。もちろん逆に

C99
int pattern_number = (y<T) + m*2;

でもいい。たまにこういうことをする。

特定の条件でゼロにする

元の記事リスペクトで。

unsigned int の変数 u があり。「f(u) が 真 なら u をゼロにする」と書きたい。

普通は

C99
if (f(u)){ u=0; }

とするけど、

C99
u &= 0u - !f(u);

と書いてもいい。

&= 版の方が分岐ペナルティがない分速いかもしれないけど、普通に書いても必要ならコンパイラが頑張るだろうから実用的な価値はなさそうだと思う(思うだけ。未調査)。

0 または 1 という判断

例えば float の値 f が 0 または 1 であるという判断は、普通

C99
if (f==0 || f==1){}

のように書くけど、f が有限だとわかっているのであれば

C99
if (f==(_Bool)f){}

と書いても同じ結果になる。
書かないけど。

真の数が ちょうど 2個

ae は論理演算の結果が入っているとして。
ae のうち、真 のものがちょうど 2個なら」という条件を書きたいとする。

この条件は

C99
if (2==a+b+c+d+e){}

と書ける。私なら仕事でもこう書くかなぁ。
普通どう書くんだろう。

上記の例は「ちょうど2個」だけど「3個以下」とかでも簡単に書ける

不等号の連鎖

ac は論理演算の結果が入っているとして。

普通なら

C99
if ((a || !b) && c){}

と書くような場面で、短絡評価が気にならないのであれば

C99
if (a < b < c){}

と書いてもいい(よくない)。具体的には

閏年判定
_Bool is_leap = !(year % 400) < !(year % 100) < !(year % 4);

こんな感じ。
念のために書いておくと、こう書くべき理由はなにもないと思う。

ということで

ということで。

_Bool と数値型の比較・演算・変換 で楽しいことが色々できて、そのうちのいくつかは実際に役に立つ。

関連記事

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?