C言語編 第20章 数値の大きさと符号

この章の概要

この章の概要です。

int型の大きさ

これまで、整数を扱うために int型の変数を利用してきました。 正の数も負の数も、int型で扱うことができた訳ですが、上限や下限はあるのでしょうか?

扱える数の限界値は当然あります。 なぜ「当然」かといえば、コンピュータはメモリ上にデータを置いており、メモリは有限だからです。

しかしながら、int型の限界値が具体的に幾つなのか、明確には定義されていません。 実は、実行環境によって異なる可能性があるのです。

現在のところ、可能性として最も高いのは、int型が 32bit分のサイズを持つ環境です。 32bit ということは、232通りの数が扱えます。 これは、4,294,967,295 という非常に大きい数ですが、正と負の両方を保持できる必要があるので、この半分にあたる -2,147,483,647〜+2,147,483,647 が、32bit の int型が扱える範囲となります (繰り返しますが、これは int型が 32bit の場合であって、環境によって異なります)。

規格上、int型は -32767〜+32767 の範囲が扱えることは保証しています。 つまり、どんなに厳しい環境下でも、この範囲内の数は表現できるということになります。 そして、この範囲の数を表現するためには、最低でも 16bit必要ですから、int型の最低サイズは 16bit(2Byte) です。

サイズが曖昧なのは意外なことですが、こうなってくると、自分の使っている環境ではどうなのかを知る必要があります。 そこで以前から使っている sizeof演算子が利用できます。 次のプログラムを実行すると、自分の環境で int型がどれだけの大きさなのか分かります。

#include <stdio.h>

int main(void)
{
	printf( "int型の大きさは %uByte\n", sizeof(int) );

	return 0;
}

実行結果:

int型の大きさは 4Byte

sizeof演算子は、( )内に、型の名前や変数の名前を入れると、そのサイズを Byte単位で報告してくれます。 あるいは ( ) を省略して、スペースで区切って指定することもできますが、( ) を付ける方が一般的かと思います。

sizeof演算子の結果は、size_t型(→リファレンス)という型で、 これはこの後で説明する符号無し整数です。 符号無し整数を printf関数で出力する場合、"%d" ではなく "%u" を使います。

size_t型は、符号無し整数であることは保証されているものの、その大きさについての保証はなく、コンパイラによって異なります。 そのため、"%lu" などのフォーマット指定を行ったり、 unsigned int型にキャスト(第21章参照)したりする必要があるかも知れません。

C99 (printf関数、scanf関数での size_t型の扱い)

C99 では、printf関数や scanf関数のフォーマット指定に、"%zu" を使えるようになっており、 これで size_t型を取り扱えます。

#include <stdio.h>

int main(void)
{
	printf( "int型の大きさは %zuByte\n", sizeof(int) );

	return 0;
}

実行結果:

int型の大きさは 4Byte

"%zu" の指定は、VisualC++ 2013 では対応していませんが、 2015/2017 では対応されています。 clang 3.7 では使用できます。
VisualC++ 2013 の場合、"%Iu" を使えば代用できますが、これは Microsoft による拡張機能なので、 他のコンパイラでは動作しなかったり、別の意味が割り当てられていたりする可能性があります。 この拡張機能は、2015/2017 でも使用できます。

int型の符号の有無

int型は、正の数も負の数も扱えます。 このように、どちらも扱える整数を符号付き整数と呼びます。

符号付き整数であることを明確に示すために、signed というキーワードが用意されています。 次の3つの宣言は、いずれも同じ意味になります。

int num;
signed int num;
signed num;

意味が同じなので、わざわざ signed というキーワードを使うことはまずありません。 省略して、int とだけ書けばいいでしょう。

符号付き整数に対して、正の数しか扱えない符号無し整数というものもあります。 符号無し整数は、unsigned というキーワードを使って表現します。

unsigned int num;
unsigned num;

上のように、「int」は省略してしまっても構いません。

なお、符号付き整数でも符号無し整数でも、 0 は表現可能です。 また、signed や unsigned を付けても、変数の大きさ自体は変わりません。

わざわざ、負数を表現する能力を捨ててまで、符号無し整数を作る理由の1つは、表現できる正の数の範囲を増やすためです。 32bit の大きさの中で、正負両方を扱うと、-2,147,483,648〜+2,147,483,647 の範囲になりますが、 正の方向だけに集中すれば、0〜4,294,967,295 まで扱える訳です。

符号付き整数で、負数の方が、正数よりも表現できる範囲が広い(32bit ならば、負数は -2,147,483,648 まで、正数は +2,147,483,647 までというように、負数側が 1 多く表現できる) というような環境が多いですが、これは保証されたものではありません。 負数の方が表現範囲が広いのは、負数の表現に 2 の補数という形式を使っているからですが、 2 の補数で表現されているかどうかは、環境によって異なるのです。

また、負数になり得ないことを明確に示すために符号無し整数を使うこともあります。 例えば、年齢は負数になり得ないので、unsigned int型にするといったことが考えられます。
ただ、型の考え方を重要視するC言語では、signed と unsigned を混在させると、 至る所で(例えば、unsigned int型に変数に、signed int型の変数を代入するとき等)、 コンパイラが警告を発するようになります(コンパイラにもよります)。 あまり安易に unsigned を乱用せず、的確な場面でだけ使うことが望まれます(これは経験が必要だと思います)。

次の例のように、符号無し整数の定数を記述する場合は、数値の末尾に「U」か「u」を付けます

1234U;

これは整数接尾語と呼ばれる指示で、この後でもう1つ登場します。 整数接尾語のない整数定数は、int型であるものとして扱われます。


符号無しの整数を扱う場合、printf関数(⇒リファレンス)や scanf関数(⇒リファレンス)などが使うフォーマット指定子も変わります。 符号無しの 10進数を扱うには "%d" の代わりに "%u" を使います。8進数や 16進数は変わらず、"%o"、"%x"、"%X" を使います

short と long

前述しているように、int型の大きさは、環境によって異なりますが、 short long という新たなキーワードを導入することによって、 大きさの異なる整数型を作り出すことができます。

short int num;
long int num;

このように「int」の手前にキーワードを付け加えます。 あるいは、

short num;
long num;

このように「int」を省略してしまっても構いません。 また、signed や unsigned のいずれかと同時に使うことも可能です。

signed short num;
unsigned long num;

組み合わせが多いので全部は挙げませんが、signed と unsigned を同時に使えない、short と long を同時に使えないという点を除けば、 どう組み合わせても構いません。
また、signed/unsigned と short/long とのどちらの組みが前に来ても構いません。

short signed num;
long unsigned num;


名前の通り、short は小さめの、long は大きめの型になるのですが、やはり具体的なサイズは環境によって異なります。 int型が 32bit であるような多くの環境では、short型は 16bit に、long型は 32bit になります。

int型と long型の大きさが同じなのは変な話ですが、規格上定められているのは、具体的な大きさではなく、最低の大きさです。 次の表のように定められています( signed は省略しますが、符号指定無しの場合と同じです)。

最小サイズ 備考
int 16bit
short 16bit int型より大きいということはない
long 32bit int型より小さいということはない
unsigned int 16bit
unsigned short 16bit int型より大きいということはない
unsigned long 32bit int型より小さいということはない

なお、short型や long型よりも int型を優先的に使うべきです。 通常、int型は、プログラムを実行する環境で最も高速に処理できるようになっています。

short型を使う場面は、メモリの使用量を少なく抑えたい場合です。 先ほど書いたように、通常は int型が一番高速で効率よく処理されます。 「short型の方が小さいから速い」という期待は成り立たないのです。

long型を使う場面は、大きな数を扱う必要がある場合です。 環境によっては、int型の大きさは 16bit しかないため、この大きさで扱えないような大きな数を使いたい場合は、long型を使わなければなりません。 同じプログラムを、他の環境で使いまわすようなことを考えているのなら、この辺りのことには気をつけておかないといけません。

例えば、int型が 32bit の環境でプログラムを作成し、そのプログラムの出来が良かったので、他の環境でも使いまわすことにしたとします。 「他の環境」の方では、int型が 16bit しかなかったとすると、大きな数を扱っている箇所で問題が起こる訳です。

次の例のように、long型の定数を記述する場合は、数値の末尾に「L」か「l」を付けます

12345678L;

「U」と同様、これも整数接尾語の一種です。 「U」と同時に指定することもでき、その場合は「UL」でも「LU」でも構いません(もちろん、小文字でも構いません)。 両方を指定すれば、unsigned long型を意味します。
なお、「l」は、「1」と見間違いやすいので「L」を使う方が良いでしょう。


printf関数や scanf関数などのフォーマット指定ですが、short型や long型であることを明示するために、1文字付加する必要があります。 short型なら "%h" を、long型なら "%l" を使います。 これを、"%d"、"%u"、"%o"、"%x" といった指定と組み合わせます。例えば、

printf( "%hd\n", sn );    /* short型 */
printf( "%lu\n", uln );   /* unsigned long型 */

long long (C99)

C99 (long long)

C99 には、long よりも更に大きな値を表す long long型 が追加されました。 VisualC++ 2013/2015/2017、clang 3.7 のいずれでも使用できます。

使い方は、long とまったく同じです。

long long int num;
unsigned long long int num;

signed は省略できます。 また、signed/unsigned のいずれかと、long long とで、どちらが手前に来ても構いません。

long long型の最小サイズは 64bit と規定されています。

long long型の定数を記述する際には、数値の末尾に「LL」か「ll」を付けます。 unsigned を表す「U」「u」とともに使う場合は、どちらが先に来ても構いません。

printf関数や scanf関数などで、long long型の値を使う場合は "%ll" と組み合わせます。

printf( "%lld\n", 100LL );    /* long long型 */
printf( "%llu\n", 100ULL );   /* unsigned long long型 */

char型

char型は、1文字を表現するための型ですが、分類としては広義整数型と呼ばれ、実は整数なのです

char型の大きさは 1Byte です。 int型と違って、char型が 1Byte なのは確定しています。

しかし、1Byte が 8bit とは限らないところに問題が潜んでいます。そういう非常に特殊な環境のことは考えないことにします。 一応、limits.h に定義されている CHAR_BIT (⇒リファレンス) の値を調べれば、char型が何bit あるのかが分かります。

char型は、広義整数型であり、その大きさが 1Byte なのですから、小さな整数であれば表現可能です。 ただ、char型が負数を扱えるかどうかという点においては、環境によって異なります

char型には、signed および unsigned を付けることが可能であり、 これらを付けることによって、符号の有無を強制的に指示します。

char num;
signed char num;
unsigned char num;

これらの3つは全て異なる型です。 たとえ、char型が符号ありの環境であっても、char型と signed char型は異なる型なのです。 int型なら、signed があっても無くても同じ型ですが、char型はそうではありません。

方針としては、char型を整数のために使おうとしないことです。 素直に文字のためだけに使うようにすれば、符号の有無は関係しません。

どうしても、1Byte の整数が必要な場合、typedef(第26章参照)を利用するべきです。

C99規格では、char型を 1Byte の整数として使う場合、その値を printf関数で出力するとき、"%hh"フォーマットが使えます。


また、文字定数の型については注意が必要です。

#include <stdio.h>

int main(void)
{
	char a = 'A';

	printf( "%u\n", sizeof('A') );
	printf( "%u\n", sizeof(a) );

	return 0;
}

実行結果:

4
1

sizeof演算子に文字定数を与えてみると、4 が返されました。 実は、C言語では、文字定数の型は int型になります。 実験した環境では int型のサイズが 4Byte なのでこのような結果になりましたが、 int型が 2Byte の環境なら、sizeof('A') も 2 になります。

C++ では、文字定数の型は char型であり、sizeof('A') の結果は 1 になります。

浮動小数点型

浮動小数点数を扱える型には、float型double型long double型の 3つがあります。

float f;
double f;
long double f;

これらの型の具体的な大きさに関しては、何も既定されていませんが、 現在の多くの環境では、float型が 4Byte、double型が 8Byte、long double型が 10Byte です。

浮動小数点数が、具体的にどの程度の範囲の値を扱えるかは、これまた具体的には決まっていません。 それに加えて、浮動小数点数をコンピュータ上で表現する方法自体、結構難しいものですから、ここでは詳しく触れないことにします。

なお、float型で表現できる範囲は、必ず double型で表現でき、 double型で表現できる範囲は、必ず long double型で表現できることになっています

float型の定数には、末尾に「F」か「f」を付け、long double型の場合は「L」か「l」を付けます

0.1F
0.1L

これらは浮動小数点接尾語と呼び、 浮動小数点接尾語を付けない場合には、double型として扱われます

使い分けの方針ですが、まず、 long double型を使う機会はほとんどありません。 残った float型と double型ですが、基本的には double型を使って下さい。 float型の方が高速になるという話もありますが、「環境による」としか言いようがないです (ゲームプログラミングなどでは、依然として float型が強要されることが多いでしょうが、それは仕方のないところでしょうか)。
非常に膨大な量の変数が必要な場合に、メモリの節約のために float型を使うことはあるかも知れませんが、 そういう事情が無い限り、double型を使う方がトラブルが少ないと言えます。

printf関数で、float型や double型の値を使う場合は "%f" を使います。 long double型の場合は "%Lf" を使います。

printf( "%f\n", 123.45f );    /* float型 */
printf( "%f\n", 123.45 );     /* double型 */
printf( "%Lf\n", 123.45L );   /* long double型 */

一方、scanf関数では、float型には "%f"、double型には "%lf"、long double型には "%Lf" を使います。

scanf( "%f", &f );     /* float型 */
scanf( "%lf", &d );    /* double型 */
scanf( "%Lf", &ld );   /* long double型 */


また、浮動小数点数の表記方法にはもう1つ、科学的記数法というものがあります。 printf関数を使って、科学的記数法で出力するには、"%e" を使います(long double型のときは "%Le")

#include <stdio.h>

int main(void)
{
	double d1 = 3.141592;
	double d2 = 31.41592;
	double d3 = 0.3141592;

	printf( "%e\n", d1 );
	printf( "%e\n", d2 );
	printf( "%e\n", d3 );

	return 0;
}

実行結果:

3.141592e+000
3.141592e+001
3.141592e-001

表記の途中にある「+」は、指数が正であることを表しています。 指数が負なら「-」にします。 「e」は単なる区切りと考えて構いません。
つまり、「3.141592e+000」であれば、3.141592 を 100倍したものという意味になるので 3.141592 のことです。 「3.141592e+001」は、3.141592 を 101倍したものなので、31.41592 を、 「3.141592e-001」なら、3.141592 を 10-1倍(つまり 10分の1)したものなので、0.3141592 を表します。 要するに、「e」の後ろの数分だけ、小数点の位置を移動させればいいだけです。

科学的記数法は、普段のソースコード中で使っても構いません。 その際、表記の途中にある「+」は、指数が正であるのならば省略可能です。 また、先ほどの printf関数の出力結果では「+001」のようになっていますが、これは「+1」のように書いても構いません。
浮動小数点接尾語の付け方にも変化はありません。 次のように、末尾に付けるだけです。

0.3141592e1f
0.3141592e1L

C99 (16進数表記の浮動小数点定数)

C99 では、浮動小数点定数を 16進数で表記できるようになりました。 この方法で表記する際には、科学的記数法を使います。

#include <stdio.h>

int main(void)
{
	double d = 0x89ab.cdefP2;

	printf( "%a\n", d );
	printf( "%f\n", d );

	return 0;
}

実行結果:

0x8.9abcdefp+14
140975.217712

16進数での科学的記数法では、先頭に 16進数を表す「0x」を付ける他、 「e」を「p (またはP)」に変更します(「e」は 16進数の文字に含まているので区別が付かないので)。
また、printf関数や scanf関数に指定するフォーマット指定子は "%a" または "%A" です。 前者と後者の違いは、「p」の部分が小文字になるか大文字になるかだけです。

この記法は、VisualC++ 2013/2015/2017 では使用できません。 clang 3.7 では使用できます。

限界値

ここまで見てきて、型の大きさというものがいかに曖昧か分かって頂けたでしょうか? そこで、具体的な最小値と最大値を知る方法が用意されています。

次のプログラムを実行すると、各型の限界値が分かります。

#include <stdio.h>
#include <limits.h>
#include <float.h>

int main(void)
{
	printf( "          char型の最小値は %d、最大値は %d\n", CHAR_MIN, CHAR_MAX );
	printf( "   signed char型の最小値は %d、最大値は %d\n", SCHAR_MIN, SCHAR_MAX );
	printf( " unsigned char型の最小値は %u、最大値は %u\n", 0, UCHAR_MAX );
	printf( "\n" );
	printf( "           int型の最小値は %d、最大値は %d\n", INT_MIN, INT_MAX );
	printf( "  unsigned int型の最小値は %u、最大値は %u\n", 0, UINT_MAX );
	printf( "\n" );
	printf( "         short型の最小値は %d、最大値は %d\n", SHRT_MIN, SHRT_MAX );
	printf( "unsigned short型の最小値は %u、最大値は %u\n", 0, USHRT_MAX );
	printf( "\n" );
	printf( "          long型の最小値は %ld、最大値は %ld\n", LONG_MIN, LONG_MAX );
	printf( " unsigned long型の最小値は %lu、最大値は %lu\n", 0, ULONG_MAX );

	printf( "\n" );
	printf( "         float型の最小値は %e、最大値は %e\n", -FLT_MAX, FLT_MAX );
	printf( "\n" );
	printf( "        double型の最小値は %e、最大値は %e\n", -DBL_MAX, DBL_MAX );
	printf( "\n" );
	printf( "   long double型の最小値は %Le、最大値は %Le\n", -LDBL_MAX, LDBL_MAX );

	return 0;
}

実行結果:

          char型の最小値は -128、最大値は 127
   signed char型の最小値は -128、最大値は 127
 unsigned char型の最小値は 0、最大値は 255

           int型の最小値は -2147483648、最大値は 2147483647
  unsigned int型の最小値は 0、最大値は 4294967295

         short型の最小値は -32768、最大値は 32767
unsigned short型の最小値は 0、最大値は 65535

          long型の最小値は -2147483648、最大値は 2147483647
 unsigned long型の最小値は 0、最大値は 4294967295

         float型の最小値は -3.402823e+038、最大値は 3.402823e+038

        double型の最小値は -1.797693e+308、最大値は 1.797693e+308

   long double型の最小値は -1.797693e+308、最大値は 1.797693e+308

整数の型に関しての情報は limits.h を #include して、 浮動小数点数の型に関しての情報は float.h を #include して調べることができます。

例えば、CHAR_MIN (⇒リファレンス)は char型の最小値を、 CHAR_MAX (⇒リファレンス)は char型の最大値を表しています (繰り返しになりますが、char型に符号があるかどうかは環境によって異なります)。
unsigned でない型の場合には、同様に ***_MIN と ***_MAX という感じの名前で用意されています。 unsigned な型の場合には、***_MAX だけが用意されています(最小値は必ず 0 です)。

浮動小数点数の場合、***_MIN という名前の値も用意されていますが、これは「表現できる数の中で、0 に一番近い正の数」を表しており、 今回調べようとしている「最小値」とは意味が異なっています。 浮動小数点数の最小値は、最大値の符号を反転させたものと一致するので、***_MAX を調べます。

最大値の次は?

ところで、最大値の次( +1 )はいくつになるのでしょう? 次のプログラムで試してみましょう。

#include <stdio.h>

int main(void)
{
	short int num = 32767 + 1;
	unsigned short int unum = 65535 + 1;

	printf( "num = %d\n", num );
	printf( "unum = %d\n", unum );

	return 0;
}

実行結果:

num = -32768
unum = 0

警告が出るかも知れませんが、ここでは無視して下さい。 重要なのは実行結果です。

この結果はつまり、最小値に戻ってきている、ということです。 符号付きである num の方は、最小値は負数になるし、符号無しである unum の方は 0 になります。

最大値というのは、全てのビットが 1 になった状態だと考えられます(符号付きの場合は違いますが、符号無しならこうなります)。 16bit の大きさであれば、

1111111111111111    /* 符号無し16bit での最大値の表現 */

こうなり、ここに +1 すると、

10000000000000000

こうなります。しかし、そもそも 16bit分の大きさしかないので、17桁目はカットされてしまいます。 すると、

0000000000000000

このような状態になり、これは言うまでもなく、単なる「0」です。 つまり、最大値から最小値へと戻ってきてしまう訳です。

このような結果は、符号無し整数であれば保証されたものですが、符号あり整数では保証されません


練習問題

問題@ short型、int型、long型の大きさを確認するプログラムを作成して下さい。

問題A 次のプログラムは、あまり適切とは言えません。理由を考えて下さい。

#include <stdio.h>

int main(void)
{
	double i;

	for( i = 0.0; i < 1000.0; i += 1.5 ){
		printf( "%.1lf\n", i );
	}

	return 0;
}

問題B char型が符号付きか、符号無しかを判定するプログラムを作成して下さい。


解答ページはこちら

参考リンク

S・P・ハービソン3世とG・L・スティール・ジュニアのCリファレンスマニュアル 第5版
 -- 規格上の定義の確認に。

更新履歴

'2017/4/19 誤字を修正(singed -> signed)

'2017/3/25 VisualC++ 2017 に対応。

'2016/10/15 clang の対応バージョンを 3.7 に更新。

'2015/10/12 clang の対応バージョンを 3.4 に更新。

'2015/9/5 VisualC++ 2012 の対応終了。

'2015/8/18 VisualC++ 2010 の対応終了。

'2015/8/15 VisualC++ 2015 に対応。

'2015/7/7 科学的記数法の説明中に誤記があったのを修正。

'2014/10/18 clang 3.2 に対応。

'2014/2/1 VisualC++ 2013 に対応。

'2014/1/15 「C99 (printf関数、scanf関数での size_t型の扱い)」の項を追加。

'2014/1/14 VisualC++ 2008 の対応終了。

'2014/1/11 clang 3.0 に対応。
「char型」の項のサンプルで、sizeof演算子の結果を printf関数で出力する際に、%dフォーマット指定子を使っていたのを、%u に修正。

'2013/4/27 科学的記数法についての解説を「限界値」の項から「浮動小数点型」の項へ移動。
浮動小数点型」の項に、浮動小数点型に対する printf関数、scanf関数について追記。
C99 (16進数表記の浮動小数点定数)」の項を追加。

'2013/4/15 C99 の long long型についての項を追加。

'2013/2/16 文字定数の型について追記。

'2011/6/25 誤字の修正。

'2010/8/7 「int型の符号の有無」の説明文を一部修正。

'2009/6/29 新規作成。



前の章へ

次の章へ

C言語編のトップページへ

Programming Place Plus のトップページへ