C
Format
Binary
printf

C言語で二進数表示

More than 1 year has passed since last update.

C 言語での開発中、データの中身を表示したいときに printf をよく使います。
中でも 16 進数表示は重宝しますが、二進数でも表示したくなったときに書式指定文字列がない。

char value; /* この値を二進数表示したいとして…… */
printf("%c%c%c%c%c%c%c%c¥n"
, value & 128 ? '1' : '0'
, value &  64 ? '1' : '0'
, value &  32 ? '1' : '0'
, value &  16 ? '1' : '0'
, value &   8 ? '1' : '0'
, value &   4 ? '1' : '0'
, value &   2 ? '1' : '0'
, value &   1 ? '1' : '0'); /* と開くのは疲れる */

三パターンほど考えたのでメモがてら残しておきます。

マクロで

with string jump table

#define BITS(c) __bits__ + 9 * (255u & c)
static const char __bits__[] =
"00000000\0""00000001\0""00000010\0""00000011\0""00000100\0""00000101\0"
"00000110\0""00000111\0""00001000\0""00001001\0""00001010\0""00001011\0"
"00001100\0""00001101\0""00001110\0""00001111\0""00010000\0""00010001\0"
"00010010\0""00010011\0""00010100\0""00010101\0""00010110\0""00010111\0"
"00011000\0""00011001\0""00011010\0""00011011\0""00011100\0""00011101\0"
"00011110\0""00011111\0""00100000\0""00100001\0""00100010\0""00100011\0"
"00100100\0""00100101\0""00100110\0""00100111\0""00101000\0""00101001\0"
"00101010\0""00101011\0""00101100\0""00101101\0""00101110\0""00101111\0"
"00110000\0""00110001\0""00110010\0""00110011\0""00110100\0""00110101\0"
"00110110\0""00110111\0""00111000\0""00111001\0""00111010\0""00111011\0"
"00111100\0""00111101\0""00111110\0""00111111\0""01000000\0""01000001\0"
"01000010\0""01000011\0""01000100\0""01000101\0""01000110\0""01000111\0"
"01001000\0""01001001\0""01001010\0""01001011\0""01001100\0""01001101\0"
"01001110\0""01001111\0""01010000\0""01010001\0""01010010\0""01010011\0"
"01010100\0""01010101\0""01010110\0""01010111\0""01011000\0""01011001\0"
"01011010\0""01011011\0""01011100\0""01011101\0""01011110\0""01011111\0"
"01100000\0""01100001\0""01100010\0""01100011\0""01100100\0""01100101\0"
"01100110\0""01100111\0""01101000\0""01101001\0""01101010\0""01101011\0"
"01101100\0""01101101\0""01101110\0""01101111\0""01110000\0""01110001\0"
"01110010\0""01110011\0""01110100\0""01110101\0""01110110\0""01110111\0"
"01111000\0""01111001\0""01111010\0""01111011\0""01111100\0""01111101\0"
"01111110\0""01111111\0""10000000\0""10000001\0""10000010\0""10000011\0"
"10000100\0""10000101\0""10000110\0""10000111\0""10001000\0""10001001\0"
"10001010\0""10001011\0""10001100\0""10001101\0""10001110\0""10001111\0"
"10010000\0""10010001\0""10010010\0""10010011\0""10010100\0""10010101\0"
"10010110\0""10010111\0""10011000\0""10011001\0""10011010\0""10011011\0"
"10011100\0""10011101\0""10011110\0""10011111\0""10100000\0""10100001\0"
"10100010\0""10100011\0""10100100\0""10100101\0""10100110\0""10100111\0"
"10101000\0""10101001\0""10101010\0""10101011\0""10101100\0""10101101\0"
"10101110\0""10101111\0""10110000\0""10110001\0""10110010\0""10110011\0"
"10110100\0""10110101\0""10110110\0""10110111\0""10111000\0""10111001\0"
"10111010\0""10111011\0""10111100\0""10111101\0""10111110\0""10111111\0"
"11000000\0""11000001\0""11000010\0""11000011\0""11000100\0""11000101\0"
"11000110\0""11000111\0""11001000\0""11001001\0""11001010\0""11001011\0"
"11001100\0""11001101\0""11001110\0""11001111\0""11010000\0""11010001\0"
"11010010\0""11010011\0""11010100\0""11010101\0""11010110\0""11010111\0"
"11011000\0""11011001\0""11011010\0""11011011\0""11011100\0""11011101\0"
"11011110\0""11011111\0""11100000\0""11100001\0""11100010\0""11100011\0"
"11100100\0""11100101\0""11100110\0""11100111\0""11101000\0""11101001\0"
"11101010\0""11101011\0""11101100\0""11101101\0""11101110\0""11101111\0"
"11110000\0""11110001\0""11110010\0""11110011\0""11110100\0""11110101\0"
"11110110\0""11110111\0""11111000\0""11111001\0""11111010\0""11111011\0"
"11111100\0""11111101\0""11111110\0""11111111";

...
char value;
printf("%s", BITS(value));

文字配列 __bits__ を、ビットパターンを表す 0 ターミネート文字列群で初期化しておき、これへのオフセットを返すマクロ BITS を介して参照する。
人間コンパイル済み switch case ジャンプテーブル方式ともいう。

文字列連結で __bits__ を定義しているのは、 0 ターミネートを \000 と書くのが面倒だったため。

註記
初出時、末尾を "11111111\0" と定義していましたがコメントを受けて "11111111" に変更しました。(つまり 1 バイト削りました)

with binary coded decimal jump table

ツイッター、そしてコメントでもいただいたので追記。
printf を使う前提で文字列化はこちらに任せ、十進数で(二進数に見える)数字をテーブル管理する。

#define BCD(c) (__bits__[c])
static const unsigned int __bits__[] = {
       0,        1,       10,       11,      100,      101,     110,       111,
    1000,     1001,     1010,     1011,     1100,     1101,    1110,      1111,
   10000,    10001,    10010,    10011,    10100,    10101,    10110,    10111,
   11000,    11001,    11010,    11011,    11100,    11101,    11110,    11111,
  100000,   100001,   100010,   100011,   100100,   100101,   100110,   100111,
  101000,   101001,   101010,   101011,   101100,   101101,   101110,   101111,
  110000,   110001,   110010,   110011,   110100,   110101,   110110,   110111,
  111000,   111001,   111010,   111011,   111100,   111101,   111110,   111111,
 1000000,  1000001,  1000010,  1000011,  1000100,  1000101,  1000110,  1000111,
 1001000,  1001001,  1001010,  1001011,  1001100,  1001101,  1001110,  1001111,
 1010000,  1010001,  1010010,  1010011,  1010100,  1010101,  1010110,  1010111,
 1011000,  1011001,  1011010,  1011011,  1011100,  1011101,  1011110,  1011111,
 1100000,  1100001,  1100010,  1100011,  1100100,  1100101,  1100110,  1100111,
 1101000,  1101001,  1101010,  1101011,  1101100,  1101101,  1101110,  1101111,
 1110000,  1110001,  1110010,  1110011,  1110100,  1110101,  1110110,  1110111,
 1111000,  1111001,  1111010,  1111011,  1111100,  1111101,  1111110,  1111111,
10000000, 10000001, 10000010, 10000011, 10000100, 10000101, 10000110, 10000111,
10001000, 10001001, 10001010, 10001011, 10001100, 10001101, 10001110, 10001111,
10010000, 10010001, 10010010, 10010011, 10010100, 10010101, 10010110, 10010111,
10011000, 10011001, 10011010, 10011011, 10011100, 10011101, 10011110, 10011111,
10100000, 10100001, 10100010, 10100011, 10100100, 10100101, 10100110, 10100111,
10101000, 10101001, 10101010, 10101011, 10101100, 10101101, 10101110, 10101111,
10110000, 10110001, 10110010, 10110011, 10110100, 10110101, 10110110, 10110111,
10111000, 10111001, 10111010, 10111011, 10111100, 10111101, 10111110, 10111111,
11000000, 11000001, 11000010, 11000011, 11000100, 11000101, 11000110, 11000111,
11001000, 11001001, 11001010, 11001011, 11001100, 11001101, 11001110, 11001111,
11010000, 11010001, 11010010, 11010011, 11010100, 11010101, 11010110, 11010111,
11011000, 11011001, 11011010, 11011011, 11011100, 11011101, 11011110, 11011111,
11100000, 11100001, 11100010, 11100011, 11100100, 11100101, 11100110, 11100111,
11101000, 11101001, 11101010, 11101011, 11101100, 11101101, 11101110, 11101111,
11110000, 11110001, 11110010, 11110011, 11110100, 11110101, 11110110, 11110111,
11111000, 11111001, 11111010, 11111011, 11111100, 11111101, 11111110, 11111111,
};

...
char value;
printf("%08d", BCD(value));

正直、この発想はなかった!
数値を文字に置き換えていく分だけオーバーヘッドはありますが、見た目の面白さは別格ですね!

with BCD conversion calculation

こちらもコメントでいただいた方法。

直前の定義が BCD のテーブルを定義する方法だったことに対し、こちらはテーブルを省略して「その場」で BCD 計算する方法。

#define BCD(c) 5 * (5 * (5 * (5 * (5 * (5 * (5 * (c & 128) + (c & 64))\\
 + (c & 32)) + (c & 16)) + (c & 8)) + (c & 4)) + (c & 2)) + (c & 1)

...
char value;
printf("%08d", BCD(value));

2 の倍数をビット抽出して、これら 2 の倍数を 5 倍して 10 進数に変換する。
ビットシフトしていないことから各ビットに畳み込まれ保存された 2 の乗数が 5 と掛け合わされて……。見事です。

マクロを大きくするだけで long long の 64 ビット 16 ビットまで拡張できるところも優れていますね!
(ジャンプテーブルで計算速度を稼ごうなどとしみったれたことを考えるようでは、甘いと痛感。デバッグ目的と割り切るなら、拡張性と合わせてこの方法がベスト)

訂正
上記計算マクロでは 16 ビット幅の整数まで対応可能です。(64 ビット整数までの表示には拡張不能でした)

BCD を使ったこの方法は 1 ビットを表現するために 4 ビットを消費するためです。 16 ビット幅のデータを表示するために 64 ビットの BCD が必要になります。そして printf が「組み込み」で対応しているのは 64 ビットまでです。

with octal coded binary

さらにコメントで、アイデアをいただきました。

コードは以下のとおり。
元データのビット間にふたつずつ 0 をパディングしたものを 8 進数表示すると、二進数の見た目になる、と言っています。

/* octal coded binary */
#define OCB(c) (c&1)|(c&2)<<2|(c&4)<<4|(c&8)<<6|(c&16)<<8|\
(c&32)<<10|(c&64)<<12|(c&128)<<14

...
char value;
printf("%08o", OCB(value));

たしかに 8 進数を使えば、ビット数は BCD の 4 ビットから 3 ビットに圧縮できます。つまり 64 ビット整数の中に 21 ビット幅までのデータを格納できる。

とはいえ使うとして 16 ビット幅まででしょう。
そして BCD とおなじで、このために書式指定文字列に l 修飾を入れるのは面倒ですね。

8 進数を使うとビットシフト演算だけで綺麗に書ける(つまり計算が早い)ところが良いですね!

関数で

別途関数を定義しておき、これを呼びだす方法。

static const char* binstr(unsigned char value) {
  static char* p = 0;
  if (!p) {
    static char buf[256 * 9] = {0};
    int i;
    for (i = 0; i < 256; ++i) {
      char *q = buf + i * 9;
      int j;
      for (j = 0; j < 8; ++j) {
        q[j] = i & (128 >> j) ? '1' : '0';
      }
    }
  }
  return p + value * 9;
}

...
char value;
printf("%s", binstr(value));

引数 value それぞれに対応する文字列を用意することがポイント。ケチって使い回すと、使い方次第でおかしなことが起きます。

毎回文字列を計算せずに済むよう一度だけ初期化するよう工夫しています。
GCC なら inline キーワードでその場に展開・埋め込むことで関数呼び出しのコストを削減できるかもしれません。

その場で

結局のところ、値を文字列に変換して表示していることに変わりはない。ということでその場で変換してもよい。(というより、その場での変換処理から、関数にしてみてマクロにしたら良い、までたどり着いたのですけれど)

char value; /* これが表示したい値として */
{
  char i, buf[9] = {0}; /* 文字配列をスタックに確保して */
  for (i = 0; i < 8; ++i) {
    buf[i] = value & (128 >> i) ? '1' : '0'; /* 順次 0, 1 文字に変換する */
  }
  printf("%s", buf); /* 文字列として出力 */
}

この方法の辛いところは、とにかく出力 printf に至るまでに余計なコードがたくさん入ること。
変数名がかぶらないようにする努力もバカになりません。