
C言語のきほん|計算の落とし穴を知ろう
「計算は合ってるはず…なのに違う?」C言語の“計算の落とし穴”を先に知って、バグを未然に防ごう。
C言語の計算は、速くて自由度が高いぶん、思わぬところで結果がズレたり、プログラムが止まったりします。
たとえば、同じ 10 ÷ 4 でも、整数同士なら 2、浮動小数点なら 2.5。どちらも“正しい”のに、知らないと混乱しちゃうんですよね。
この記事では、C言語で特につまずきやすい「計算の落とし穴」をまとめて解説します。
- 数が大きすぎて“巻き戻る”オーバーフロー
- 分母が 0 で落ちるゼロ割
- 整数同士の除算で小数が消える問題
- 浮動小数点で 0.1 + 0.2 が 0.3 にならないことがある誤差
どれも「知っていれば避けられる」タイプの落とし穴です。焦らず一つずつ、実例で感覚をつかんでいきましょう。
オーバーフロー
何が起きるの?
型が表現できる範囲を超えると、計算結果が正しく表せなくなります。
特に int で大きい加算・乗算をすると起きやすいです。
たとえば 32ビット int の最大値は 2,147,483,647。これを超えると、結果が“変な値”になります(環境にもよりますが、典型的には負数になったりします)。
ありがちな例(イメージ)
| 型 | おおよその範囲(符号あり) | 起きやすいこと |
|---|---|---|
| int(32bit想定) | -2,147,483,648 ~ 2,147,483,647 | 2,000,000,000 + 2,000,000,000 などで危険 |
| long long(64bit想定) | 約 -9e18 ~ 9e18 | かなり安全だが無限ではない |
※ 型のビット幅は環境で変わります。厳密に扱うなら limits.h の INT_MAX などで確認します。
防ぎ方の基本
- 最初から大きい型(long long など)で計算する
- 乗算・加算の前に「溢れないか」チェックする
- 金額・個数など、上限が想定できる値は上限設計をする
シンプルな例:危ない計算と安全な計算
ファイル名:6_3_1.c
#include <stdio.h>
int main(void)
{
int a = 2000000000; /* 20億 */
int b = 2000000000; /* 20億 */
/* 注意:int の範囲を超える可能性が高い */
int sum_int = a + b;
/* 型を大きくして計算する(先に long long にしてから足す) */
long long sum_ll = (long long)a + (long long)b;
printf("int で計算した結果: %d\n", sum_int);
printf("long long で計算した結果: %lld\n", sum_ll);
printf("メッセージ: 大きい数は型に気をつけよう!\n");
return 0;
}ポイントは「代入先を long long にした」だけじゃなく、計算そのものを long long で行うことです。
(キャストが無いと、先に int で計算してから long long に入れてしまうので手遅れになることがあります。)
ゼロ割
何が起きるの?
除算 / や剰余 % で、分母が 0 だとエラーになります。
環境によってはクラッシュしたり、実行時エラーになったりします。
防ぎ方
割り算の前に if で分母をチェックします。考え方はとてもシンプルです。
| 目的 | 例 | 何を防げる? |
|---|---|---|
| ゼロ割の回避 | if (b != 0) { a / b; } | 実行時エラー・クラッシュ |
| エラーメッセージ | else { printf(...); } | ユーザーが原因を理解できる |
シンプルな例:安全な割り算
ファイル名:6_3_2.c
#include <stdio.h>
int main(void)
{
int total, people;
printf("合計金額(円)を入力してください > ");
scanf("%d", &total);
printf("人数を入力してください > ");
scanf("%d", &people);
if (people != 0) {
int per = total / people;
printf("1人あたりは%d円です。\n", per);
} else {
printf("人数が0なので割り算できません。\n");
}
return 0;
}ここでは「人数 0」をゼロ割の例にしています。現実の入力でも起こりがちなので、実用面でも大事です。
整数同士の除算
何が起きるの?
int 同士の割り算は、小数点以下が切り捨てになります。
たとえば 10 / 4 は、整数同士なら 2。
小数まで欲しいなら、どこかで浮動小数点にして計算します(キャストするなど)。
例で確認
| 式 | 型 | 結果 |
|---|---|---|
| a / b | int / int | 2 |
| (double)a / b | double / int | 2.5 |
シンプルな例:同じ式でも結果が変わる
ファイル名:6_3_3.c
#include <stdio.h>
int main(void)
{
int a = 10;
int b = 4;
int result1 = a / b; /* 整数同士なので 2 */
double result2 = (double)a / b; /* 2.5 */
printf("整数同士の除算: %d\n", result1);
printf("浮動小数点で除算: %.1f\n", result2);
printf("メッセージ: 型が変わると答えも変わるよ。\n");
return 0;
}「小数が欲しいなら double にする」というより、計算のどの段階で double になるかが重要です。
浮動小数点数の誤差
何が起きるの?
float や double は、内部では 2進数で近い値として表現します。
そのため、10進数でピッタリ表せそうな 0.1 などが、内部的には“近い値”になり、計算結果に誤差が出ることがあります。
よくある現象
- 0.1 + 0.2 を表示すると 0.30000000000000004 っぽく見える
- 合計金額や税計算を浮動小数点で積み上げていくと、最後に 1 円ズレることがある
影響を抑えるコツ
- 精度が必要なら double を使う(float より誤差が出にくい)
- お金の計算は 整数(円)で持つ(小数を避ける)
- 比較するときは == を避けて、差の絶対値で判定する(許容誤差)
シンプルな例:誤差の雰囲気を見る
ファイル名:6_3_4.c
#include <stdio.h>
int main(void)
{
double x = 0.1;
double y = 0.2;
double z = x + y;
printf("0.1 + 0.2 = %.17f\n", z);
printf("メッセージ: 小数は内部表現の都合で少しズレることがあるよ。\n");
return 0;
}表示桁数を増やすと「ズレ」が見えやすくなります。普段の表示では丸められて気づきにくいのが、また落とし穴なんです。
ぱっと見で整理する早見表
| 落とし穴 | 起きる条件 | 典型的な症状 | 基本対策 |
|---|---|---|---|
| オーバーフロー | 型の範囲を超える | 値が不自然になる(負数など) | 大きい型、事前チェック |
| ゼロ割 | 分母が 0 | 実行時エラー、クラッシュ | if で分母チェック |
| 整数同士の除算 | int / int | 小数点以下が消える | キャスト、double で計算 |
| 浮動小数点誤差 | float/double の計算 | わずかなズレ | double、整数化、許容誤差 |
実践問題
商品の税込み合計金額(円)と、支払う金額(円)を入力し、お釣り(円)を求めるプログラムを作成してください。
- 入力はどちらも整数で入力します。
- 支払う金額が合計金額より小さい場合は、エラーメッセージを表示します。
- お釣りは 合計金額との差(支払う金額 - 合計金額)です。
実行結果例(水色文字は入力)
税込み合計(円)を入力してください > 1780
支払う金額(円)を入力してください > 2000
お釣りは220円です。
支払う金額が不足している例
税込み合計(円)を入力してください > 1780
支払う金額(円)を入力してください > 1500
支払う金額が不足しています。
解答例
ファイル名:6_3_5.c
#include <stdio.h>
int main(void)
{
int total;
int pay;
printf("税込み合計(円)を入力してください > ");
scanf("%d", &total);
printf("支払う金額(円)を入力してください > ");
scanf("%d", &pay);
if (pay >= total) {
int change = pay - total;
printf("お釣りは%d円です。\n", change);
} else {
printf("支払う金額が不足しています。\n");
}
return 0;
}解説
この問題は「割り算」ではないですが、計算の安全性を if で守るという意味で、ゼロ割対策と同じ発想が出てきます。
- if の条件で「計算していい状況か」を先に判定する
- 条件を満たさない場合は、計算せずにメッセージを出す
このクセをつけておくと、ゼロ割だけでなく、範囲外アクセスや負の値など、いろんな事故をまとめて減らせます。
実践問題:(BMI)での注意点(丸括弧と表示)
BMI は次の式でしたね。
BMI = 体重(kg) ÷ 身長(m)²
身長(m)² は 身長(m)×身長(m)
ここでの落とし穴は主に 2つです。
- 身長(cm)→(m)への変換で 100.0 で割る(整数の 100 だと意図せず整数計算になる危険がある)
- 分母は height_m * height_m の形にして、丸括弧で順序を明確にする
イメージとしてはこうです。
height_m = height_cm / 100.0
bmi = weight / (height_m * height_m)小数第2位まで表示したいので、printf は %.2f を使います(double なら %f でOKです)。
解答例
ここからは、BMI問題を「落とし穴にハマらない形」で仕上げていきます。ポイントは 整数同士の除算を避けることと、丸括弧で計算順序をはっきりさせることです。
ファイル名:6_3_6.c
#include <stdio.h>int main(void)
{
double weight; /* 体重(kg) */
double height_cm; /* 身長(cm) */
double height_m; /* 身長(m) */
double bmi; /* BMI */ printf("体重(kg)を入力してください > ");
scanf("%lf", &weight); printf("身長(cm)を入力してください > ");
scanf("%lf", &height_cm);
if (height_cm <= 0.0) {
printf("身長は正の値を入力してください。\n");
return 0;
} /* cm を m に変換(整数除算を避けるため 100.0 を使う) */
height_m = height_cm / 100.0; /* BMI = 体重 ÷ (身長(m) × 身長(m)) */
bmi = weight / (height_m * height_m); printf("あなたのBMIは%.2fです。\n", bmi); return 0;
printf("あなたのBMIは%.2fです。\n", bmi);
}ここが大事ポイント
- scanf の %lf は double 用です(float なら %f ですが、Cの scanf はここがややこしいところ)
- 100 ではなく 100.0 にして、最初から浮動小数点で計算します
- 分母は height_m * height_m にして、さらに ( ) で包んで優先順位を明確化します
よくある間違い例(落とし穴)と、何がダメか
間違い例1:100 で割ってしまう(整数除算っぽい事故)
height_m = height_cm / 100; /* 100 が整数 */この書き方だと、状況によっては「思ったより危ない」です。
- height_cm が double なら、100 は自動的に 100.0 扱いになって計算自体は動きます
- でも、変数を int に変えた瞬間に 整数除算になって小数が消える事故が起きやすいです
つまり 未来の自分(または他人)が型を変えたときに壊れやすい書き方なんですね。なので最初から 100.0 にして「浮動小数点で計算する意思」をコードに刻むのが安全です。
間違い例2:丸括弧を付けず、意図しない式になる
bmi = weight / height_m * height_m;一見それっぽいのに、これだと
- weight / height_m を計算して
- その結果に height_m を掛ける
となって、結果はほぼ weight になってしまいます(分母で割ったのに、また掛けて打ち消してる)。
BMI は「分母全体」で割るので、こう書きます。
bmi = weight / (height_m * height_m);
浮動小数点の誤差と表示(小数第2位)
BMIは小数計算なので、内部的にわずかな誤差が出ることがあります。ただし、今回は表示を小数第2位までに丸めるので、通常は問題になりにくいです。
printf("あなたのBMIは%.2fです。\n", bmi);
- 表示は printf で %.2f を使う
- bmi が double でも printf は %f でOK(scanf と違ってここは混乱ポイント)
入力値のチェック(ゼロ割の考え方につなげる)
身長が 0 に近い値だと分母が 0 に近くなり、計算が破綻します。ゼロ割対策と同じ発想で、防御できます。
if (height_cm <= 0.0) {
printf("身長は正の値を入力してください。\n");
return 0;
}
「計算していいかどうか」を if で先に判定するクセは、ほんとに強いです。
