C言語基礎|整数拡張と通常の算術型変換

小さい型のまま計算してるつもりが、いつの間にか型が変わってる!―Cの型変換ルールを“表でスッキリ”整理しよう。

Cの計算は「型変換」が勝手に入るのがふつう

C言語の式は、見た目どおりに計算してくれることも多いんですが、実はその裏で

  • 小さい整数型が自動で大きい型に持ち上げられる(整数拡張)
  • さらに、演算のために左右の型をそろえる(通常の算術型変換)

という“型変換の2段階”がほぼ毎回起きます。

これを知らないと、たとえば

  • unsigned と signed を混ぜたら結果が巨大な数になる。
  • char や short を計算したら、結果が int として扱われていた。
  • 浮動小数点を int に直したら小数部が消えた。

みたいな「えっ?」が起こります。ここを、表と図でしっかり整えていきますね。

まずは全体像:式の型はこう決まる

図:式が評価されるときの型変換の流れ

整数拡張:小さい整数型はまず int か unsigned int になる

何が対象?

「int 型もしくは unsigned int 型を使ってよい式の中」では、次のような型がまず拡張されます。

  • _Bool
  • char / signed char / unsigned char
  • short / unsigned short
  • int より小さい整数型の式
  • int / unsigned int のビットフィールド(幅が int 未満など)

どう拡張される?

ポイントはこれだけです。

  • 元の型の全ての値が int に収まるなら int
  • 収まらないなら unsigned int

整数拡張の決め方(超重要)

元の型(例)判定拡張後
signed char / shortだいたい int に収まるint
unsigned char(0〜255)多くの処理系で int に収まるint
unsigned short(0〜65535)int が 16bit で収まらない場合があるunsigned int になり得る
_Bool0 or 1 なので必ず int に収まるint

表の説明
多くの現代環境では int が 32bit なので、unsigned char や unsigned short もだいたい int になります。
ただし「処理系による」ので、“最終的な型はルールで判定する”のが安全です。

通常の算術型変換:演算の前に左右を同じ型にそろえる

算術型(整数型・浮動小数点型)をオペランドに取る多くの演算子(+ - * / % など)は、同じ手順で型をそろえてから計算します。これが 通常の算術型変換 です。

まずは浮動小数点が優先

共通の実数型を決めるルール

条件行われる変換結果の型
どちらかが long doubleもう片方も long double へlong double
それ以外で、どちらかが doubleもう片方も double へdouble
それ以外で、どちらかが floatもう片方も float へfloat
それ以外次に整数どうしのルールへ(整数の結果型)

表の説明
混ざったら “より広い浮動小数点型” に寄せます。
たとえば int + double は int を double に変換してから計算し、結果も double です。

整数どうしの通常の算術型変換:signed と unsigned が混ざると要注意

浮動小数点が絡まないなら、ここからは整数どうしの調整です。
このとき、まず 整数拡張 を両方に行ってから、次の規則でそろえます。

整数どうしをそろえる規則(実務で一番事故るところ)

ルール条件変換のしかたありがちな現象
12つが同じ型変換しないそのまま
2両方とも signed または両方とも unsigned変換順位が低い方を高い方へshort と long なら short が long へ
3unsigned 側の順位が signed 側以上signed を unsigned の型へ負数が巨大な正数に化ける
4signed の型が unsigned の全値を表現できるunsigned を signed の型へたとえば long が unsigned int を全部表せる環境
5それ以外両方を signed 側に対応する unsigned 型へ結局 unsigned 寄りになる

まず押さえる:整数変換順位って何?

整数変換順位は、ざっくり言うと「型の強さ(優先度)」です。

  • 2つの整数型を同じ型にそろえるとき
    順位が低い方が高い方へ寄せられやすい
  • signed と unsigned が混ざるとき
    → 順位に加えて「符号の有無」ルールが絡んで、挙動がややこしくなる

ここを表でクリアにします。

整数変換順位の詳しい表(よく使う型を全部まとめ)

この表は「どの型がどの段にいるか」を整理したものです。
同じ段の中では、signed と unsigned は“順位としては同格”扱いになります(挙動は符号ルールで変わるけど、まず順位は同じ段だよ、という意味)。

整数変換順位(段ごと)

変換順位(低 → 高)型(signed 系)型(unsigned 系)よくある用途・特徴
1_Bool(なし)0/1 の真偽。式の中では整数拡張で int 側へ上がりやすい
2signed charunsigned char1バイト整数。文字コードや小さい数値の保持
3shortunsigned short2バイトのことが多い。古い環境や省メモリで登場
4intunsigned intいちばん基本。算術演算の中心になりやすい
5longunsigned longポインタサイズに近いことが多い環境もある
6long longunsigned long long64bit 整数の代表格。大きいカウンタやIDなど

この表の説明

  • 「順位」は型変換の“力関係”を表します。
  • 同じ段(例:int と unsigned int)は順位同じ。
  • ただし signed/unsigned が混ざるときは、順位だけでなく符号ルールで結果が変わります。

図:整数変換順位の階段

図の説明
上に行くほど“強い型”。弱い型は強い型にそろえられやすいです。

値の型変換:代入やキャストで起きる“値の変化”も押さえよう

ここは「式の型」ではなく「値そのものがどう変わるか」です。

符号付き整数型と符号無し整数型の変換

整数型 → 整数型の変換結果

変換変換先で表現可能?結果
整数 → 別の整数可能値は変わらない
整数 → unsigned(表現不可)不可最大値+1 で割った剰余(いわゆる modulo)
整数 → signed(表現不可)不可処理系定義の値になるか、処理系定義のシグナル生成

表の説明
unsigned への変換は “ぐるっと回る” ルールが明確。
signed への変換は環境依存が出るので、危ない変換は避けるのが基本です。

浮動小数点型と整数型の相互変換

浮動小数点 → 整数

  • 小数部は切り捨て
  • 整数部が変換先の範囲に入らないと 動作は定義されない

浮動小数点 → 整数の要点

変換結果
12.9 → int小数部切り捨て12
-3.1 → int小数部切り捨て-3
1e100 → int範囲外動作は定義されない

整数 → 浮動小数点

  • 正確に表現できるなら値は変わらない
  • 正確に表現できないなら、近い表現可能値へ(どちらに寄せるかは処理系定義)

浮動小数点型どうしの変換

float / double / long double の変換

変換状況結果
float → double / long double拡張値は変わらない
double → float精度が足りない場合あり近い表現可能値へ
long double → double / float範囲や精度で丸めが起きることあり近い表現可能値へ
範囲外への変換範囲外動作は定義されない

よくあるハマりポイントを“短く”表で確認

見た目は普通なのに結果が変わりやすい例

起きていること対策
負の signed + unsignedsigned が unsigned に変換されることがあるunsigned を混ぜる前に型をそろえる
char や short の計算まず int へ整数拡張される意図する型で計算するなら明示的にキャスト
float を繰り返し制御に使う誤差のせいで比較が崩れるループは int で回して最後に割る
double → int小数部切り捨て、範囲外は定義されない範囲チェックを入れる