C言語で最初に覚えるべき基本が四則演算と剰余演算です。
整数と浮動小数点で挙動が異なり、特に割り算は落とし穴が多い分野です。
本記事では、演算子の基本から整数除算と浮動小数点除算の違い、%の重要ポイント、優先順位、よくあるミスまで、動作が分かるサンプルコード付きで丁寧に解説します。
基本の演算子
演算子の概要
C言語の演算子は直感的に見えますが、型や文脈によって結果が変わることがあります。
ここでは整数を使い、基本の動作を確認します。
+(加算)、-(減算)、*(乗算)、/(除算)、%(剰余)
- 加算、減算、乗算は直感どおりの結果になります。
- 除算は整数同士の場合、小数部分が切り捨てられます(例:
7/2 → 3
)。 - 剰余は割り算の余りを返します。整数型同士でのみ使用できます。
以下のサンプルで基本の動作をまとめて確認します。
#include <stdio.h>
int main(void) {
int a = 3;
int b = 2;
// 基本の四則演算と剰余
printf("a + b = %d\n", a + b); // 3 + 2 → 5
printf("a - b = %d\n", a - b); // 3 - 2 → 1
printf("a * b = %d\n", a * b); // 3 * 2 → 6
// 整数同士の除算は小数部が切り捨てられる
printf("a / b = %d\n", a / b); // 3 / 2 → 1
// 剰余(余り)。整数型同士でのみ使用可能
printf("a %% b = %d\n", a % b); // 3 % 2 → 1
return 0;
}
a + b = 5
a - b = 1
a * b = 6
a / b = 1
a % b = 1
整数除算と浮動小数点除算
整数/整数は小数部切り捨て
整数同士の除算は常に小数部を捨て、結果は整数になります。
C99以降では「0方向への丸め(切り捨て)」です。
どちらかが浮動小数点なら浮動小数点除算
片方でも float
や double
であれば、小数点を含む結果が得られます。
キャストで意図的に浮動小数点除算へ誘導するのが定石です。
#include <stdio.h>
int main(void) {
int x = 7;
int y = 2;
// 整数同士 → 小数部は切り捨てられ、その後にdoubleへ変換
double d1 = x / y; // 3 → 3.0 として格納
// どちらかが浮動小数点なら浮動小数点除算
double d2 = x / 2.0; // 3.5
double d3 = (double)x / y; // 3.5
// キャストの位置に注意:先に整数除算が行われてからキャスト
double d4 = (double)(x / y); // 3 → 3.0
printf("x / y (int / int) → d1 = %.1f\n", d1);
printf("x / 2.0 (doubleを含む) → d2 = %.1f\n", d2);
printf("(double)x / y → d3 = %.1f\n", d3);
printf("(double)(x / y) → d4 = %.1f\n", d4);
return 0;
}
x / y (int / int) → d1 = 3.0
x / 2.0 (doubleを含む) → d2 = 3.5
(double)x / y → d3 = 3.5
(double)(x / y) → d4 = 3.0
よくある間違い(代入時の見落とし)
double d = 1/2;
は一見 0.5 に見えますが、1/2
が先に整数除算され 0 になってから double
に変換されます。
正しくは double d = 1.0/2;
または (double)1/2;
です。
剰余演算子 % のポイント
使用制約と定義
- 剰余演算子
%
は整数型同士でのみ使用できます。float
やdouble
では使えません。 - C99以降では、次の性質が成り立ちます。
- 恒等式:
a == (a/b) * b + (a % b)
- 余りの符号:
a % b
の符号は左オペランドa
に一致(0 の場合は符号なし)。
- 恒等式:
また、/
と %
のどちらも、右オペランドが 0 のときは未定義動作です。
実行時エラー相当で、必ず事前チェックが必要です。
負数を含む場合の具体例
#include <stdio.h>
int main(void) {
int a = -7, b = 2, c = -2;
int q1 = a / b; // -7 / 2 → -3 (0方向へ丸め)
int r1 = a % b; // → -1 (左オペランドaと同符号)
int q2 = 7 / c; // 7 / -2 → -3
int r2 = 7 % c; // → 1 (左オペランド7の符号に一致)
// 恒等式の検証
int check1 = (q1 * b + r1 == a);
int check2 = (q2 * c + r2 == 7);
printf("a = %d, b = %d, c = %d\n", a, b, c);
printf("a / b = %d, a %% b = %d\n", q1, r1);
printf("7 / c = %d, 7 %% c = %d\n", q2, r2);
printf("恒等式 a == (a/b)*b + (a%%b): %s\n", check1 ? "成立" : "不成立");
printf("恒等式 7 == (7/c)*c + (7%%c): %s\n", check2 ? "成立" : "不成立");
// これはコンパイルエラー(doubleには%不可)
// double ng = 7.0 % 2; // error: invalid operands to binary %
// 0除算/0剰余の回避例
int denom = 0;
if (denom != 0) {
printf("10 / denom = %d\n", 10 / denom);
} else {
printf("denomが0のため、/ や %% は実行しません。\n");
}
return 0;
}
a = -7, b = 2, c = -2
a / b = -3, a % b = -1
7 / c = -3, 7 % c = 1
恒等式 a == (a/b)*b + (a%b): 成立
恒等式 7 == (7/c)*c + (7%c): 成立
denomが0のため、/ や % は実行しません。
演算子の優先順位と括弧
優先順位と結合規則
四則演算と剰余の優先順位は以下のとおりです。
*
, /
, %
が高く、+
, -
が低いです。
同じ優先度の演算は左から右(左結合)で評価されます。
演算子群 | 優先順位 | 結合規則 |
---|---|---|
*, /, % | 高い | 左から右(左結合) |
+, – | 低い | 左から右(左結合) |
意図を明確にするには括弧の使用が最良です。
読み手にも安全で、バグの温床を減らします。
具体例(評価順の違い)
#include <stdio.h>
int main(void) {
int v1 = 2 + 3 * 4; // * が先 → 2 + 12 = 14
int v2 = (2 + 3) * 4; // 括弧が先 → 5 * 4 = 20
// 同優先度は左から右
int v3 = 20 / 3 * 3; // (20 / 3) * 3 = 6 * 3 = 18
int v4 = 20 / (3 * 3); // 20 / 9 = 2
printf("2 + 3 * 4 = %d\n", v1);
printf("(2 + 3) * 4 = %d\n", v2);
printf("20 / 3 * 3 = %d\n", v3);
printf("20 / (3 * 3) = %d\n", v4);
return 0;
}
2 + 3 * 4 = 14
(2 + 3) * 4 = 20
20 / 3 * 3 = 18
20 / (3 * 3) = 2
よくある落とし穴
整数同士の除算で小数が消える
整数除算は小数部が消えるため、意図せず 0 や切り捨て結果になることがあります。
必要に応じて片方を double
にキャストしてください。
#include <stdio.h>
int main(void) {
double d_bad = 1 / 2; // 先に int の 1/2 → 0、結果は 0.0
double d_ok1 = 1.0 / 2; // 0.5
double d_ok2 = (double)1 / 2;// 0.5
printf("d_bad = %.1f (意図しない)\n", d_bad);
printf("d_ok1 = %.1f (期待どおり)\n", d_ok1);
printf("d_ok2 = %.1f (期待どおり)\n", d_ok2);
return 0;
}
d_bad = 0.0 (意図しない)
d_ok1 = 0.5 (期待どおり)
d_ok2 = 0.5 (期待どおり)
% は浮動小数点では使えない
%
は整数専用です。
小数の剰余が必要な場合は、fmod
(math.h
)を利用します。
#include <stdio.h>
#include <math.h> // fmodを使う
int main(void) {
double a = 7.5, b = 2.0;
printf("fmod(%.1f, %.1f) = %.1f\n", a, b, fmod(a, b)); // 7.5 % 2.0 相当
return 0;
}
fmod(7.5, 2.0) = 1.5
0 で割らない・0 で余りを取らない
/
と %
の右オペランドが 0 の場合は未定義動作です。
実行前に必ずチェックします。
#include <stdio.h>
int main(void) {
int numerator = 10;
int denom = 0;
if (denom == 0) {
printf("エラー: 0 で割る/余りを取ることはできません。\n");
} else {
printf("%d / %d = %d\n", numerator, denom, numerator / denom);
printf("%d %% %d = %d\n", numerator, denom, numerator % denom);
}
return 0;
}
エラー: 0 で割る/余りを取ることはできません。
まとめ
+
, -
, *
, /
, %
の基本挙動を理解し、特に /
は整数同士だと小数部が切り捨てられる点に注意します。
演算子の優先順位は *
, /
, %
が高く、+
, -
が低いです。同優先度は左から右に評価されます。意図を明確にするため括弧で補強するのが安全です。
剰余 %
は整数専用で、C99以降は a == (a/b)*b + (a%b)
が成り立ち、a%b
の符号は左オペランド a
に一致します。
浮動小数点の結果が欲しいときはキャストやリテラル(例: 2.0
)を使い、%
の代わりに fmod
を検討します。
0 での /
と %
は未定義であり、実行前チェックが必須です。これらの基本を押さえることで、数値演算のバグを大幅に減らせます。