汎整数拡張汎整数拡張(はんせいすうかくちょう、英: integral promotion)[1]とは、C言語およびC++において整数の扱いをする上で、ある条件のもとにその整数の型を格上げ、あるいは格下げする変換のことをいう。JIS X 3010:2003(C99相当)では「整数拡張」(integer promotion)[2] と呼び、JIS X 3014:2003(C++03相当)では「汎整数昇格」(integral promotion)[3] と呼ぶが、意味は変わらない。 格上げ・格下げ格上げとは、より多くの値を表現できる型へ変換することで、要はより多くのビットを持つ型への変換である。格下げとは、現在の型で表現できる最大値を表現できない型へ変換することで、要はより少ないビットを持つ型への変換である。 例として、 格上げ格上げをする際、変換後の値は、変換前と変換後の型および値の関係が以下のうちどれかである場合には、変換前の値が維持される。
ただし、
という条件の場合に限り、変換後の値は /* 変換前の値をa、変換後の型をT型とする。T_MAXはT型の最大値。 */
(signed T)a + (1 + T_MAX)
となる。 格下げ格下げをする際、変換前の値を
変換前の値が維持されない場合を以下に列挙する。
条件と変換結果端的に言うと、 例えば、 同様に、 #include <stdio.h>
#include <limits.h>
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;
}
上記において、 同様に、 別の例を示す。 #include <stdio.h>
#include <limits.h>
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)の代入式では、まず右辺式 一方、上記(2)の代入式では、まず右辺式 uc = uc + 1; /* ++uc の解釈 */
上記における単純代入演算子 今回の変換では、前述の「格下げ」項に示した《「符号付き→符号無し」であり、かつaが正の数である場合》の規則である a % (1 + T_MAX)
が適用される。ここで、 256 % (1 + 255)
256 % 256
となり、256を256で割った余りは0になるので、最終的に 汎整数拡張の特異性上記「条件と変換結果」の項に示したサンプルの式(2)では、インクリメント演算子の意味と、各型で表現可能な値の範囲を知ってさえいれば、(汎整数拡張のルールについて詳しく知らずとも)結果は十分予測できる。しかし、汎整数拡張はコンパイル時に勝手に裏で行なわれる「暗黙の型変換」であるため、ルールを知らなければ意外なバグの原因となる場合がある。 #include <stdio.h>
int main(void) {
int si = -1;
unsigned int ui = 1;
printf("%d\n", si < ui);
return 0;
}
上記の例では、 具体的な解決策としては、式の中で用いる変数の型を揃える、できるかぎり表現可能な値の範囲の広い型を使用する、といったことが挙げられる。 また、処理系により整数型のビット数が異なることがあるので、あるソースコードをそのまま別の処理系で動作させる際、汎整数拡張により、移植前の処理系では起こり得なかったバグが急に発生するというケースもある。この場合は、整数型のビット数に依存しない、移植性の高いソースコードを書くということが何よりの解決策となる。 他の言語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ビット符号付き整数型 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
脚注注釈出典関連項目外部リンク |