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,6472,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 / bint / int2
(double)a / bdouble / int2.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 で先に判定するクセは、ほんとに強いです。