汎整数拡張

汎整数拡張（はんせいすうかくちょう、）とは、C言語およびC++において整数の扱いをする上で、ある条件のもとにその整数の型を格上げ、あるいは格下げする変換のことをいう。JIS X 3010:2003（C99相当）では「整数拡張」(integer promotion) と呼び、JIS X 3014:2003（C++03相当）では「汎整数昇格」(integral promotion) と呼ぶが、意味は変わらない。
格上げ・格下げ.
格上げとは、より多くの値を表現できる型へ変換することで、要はより多くのビットを持つ型への変換である。格下げとは、現在の型で表現できる最大値を表現できない型へ変換することで、要はより少ないビットを持つ型への変換である。
例として、codice_1型をcodice_2型に変換するのは格上げ、codice_2型をcodice_1型に変換するのは格下げである。
格上げ.
格上げをする際、変換後の値は、変換前と変換後の型および値の関係が以下のうちどれかである場合には、変換前の値が維持される。
ただし、
という条件の場合に限り、変換後の値は
/* 変換前の値をa、変換後の型をT型とする。T_MAXはT型の最大値。 */
となる。
格下げ.
格下げをする際、変換前の値をcodice_5、変換後の型をcodice_6型とすると、変換後の値は、変換前と変換後の型および値の関係が以下のうちどちらかである場合には、変換前の値が維持される。
変換前の値が維持されない場合を以下に列挙する。
a % (1 + T_MAX)
a % (1 + T_MAX)
条件と変換結果.
端的に言うと、codice_2型あるいはcodice_18型を使用できる式の中では、codice_1, codice_20, codice_2, codice_2ビットフィールドの符号付き・符号無しにかかわらず、それら元の型の全ての値をcodice_2型で表現できるならばそれらの値をcodice_2型に、それ以外はcodice_18型に変換するということである。演算に先立って、すべてのオペランドの型が揃えられる。
例えば、codice_26型同士の演算では、codice_26型のまま演算するのではなく、codice_26型の値をまずcodice_2型に格上げする変換が暗黙的に行なわれる。その後、途中の各演算はcodice_2型で行なわれる。これにより、各演算結果がcodice_31を超えたりcodice_32未満となったりしないかぎり、計算途中の値がオーバーフローして算術エラーが起こることはない。
同様に、codice_2型での演算結果をcodice_26型に代入する際に格下げする変換が暗黙的に行なわれるが、最終結果がcodice_35を超えたりcodice_36未満となったりしないかぎり、オーバーフローが起こることはない。
int main(void) {
 unsigned char uc1 = 100;
 unsigned char uc2 = 100;
 unsigned char uc3 = 0;
 unsigned char uc4 = 200;
 int si = uc1 * uc2; /* 10000 */
 unsigned char uc5 = -(uc1 * uc2) / (uc3 - uc4); /* 50 */
 printf("INT_MIN = %+d\n", INT_MIN);
 printf("INT_MAX = %+d\n", INT_MAX);
 printf("UCHAR_MAX = %d\n", UCHAR_MAX);
 printf("si = %d\n", si);
 printf("uc5 = %d\n", uc5);
 return 0;
上記において、codice_26型の値はすべてcodice_2型で表現可能であることから、先程示した汎整数拡張の規則が適用され、式におけるcodice_39とcodice_40の評価結果は、いったんcodice_2型に格上げされる。つまり、は暗黙的にとみなされる。
同様に、は暗黙的にとみなされる。
別の例を示す。
int main(void) {
 unsigned char uc = UCHAR_MAX; /* UCHAR_MAX は unsigned char 型で表現できる最大値 */
 int si1 = uc + 1; /* (1) */
 int si2 = ++uc; /* (2) */
 printf("si1 = %d\n", si1);
 printf("si2 = %d\n", si2);
 return 0;
上記(1)の代入式では、まず右辺式において、codice_26型のオブジェクトであるcodice_43に、codice_2型のリテラルであるcodice_45を加算する演算を行なう。このとき、先程と同じように汎整数拡張が適用され、codice_43の評価結果は、いったんcodice_2型に格上げされる。この処理系ではcodice_1型が8ビットであると仮定すれば、codice_35の値はcodice_50になる。そしてこのとき、codice_43の評価結果であるcodice_50はcodice_26型ではなくcodice_2型である。ゆえに、の演算結果はcodice_2型のcodice_56すなわちcodice_57となり、codice_58に代入される値はcodice_57である。
一方、上記(2)の代入式では、まず右辺式において、codice_26型のオブジェクトであるcodice_43に前置インクリメント演算子が付いているから、以下のようにcodice_43にcodice_45を加算した値をcodice_43に代入するという計算が行なわれる。
uc = uc + 1; /* ++uc の解釈 */
上記における単純代入演算子codice_65の右オペランドには先程と同じように汎整数拡張が適用されて、codice_43の評価結果codice_50はcodice_2型に変換され、そしての演算結果はcodice_2型のcodice_57となる。その後、codice_2型のcodice_57をcodice_26型のcodice_43に代入する演算が行なわれる。しかしこのとき、codice_26型ではcodice_57を表現することはできないので、codice_2型のcodice_57を、codice_26型に格下げする変換が行なわれることになる。
今回の変換では、前述の「格下げ」項に示した《「符号付き→符号無し」であり、かつaが正の数である場合》の規則である
a % (1 + T_MAX)
が適用される。ここで、codice_5はcodice_57、codice_82はcodice_26型の最大値つまりcodice_35でcodice_50となる。これらの値を上記の公式に代入してみると、
256 % (1 + 255)
256 % 256
となり、256を256で割った余りは0になるので、最終的にcodice_86に代入される値はcodice_36になる。つまり、情報の欠落が発生することになる。
汎整数拡張の特異性.
上記「条件と変換結果」の項に示したサンプルの式(2)では、インクリメント演算子の意味と、各型で表現可能な値の範囲を知ってさえいれば、（汎整数拡張のルールについて詳しく知らずとも）結果は十分予測できる。しかし、汎整数拡張はコンパイル時に勝手に裏で行なわれる「暗黙の型変換」であるため、ルールを知らなければ意外なバグの原因となる場合がある。
int main(void) {
 int si = -1;
 unsigned int ui = 1;
 printf("%d\n", si < ui);
 return 0;
上記の例では、codice_88のほうがcodice_45より小さいことから、一見しての比較結果は真 (codice_45) となるように見えるが、実際には汎整数拡張により、codice_91のほうがcodice_18に変換され、codice_88がcodice_94に変換されることによって、比較結果は偽 (codice_36) となる。とはいえ、このような例（符号付き型と符号無し型の比較）は典型的なプログラミングミスであり、通例コンパイラが警告を発する対象となる。
具体的な解決策としては、式の中で用いる変数の型を揃える、できるかぎり表現可能な値の範囲の広い型を使用する、といったことが挙げられる。
また、処理系により整数型のビット数が異なることがあるので、あるソースコードをそのまま別の処理系で動作させる際、汎整数拡張により、移植前の処理系では起こり得なかったバグが急に発生するというケースもある。この場合は、整数型のビット数に依存しない、移植性の高いソースコードを書くということが何よりの解決策となる。
他の言語.
C/C++以外の言語にも、整数型および浮動小数点数型を包括した、類似の暗黙的な型昇格ルールが存在する。例えばJavaではnumeric promotionと呼ばれている。C#ではtype promotionと呼ばれている。暗黙の型昇格により、異なる型同士の演算はいったん上位の型に変換されてから実行される。ここでは簡単に述べるにとどめる。詳細はそれぞれの言語規格を参照されたい。
Javaは各整数型のビット数が規格で厳密に定められており、符号無し整数型をサポートせず、また暗黙の拡大変換 (widening conversion) はサポートするものの、暗黙の縮小変換 (narrowing conversion) はサポートしない。このため、C/C++よりもプログラミングミスが起こりにくくなっている。
byte sb1 = 100;
byte sb2 = 100;
byte sb3 = -100;
byte sb4 = 100;
int si = sb1 * sb2;
//byte sb5 = -(sb1 * sb2) / (sb3 - sb4); // Compile Error.
byte sb5 = (byte)(-(sb1 * sb2) / (sb3 - sb4)); // OK.
System.out.println("si = " + si);
System.out.println("sb5 = " + sb5);
C#は符号無し整数型をサポートするが、Java同様に暗黙の縮小変換はサポートしない。
byte ub1 = 100;
byte ub2 = 100;
byte ub3 = 0;
byte ub4 = 200;
int si = ub1 * ub2;
//byte ub5 = -(ub1 * ub2) / (ub3 - ub4); // Compile Error.
byte ub5 = (byte)(-(ub1 * ub2) / (ub3 - ub4)); // OK.
System.Console.WriteLine("si = " + si);
System.Console.WriteLine("ub5 = " + ub5);
C#では符号付き整数型と符号無し整数型の比較結果も、C/C++と違って直感的で自然なものとなる。ただし、これはより上位の符号付き整数型に型昇格されて演算されているからであり、例えば32ビット符号付き整数型codice_2と32ビット符号無し整数型codice_97の比較は、いったん64ビット符号付き整数型codice_98に変換されてから実行される。64ビット符号付き整数型codice_98と64ビット符号無し整数型codice_100の比較はサポートされず、コンパイルエラーになる。
int si = -1;
uint ui = 1;
System.Console.WriteLine(si < ui); // True
long sl = -1L;
ulong ul = 1UL;
System.Console.WriteLine(sl < ul); // Compile Error.
一方F#など、暗黙の型昇格を許さず、異なる型同士の演算には必ず明示的な変換が事前に必要となる言語もある。
let x : int = 100
//let y : sbyte = -1 // Compile Error.
let y : sbyte = -1y // OK.
//let z : int = x + y // Compile Error.
let z : int = x + int y // OK.
//let w : int = y // Compile Error.
let w : int = int y // OK.
printfn "z = %d" z
printfn "w = %d" w