汎整数拡張

**汎整数拡張**（はんせいすうかくちょう、）とは、C言語およびC++において整数の扱いをする上で、ある条件のもとにその整数の型を格上げ、あるいは格下げする変換のことをいう。JIS X 3010:2003（C99相当）では「整数拡張」(integer promotion) と呼び、JIS X 3014:2003（C++03相当）では「汎整数昇格」(integral promotion) と呼ぶが、意味は変わらない。

## 格上げ・格下げ

格上げとは、より多くの値を表現できる型へ変換することで、要はより多くのビットを持つ型への変換である。格下げとは、現在の型で表現できる最大値を表現できない型へ変換することで、要はより少ないビットを持つ型への変換である。

例として、`char`型を`int`型に変換するのは格上げ、`int`型を`char`型に変換するのは格下げである。

### 格上げ

格上げをする際、変換後の値は、変換前と変換後の型および値の関係が以下のうちどれかである場合には、変換前の値が維持される。

*「符号付き→符号付き」
*「符号無し→符号付き」
*「符号付き→符号無し」であり、かつ変換前の値が正の数である
*「符号無し→符号無し」

ただし、

*「符号付き→符号無し」であり、かつ変換前の値が負の数である

という条件の場合に限り、変換後の値は


```c
/* 変換前の値をa、変換後の型をT型とする。T_MAXはT型の最大値。 */
(signed T)a + (1 + T_MAX)
```


となる。

### 格下げ

格下げをする際、変換前の値を`a`、変換後の型を`T`型とすると、変換後の値は、変換前と変換後の型および値の関係が以下のうちどちらかである場合には、変換前の値が維持される。

*「符号付き→符号付き」であり、かつ`a`が`signed T`で表現可能である
*「符号無し→符号付き」であり、かつ`a`が`signed T`で表現可能である

変換前の値が維持されない場合を以下に列挙する。

*「符号付き→符号付き」であり、かつ`a`が`signed T`で表現できない場合→処理系依存
*「符号無し→符号付き」であり、かつ`a`が`signed T`で表現できない場合→処理系依存
*「符号付き→符号無し」であり、かつ`a`が正の数である場合→
:
```c
a % (1 + T_MAX)
```

*「符号付き→符号無し」であり、かつ`a`が負の数である場合→
:
```c
(1 + T_MAX) - (-a % (1 + T_MAX))
```

*「符号無し→符号無し」である場合→
:
```c
a % (1 + T_MAX)
```


## 条件と変換結果

端的に言うと、`int`型あるいは`unsigned int`型を使用できる式の中では、`char`, `short`, `int`, `int`ビットフィールドの符号付き・符号無しにかかわらず、それら元の型の全ての値を`int`型で表現できるならばそれらの値を`int`型に、それ以外は`unsigned int`型に変換するということである。演算に先立って、すべてのオペランドの型が揃えられる。

例えば、`unsigned char`型同士の演算では、`unsigned char`型のまま演算するのではなく、`unsigned char`型の値をまず`int`型に格上げする変換が暗黙的に行なわれる。その後、途中の各演算は`int`型で行なわれる。これにより、各演算結果が`INT_MAX`を超えたり`INT_MIN`未満となったりしないかぎり、計算途中の値がオーバーフローして算術エラーが起こることはない。

同様に、`int`型での演算結果を`unsigned char`型に代入する際に格下げする変換が暗黙的に行なわれるが、最終結果が`UCHAR_MAX`を超えたり`0`未満となったりしないかぎり、オーバーフローが起こることはない。


```c
#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;
}
```

上記において、`unsigned char`型の値はすべて`int`型で表現可能であることから、先程示した汎整数拡張の規則が適用され、式における`uc1`と`uc2`の評価結果は、いったん`int`型に格上げされる。つまり、は暗黙的にとみなされる。

同様に、は暗黙的にとみなされる。

別の例を示す。

```c
#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)の代入式では、まず右辺式において、`unsigned char`型のオブジェクトである`uc`に、`int`型のリテラルである`1`を加算する演算を行なう。このとき、先程と同じように汎整数拡張が適用され、`uc`の評価結果は、いったん`int`型に格上げされる。この処理系では`char`型が8ビットであると仮定すれば、`UCHAR_MAX`の値は`255`になる。そしてこのとき、`uc`の評価結果である`255`は`unsigned char`型ではなく`int`型である。ゆえに、の演算結果は`int`型の`255 + 1`すなわち`256`となり、`si1`に代入される値は`256`である。

一方、上記(2)の代入式では、まず右辺式において、`unsigned char`型のオブジェクトである`uc`に前置インクリメント演算子が付いているから、以下のように`uc`に`1`を加算した値を`uc`に代入するという計算が行なわれる。


```c
uc = uc + 1; /* ++uc の解釈 */
```


上記における単純代入演算子`=`の右オペランドには先程と同じように汎整数拡張が適用されて、`uc`の評価結果`255`は`int`型に変換され、そしての演算結果は`int`型の`256`となる。その後、`int`型の`256`を`unsigned char`型の`uc`に代入する演算が行なわれる。しかしこのとき、`unsigned char`型では`256`を表現することはできないので、`int`型の`256`を、`unsigned char`型に格下げする変換が行なわれることになる。

今回の変換では、前述の「格下げ」項に示した《「符号付き→符号無し」であり、かつaが正の数である場合》の規則である


```c
a % (1 + T_MAX)
```


が適用される。ここで、`a`は`256`、`T_MAX`は`unsigned char`型の最大値つまり`UCHAR_MAX`で`255`となる。これらの値を上記の公式に代入してみると、
 

```c
256 % (1 + 255)
256 % 256
```


となり、256を256で割った余りは0になるので、最終的に`si2`に代入される値は`0`になる。つまり、情報の欠落が発生することになる。

## 汎整数拡張の特異性

上記「条件と変換結果」の項に示したサンプルの式(2)では、インクリメント演算子の意味と、各型で表現可能な値の範囲を知ってさえいれば、（汎整数拡張のルールについて詳しく知らずとも）結果は十分予測できる。しかし、汎整数拡張はコンパイル時に勝手に裏で行なわれる「暗黙の型変換」であるため、ルールを知らなければ意外なバグの原因となる場合がある。


```c
#include <stdio.h>

int main(void) {
    int si = -1;
    unsigned int ui = 1;
    printf("%d\n", si < ui);
    return 0;
}
```


上記の例では、`-1`のほうが`1`より小さいことから、一見しての比較結果は真 (`1`) となるように見えるが、実際には汎整数拡張により、`si`のほうが`unsigned int`に変換され、`-1`が`UINT_MAX`に変換されることによって、比較結果は偽 (`0`) となる。とはいえ、このような例（符号付き型と符号無し型の比較）は典型的なプログラミングミスであり、通例コンパイラが警告を発する対象となる。

具体的な解決策としては、式の中で用いる変数の型を揃える、できるかぎり表現可能な値の範囲の広い型を使用する、といったことが挙げられる。

また、処理系により整数型のビット数が異なることがあるので、あるソースコードをそのまま別の処理系で動作させる際、汎整数拡張により、移植前の処理系では起こり得なかったバグが急に発生するというケースもある。この場合は、整数型のビット数に依存しない、移植性の高いソースコードを書くということが何よりの解決策となる。

## 他の言語
C/C++以外の言語にも、整数型および浮動小数点数型を包括した、類似の暗黙的な型昇格ルールが存在する。例えばJavaではnumeric promotionと呼ばれている。C#ではtype promotionと呼ばれている。暗黙の型昇格により、異なる型同士の演算はいったん上位の型に変換されてから実行される。ここでは簡単に述べるにとどめる。詳細はそれぞれの言語規格を参照されたい。

Javaは各整数型のビット数が規格で厳密に定められており、符号無し整数型をサポートせず、また暗黙の拡大変換 (widening conversion) はサポートするものの、暗黙の縮小変換 (narrowing conversion) はサポートしない。このため、C/C++よりもプログラミングミスが起こりにくくなっている。

```java
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同様に暗黙の縮小変換はサポートしない。

```csharp
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`と32ビット符号無し整数型`uint`の比較は、いったん64ビット符号付き整数型`long`に変換されてから実行される。64ビット符号付き整数型`long`と64ビット符号無し整数型`ulong`の比較はサポートされず、コンパイルエラーになる。

```csharp
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#など、暗黙の型昇格を許さず、異なる型同士の演算には必ず明示的な変換が事前に必要となる言語もある。

```fsharp
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
```


## 脚注

### 注釈

### 出典

## 関連項目
* C言語
* C++
* 整数型
* 型変換

## 外部リンク
* INT02-C. 整数変換のルールを理解する