C言語基礎|浮動小数点と繰返し制御

小数でループを回すと、思わぬ落とし穴にハマる。安全に回すコツをやさしく整理!

0.01 を100回足したら 1.0…のはずが?

浮動小数点(float / double)は便利なんだけど、0.01 のような値を“ピッタリ”表せないことがあります。
そのまま繰返し制御に使うと、

  • 最後が 1.0 にならず 0.999999… で止まる。
  • 1.0 にならないので、終了条件によっては無限ループになる。

みたいなことが起きます。

この記事では、なぜ起きるのかをちゃんと理解して、安全な繰返しの書き方(整数で制御するコツ)まで身につけます。

まず結論:ループの「回数管理」は整数が強い

浮動小数点でループ制御するときの注意点

やりたいことありがちな書き方起きやすい問題おすすめ
0.0 から 1.0 まで 0.01 ずつfor (float x=0.0; x<=1.0; x+=0.01)誤差が累積して最後が 1.0 にならない回数は int で数えて、毎回 x を計算
1.0 になったら終了x != 1.0 を終了条件にする1.0 にならず無限ループ!= での比較は避ける
進捗を刻みたいx を加算で増やす細かい刻みほど誤差が目立つi / 100.0 のように求め直す

表の説明
浮動小数点は「近い値」になるだけで、毎回の加算でズレが少しずつ増えることがあります。
そのため、回数や終了判定は整数にしておくと事故が減ります。

どうして誤差が出るの?(やさしく理解)

浮動小数点は内部的には 2進数の小数で表現されます。
ここで重要なのが、10進の 0.01 や 0.1 は 2進の小数で有限桁にならないことが多いという点です。

図:10進の小数が2進で割り切れないイメージ

図の説明
2進で「終わる」小数は 1/2, 1/4, 1/8…みたいな形(0.5, 0.25, 0.125…)が中心です。
0.01 みたいな値は、近い値で近似されるので、加算を繰返すとズレが育ちます。

終了条件に != を使うと危険な理由

たとえば、次のように書くとします。

  • x を 0.01 ずつ足していって
  • x が 1.0 になったら終わる

でも、x が ピッタリ 1.0 にならない場合があるので、1.0 との比較で止まれません。

浮動小数点の比較で起きること

比較期待現実に起こりうること
x == 1.0いつか true になるずっと false のままの可能性
x != 1.0いつか false になるずっと true のままの可能性(無限ループ)
x <= 1.0どこかで終わる終わるが最後が 0.999999… などになりがち

表の説明
== や != は「完全一致」を要求するので、浮動小数点には不向きです。
終了判定に使うなら「許容誤差(epsilon)」を使う方法もあるけど、回数制御を整数にするのが一番わかりやすく安全です。

サンプルプログラム

  • 温度を 0.0℃ → 1.0℃ まで 0.01℃刻みで表示したい
  • ただし「危険な方法」と「安全な方法」を並べて確認する

というプログラムの例です。

プロジェクト名:chap7-26-1 ソースファイル名:chap7-26-1.c

// 浮動小数点の加算ループと、整数で制御するループの比較
#include <stdio.h>

int main(void)
{
    printf("【比較】0.01ずつ進める処理を2通りで見てみます。\n\n");

    // 1) 浮動小数点を加算して進める(誤差が累積しやすい)
    printf("■ 方法A:floatを加算して進める\n");
    for (float t = 0.0f; t <= 1.0f; t += 0.01f) {
        // 端の方で 0.999999 のようにズレが見えることがある
        printf("温度A = %.6f\n", t);
    }

    putchar('\n');

    // 2) 整数で回数を管理して、その都度計算(誤差が累積しにくい)
    printf("■ 方法B:intで回数管理して、毎回計算する\n");
    for (int i = 0; i <= 100; i++) {
        float t = i / 100.0f;
        printf("温度B = %.6f\n", t);
    }

    return 0;
}

このプログラムで観察できること

  • 方法Aは、最後付近で 1.0 ぴったりにならず、0.999999… のように見えることがある
  • 方法Bは、毎回 t を i / 100.0 で求め直すので、誤差が積み上がりにくい
    (それでも t 自体が完全に表現できるとは限らないけど、ズレが「蓄積」しないのが強み)

登場する命令(構文)と、何をする命令か

for 文

書式

  • for (初期化; 条件式; 更新) 文;

何をする命令?
決まった回数や条件の間、処理を繰返します。

このテーマでは「条件式」と「更新」に注意です。
float を更新に使うと誤差が積み重なるので、回数 i を int で持つのがコツです。

printf 関数

書式

  • printf(書式文字列, 引数1, 引数2, ...);

何をする命令?
画面に文字や数値を表示します。

今回使う主な変換指定子

指定子何を表示
%f浮動小数点(double扱い)%.6f
%.6f小数点以下6桁で表示0.999999 などが見える

表の説明
%.6f のように桁数を固定すると、ズレが目で見えて理解しやすくなります。

図で理解:誤差の累積と、求め直しの違い

図:2つの考え方の違い

図の説明
方法Aは “誤差のある値” をさらに足すので、ズレが蓄積しやすいです。
方法Bは “毎回作り直す” ので、ズレが増え続ける形になりにくいです。

16進浮動小数点定数と16進出力(%a / %A)

16進浮動小数点定数の形

  • 0x整数部.小数部P指数部接尾語

ポイントは P です。
10進表記の E が 10の指数だったのに対して、ここでは 2の指数になります。

表記イメージ
0x1.23P116進の 1.23 × 21
0xA.FP1L16進の A.F × 21(long double)

printf の %a / %A

書式

  • printf(%a, 値);
  • printf(%A, 値);

何をする命令?
浮動小数点数を 16進の浮動小数点形式で表示します。
誤差を「どういう2進表現になっているか」で確認したいときに便利です。

演習問題

演習7-10:2つの方法を横に並べて表示

float を加算して進める方法と、int を 0..100 で回して i/100.0 を使う方法を、同じ行に並べて表示せよ。

解答例

プロジェクト名:chap7-26-2ソースファイル名:chap7-26-2.c

#include <stdio.h>

int main(void)
{
    float a = 0.0f;

    printf(" i   加算で進めるA        計算で作るB\n");
    for (int i = 0; i <= 100; i++) {
        float b = i / 100.0f;
        printf("%3d  %.10f   %.10f\n", i, a, b);
        a += 0.01f;
    }

    return 0;
}

解説

  • A は誤差が積み上がる。
  • B は毎回作るので、ズレが増え続けにくい。
  • %.10f のように桁を増やすと差が見やすいです。

演習7-11:合計値を比べて考察する

0.0 から 1.0 まで 0.01刻みの値を全部足した合計を、方法Aと方法Bで求めて表示せよ。結果の違いを観察せよ。

解答例

プロジェクト名:chap7-26-3 ソースファイル名:chap7-26-3.c

#include <stdio.h>

int main(void)
{
    float sumA = 0.0f;
    float sumB = 0.0f;

    // 方法A:加算で進める
    for (float x = 0.0f; x <= 1.0f; x += 0.01f) {
        sumA += x;
    }

    // 方法B:整数で制御して毎回計算
    for (int i = 0; i <= 100; i++) {
        float x = i / 100.0f;
        sumB += x;
    }

    printf("合計A(加算で進める) = %.10f\n", sumA);
    printf("合計B(毎回計算する) = %.10f\n", sumB);

    return 0;
}

解説

  • どちらも浮動小数点なので誤差はゼロになりません。
  • でも「誤差の増え方」が違うので、合計にも差が出ることがあります。
  • 実務では、金額などの累積は整数(最小単位)で扱うのが定番です。

重要ポイント(やさしくまとめ)

  • 浮動小数点を繰返し制御の基準にすると、誤差で事故りやすい。
  • 終了条件に != を使うのは危険(1.0 にならないことがある)
  • ループの回数や判定は int で管理して、浮動小数点値は 毎回計算するのが安全