読者です 読者をやめる 読者になる 読者になる

C言語での整数型間の変換

C言語の整数型には符号付き・符号なし、ビット列の大きさの2要素の組み合わせから種々の型があり、その間の変換規則にはややこしいものがある。
変換規則をまとめる。

以下の結果はC標準で規定されている規則であるかどうかまでは調べていない。
以下の結果は符号ありのビット表現が2の補数となっている環境下での結果だがC標準では符号ありのビット表現が2の補数であることを要請していない(らしい)。

なお、実行環境

解析のために以下のような関数を定義しておく。
(show_bit関数ではCPUがリトルエンディアンであることを仮定している。)

void show_bit(void *p, int len)
{
  unsigned char *s;
  int i, j;

  s = (unsigned char *)p;
  for (i = len - 1; i >= 0; i--) {
    for (j = 7; j >= 0; j--) {
      printf("%d", (*(s + i) >> j) & 1);
    }
    putc(' ', stdout);
  }
  putc('\n', stdout);
}


void show_short(short x)
{
  printf("%hd\n", x);
  show_bit(&x, sizeof(x));
}

void show_unsigned_short(unsigned short x)
{
  printf("%hu\n", x);
  show_bit(&x, sizeof(x));
}

void show_int(int x)
{
  printf("%d\n", x);
  show_bit(&x, sizeof(x));
}

void show_unsigned_int(unsigned int x)
{
  printf("%u\n", x);
  show_bit(&x, sizeof(x));
}

同一サイズ間の変換

例えば、int -> unsigned intunsigned short -> shortのような変換。

  • ビット列:変換前と変換後で同一
  • 表現される数値:変換前と変換後で変わり得る
  short x = 0x7fffU;
  unsigned short y = x;

  show_short(x);
  show_unsigned_short(y);
  show_unsigned_short(++y);
  x = y;
  show_short(x);

結果は

32767
01111111 11111111
32767
01111111 11111111
32768
10000000 00000000
-32768
10000000 00000000

サイズの拡大

符号なしから符号なしへの拡大

例えば、unsigned short -> unsigned intのような変換。

  • ビット列:拡大前のビット列の先頭に0のビットを付加したものが拡大後のビット列(zero extension)
  • 表現される数値:拡大前と拡大後で同一
  unsigned short sx = 0x0fffU;
  unsigned short sy = 0xf000U;
  unsigned int x = sx;
  unsigned int y = sy;

  show_unsigned_short(sx);
  show_unsigned_short(sy);
  show_unsigned_int(x);
  show_unsigned_int(y);

結果は

4095
00001111 11111111
61440
11110000 00000000
4095
00000000 00000000 00001111 11111111
61440
00000000 00000000 11110000 00000000

拡大後の型が表現する数値の範囲が拡大前の型が表現する数値の範囲を含んでいるので変なことは起こらない。

符号ありから符号ありへの拡大

例えば、short -> intのような変換。

  • ビット列:拡大前のビット列の先頭に拡大前のビット列の先頭ビット付加したものが拡大後のビット列(sign extension)
  • 表現される数値:拡大前・拡大後で同一
  short sx = 0x0fff;
  short sy = 0xf000;
  int x = sx;
  int y = sy;

  show_short(sx);
  show_short(sy);
  show_int(x);
  show_int(y);

結果は

4095
00001111 11111111
-4096
11110000 00000000
4095
00000000 00000000 00001111 11111111
-4096
11111111 11111111 11110000 00000000

これも、拡大後の型が表現する数値の範囲が拡大前の型が表現する数値の範囲を含んでいるので変なことは起こらない。

符号なしから符号ありへの拡大

例えば、unsigned short -> intのような変換。

  • ビット列:拡大前のビット列の先頭に0のビットを付加したものが拡大後のビット列(zero extension)
  • 表現される数値:拡大前・拡大後で同一
  unsigned short sx = 0x0fffU;
  unsigned short sy = 0xf000U;
  int x = sx;
  int y = sy;

  show_unsigned_short(sx);
  show_unsigned_short(sy);
  show_int(x);
  show_int(y);

結果は

4095
00001111 11111111
61440
11110000 00000000
4095
00000000 00000000 00001111 11111111
61440
00000000 00000000 11110000 00000000

これも、拡大後の型が表現する数値の範囲が拡大前の型が表現する数値の範囲を含んでいるので変なことは起こらない。

符号ありから符号なしへの拡大

例えば、short -> unsigned intのような変換。

  • ビット列:拡大前のビット列の先頭に拡大前のビット列の先頭ビット付加したものが拡大後のビット列(sign extension)
  • 表現される数値:拡大前・拡大後で変わり得る
  short sx = 0x0fff;
  short sy = 0xf000;
  unsigned int x = sx;
  unsigned int y = sy;

  show_short(sx);
  show_short(sy);
  show_unsigned_int(x);
  show_unsigned_int(y);
  show_unsigned_int((unsigned int)(int)sy);
  show_unsigned_int((unsigned int)(unsigned short)sy);

結果は

4095
00001111 11111111
-4096
11110000 00000000
4095
00000000 00000000 00001111 11111111
4294963200
11111111 11111111 11110000 00000000
4294963200
11111111 11111111 11110000 00000000
61440
00000000 00000000 11110000 00000000

結果から、先にサイズを変換し、その後、符号あり・符号なしを変換していることがわかる。(short -> int -> unsigned int)

これは変なことが起こる。

サイズの縮小

符号あり・符号なしに関わらず、

  • ビット列:縮小前のビット列の先頭のビットを切り捨てたものが縮小後のビット列
  • 表現される数値:縮小前と縮小後で変わり得る
  int x = 0x0fffffff;
  int y = 0xffffffff;
  short sx = x;
  unsigned short sy = y;

  show_int(x);
  show_int(y);
  show_short(sx);
  show_unsigned_short(sy);

結果は

268435455
00001111 11111111 11111111 11111111
-1
11111111 11111111 11111111 11111111
-1
11111111 11111111
65535
11111111 11111111