整数型は単純なようで処理系によって型のサイズが異なったり、演算では拡張されたりなどやっかいな性質をもっています。これらの性質を正しく理解したうえで使用しないと、原因の特定が困難な問題を引き起こす恐れがあります。浮動小数点数型よりも扱いに気を配らないといけないのが整数型なのです。
整数型の範囲
整数型の表現できる範囲は処理系によって異なります。C言語では各整数型が表現できる最低の値を規定しており、少なくとも下記表に示す値を表現することができます。なお表に出てくる「CHAR_MIN」「CHAR_MAX」などは、<limits.h>で定義されているマクロ定数です。
型 | 最小値 | 最大値 | ||
---|---|---|---|---|
char | CHAR_MIN | 0 | CHAR_MAX | UCHAR_MAX |
SCHAR_MIN | SCHAR_MAX | |||
signed char | SCHAR_MIN | -127 | SCHAR_MAX | +127 |
signed short int | SHRT_MIN | -32767 | SHRT_MAX | +32767 |
signed int | INT_MIN | -32767 | INT_MAX | +32767 |
signed long int | LONG_MIN | -2147483647 | LONG_MAX | +2147483647 |
signed long long | LLONG_MIN | -9223372036854775807 | LLONG_MAX | +9223372036854775807 |
unsigned char | 0 | UCHAR_MAX | 255 | |
unsigned short int | 0 | USHRT_MAX | 65535 | |
unsigned int | 0 | UINT_MAX | 65535 | |
unsigned long int | 0 | ULONG_MAX | 4294967295 | |
unsigned long long | 0 | ULLONG_MAX | 18446744073709551615 |
- 符号付き整数の扱いは処理系依存です。1の補数の場合は「-127~」や「-32767~」、2の補数の場合は「-128~」や「-32768~」となります。なお次期C言語の規格(C2x)では、符号付き整数を2の補数で表すように規定される計画です。C++は、C++20で規定済みです。
- 2の補数の処理系で負の最小値をdefineする場合は、
#define MIN_VALUE (-2147483648L)
ではなく、下記のようにしてください。
#define MIN_VALUE (-2147483647L – 1)
これはコンパイラーが「-」と「2147483648L」を分けて解釈するためです。
- 「short int」は「short」、「long int」は「long」と、「int」は省略して記述するのが一般的です。
- 一般的にshort型は2バイトで、int型やlong型のサイズは処理系依存であることが多いです。ただしchar型だけは1バイトであることが規定されています。
- C言語では、1バイトが8ビットであるとは規定されていません。8ビット以上であることとなっています。まず8ビットであると思いますが、使用している処理系のビット数がいくらであるかは<limits.h>の「CHAR_BIT」で確認できます。
- コンピューターの分野では、1バイトは必ずしも8ビットではありません。このため確実に8ビットであることを表す単位として「octet(オクテット)」というものがあり、特に通信分野で使用されています。
なお2の補数と1の補数の表現方法の違いについては、下記表を参照してください。
2の補数 | 1の補数 | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
値 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 同左 | |||||||
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | ||||||||
2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | ||||||||
… | ||||||||||||||||
126 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | ||||||||
127 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | ||||||||
-128 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||||||||
-127 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-126 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
… | ||||||||||||||||
-2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 |
-1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
- 1の補数の処理系では、偶数の最下位ビットが必ずしも0にはなりません。このため、下記のように偶数であるかを判定しようとした場合、処理系によっては誤動作する恐れがあります。
if ((a & 0x01) == 0)
型変換
型の異なる整数型に代入する場合の挙動は、次のようになります。 なお変換前の値をa、変換後の型をT、変換後の型の最大値をT_MAX、変換後の型の最小値をT_MINとします。
変換内容 | 変換結果 |
---|---|
符号付き ⇒ 符号付き | 変換前の値を維持する |
符号なし ⇒ 符号付き | 変換前の値を維持する |
符号付き(a≧0) ⇒ 符号なし | 変換前の値を維持する |
符号付き(a<0) ⇒ 符号なし | (signed T)a + ( 1 + T_MAX ) |
符号なし ⇒ 符号なし | 変換前の値を維持する |
変換内容 | 変換結果 |
---|---|
符号付き(T_MIN≦a≦T_MAX) ⇒ 符号付き | 変換前の値を維持する |
符号なし(a≦T_MAX) ⇒ 符号付き | 変換前の値を維持する |
符号付き(「a<T_MIN」or「a>T_MAX」) ⇒ 符号付き | 処理系依存 |
符号なし(a>T_MAX) ⇒ 符号付き | 処理系依存 |
符号付き(a≧0) ⇒ 符号なし | a % ( 1 + T_MAX ) |
符号付き(a<0) ⇒ 符号なし | ( 1 + T_MAX ) – ( -a % ( 1 + T_MAX ) ) |
符号なし ⇒ 符号なし | a % ( 1 + T_MAX ) |
- 「符号付き(a<0) ⇒ 符号なし」ルールを利用することで、処理系に依存せずすべてのビットを1にした値を得られます。
// long型が32ビットの場合 “a = 0xFFFFFFFF” となる
// long型が64ビットの場合 “a = 0xFFFFFFFFFFFFFFFF” となる
unsigned long a = -1;
整数拡張
int型より小さな整数型は、演算時に整数拡張されます。元の型の値をint型で表現できる場合はint型に、それ以外の場合はunsigned int型に変換します。
signed char cresult, c1, c2, c3;
c1 = 100;
c2 = 3;
c3 = 4;
// signed charは300を表せないが、intに変換されるため300を保持でき"300/4=75"となる
cresult = c1 * c2 / c3;
整数リテラル
10進数以外の整数リテラル(ソースコード内に直接記述した定数)を表現する方法として、下記表のものが用意されています。
基数 | 先頭に付加する文字 | 例 |
---|---|---|
2進 | 0b | 0b01010101 |
8進 | 0 | 01234 |
16進 | 0x | 0x1234 |
- 先頭に「0」をつけると8進数となります。位置揃えで先頭に0を付加しないように注意してください。
- 2進数を「0b」で表現できるのは、gccなど一部の処理系のみです。しかし次期C言語の規格(C2x)では「0b」と規定される計画なので、記載しておきます。C++は、C++14で「0b」を規定済みです。
- 次期C言語の規格(C2x)では「0b」が規定されると同時に、printf系やscanf系に「%b」指定子が追加される計画です。
幅指定整数型
上で述べたとおり、C言語では整数型のサイズが処理系依存です。これを知らずに既存のソースコードを移植すると、思わぬ痛手を被る恐れがあります。この対策としてC言語ではサイズを指定した整数型、幅指定整数型というものが用意されています。
幅指定整数型を使用するには<stdint.h>をインクルードします。そしてintやlongなどの代わりにint8_tやint16_tで変数を宣言するだけです。符号なし整数「unsigned」を「u」のみで表せられるため使わない手はないでしょう。
#include <stdint.h>
int8_t i8; // 符号あり1バイト
int16_t i16; // 符号あり2バイト
int32_t i32; // 符号あり4バイト
int64_t i64; // 符号あり8バイト
uint8_t u8; // 符号なし1バイト
uint16_t u16; // 符号なし2バイト
uint32_t u32; // 符号なし4バイト
uint64_t u64; // 符号なし8バイト
- 文字列を取り扱う場合に限り、char型を使用しても構いません。これは<string.h>など古くからある関数がchar型を要求するためです。
- 1、2、4バイト変数を「S1」「S2」「S4」や「S8」「S16」「S32」と表すこともあります。しかし移植性を考えると、標準ライブラリーで定義された型を用いることを推奨します。
その他注意点
整数型どうしの演算結果は整数型となります。これが何を表しているかというと、整数型どうしの割り算を行うと小数点以下が切り捨てられる、という事象が発生するのです。
int a = 3;
int b = 2;
int c;
// c = 3 / 2 = 1.5 = 1
c = a / b;
上記は分かりやすい例で、この程度であれば不具合はそうそう起きません。しかし下記の例はどうでしょうか?左式がdouble型のため「c = 1.5」になりそうに見えます。ところが「整数型どうしの演算結果は整数型になる」というのは一演算毎に発生するのです。したがって下記例の結果は「c = 1」となります。
int a = 3;
int b = 2;
double c;
// c = 3 / 2 = 1.5 = 1
c = a / b;
これを「c = 1」ではなく「c = 1.5」とするためには、キャストという操作を行う必要があります。キャストを行うと、任意の型に変換することができます。したがってキャストを行うことで整数型どうしの演算ではなく、整数型と浮動小数点数型の演算に変えてしまえばいいのです。
int a = 3;
int b = 2;
double c;
// c = (double)3 / 2 = 3.0 / 2 = 1.5
c = (double)a / b;
- 意図しない整数型どうしの割り算による演算誤差は、よく起こります。原因の分からない誤差が発生する場合は、整数型どうしの割り算が発生していないか確認してみてください。
- 意図しない整数型どうしの割り算を防ぐためには、グローバル変数の使用を避けること、関数の長さを短くすることなどの対策が考えられます。変数宣言と演算式をスクロールなしで見渡すことができれば、型の誤りは起きにくくなります。