演習 2-9

無職の間にK&Rを再読。演習問題の解答をさらす。解く順番は適当。

演習 2-9
2の補数システムでは、x = x & (x - 1) により、x の最も右の1ビットが消える。なぜか説明せよ。この事実を使って、もっと速い bitcount プログラムを書け。


Exercise 2-9
In a two's complement number system, x &= (x - 1) deletes the rightmost 1-bit in x. Explain why. Use this observation to write a faster version of bitcount.


「説明せよ」と言われても困るが、1 を引くと最も右の1ビットが繰り下げられる。そうすると最も右の1ビットと1つ下位のビットだけが反転する。それを&演算すると、反転した2ビットだけが 0 になる。つまり、最も右の1ビットが消える。


説明がしづらいけど、例示すれば分かりやすい。

  0101 1100
- 0000 0001
= 0101 1010

この場合、最も右の 2 ビット目とその隣の 1 ビットが反転している。

  0101 1100
& 0101 1010
= 0101 1000

それを元の x と&演算すると 2 ビット目が消えてなくなる。


最初の bitcount は最も左のビットが出てくるまで繰り返すので、例えば 32ビットの最上位だけが 1 だった場合でも繰り返し回数は32回となる。一方、速いほうの bitcount では、 1 になっているビットの数だけ繰り返すので、上記の場合は繰り返しは1回ですむ。


しかし、美しいなぁ。
最初問題文見ただけでは意味が分からなかったけど、実際に試してみて「すげーっ!」てなった。
独力でこういう発想ができるようになりたい。

#include <stdio.h>

/* Answer */
int bitcount(unsigned x)
{
  int ret;
  
  for (ret = 0; x != 0; ret++)
    x = x & (x - 1);
  
  return ret;
}



void showbits(long num, int size);
void _showbits(long num, int remain);


int main(void)
{
  unsigned x = 0xABCD1234;

  showbits(x, sizeof(x));
  
  printf("%d\n", bitcount(x));
  
  return 0;
}



void showbits(long num, int size)
{
  _showbits(num, size * 2 - 1);
  putchar('\n');
}


void _showbits(long num, int remain)
{
  static const char bitpattern[16][5] = {
    "0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111",
    "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111",
  };
 
  if (remain > 0) {
    _showbits(num >> 4, remain - 1);
    putchar(',');
  }
  
  printf("%s", bitpattern[ num & 0x0F ]);
}