C言語基礎|浮動小数点型

小数は“だいたい”で入る箱。だからこそ、誤差と上手につき合おう。

整数では足りない世界がある

整数型は便利だけど、小数部を持つ値(実数)をそのまま表せません。
たとえば 3.14、0.1、1.0/3 みたいな値を扱うとき、C言語では 浮動小数点型 の出番です。

浮動小数点型は、ざっくり言うと

  • すごく大きい数も表せる(大きさの範囲が広い)
  • でも すべての実数を正確には表せない(精度に限界がある)

という「強みと弱み」を同時に持っています。
ここを理解すると、0.1 + 0.2 が 0.3 にならないみたいな現象も怖くなくなりますよ。

浮動小数点型の種類

C言語の代表的な浮動小数点型

代表的な用途ざっくりしたイメージ
floatメモリ節約が大事なとき軽いけど精度は控えめ
double基本はこれバランスが良い標準選手
long double精度を最優先したいときさらに高精度(ただし処理系依存が強い)

表の説明
この3つは「左ほど軽く、右ほど精度や範囲が強い」傾向です。
ただし、実際のビット数や精度は処理系(コンパイラや環境)次第です。

まず大事:浮動小数点は“完全な実数”ではない

浮動小数点は、値を 仮数(有効数字)指数(スケール) に分けて表します。
10進数のたとえだと、こんな形です。

図:指数と仮数のイメージ(10進のたとえ)

図の説明

  • 仮数の桁数が多いほど「細かい違い」を表せます(精度が上がる)
  • 指数の範囲が広いほど「とても大きい/とても小さい」を表せます(大きさの範囲が広がる)

実際のコンピュータでは、これを 2進数の仮数と指数で持ちます。
ここがポイントで、10進数でキレイな値でも、2進ではキレイに終わらないことがあるんです。

“0.1”が正確に入らない理由:2進数の小数

10進数の小数は 10の負のべき乗(10-1, 10-2…)で表せますよね。
2進数の小数は 2の負のべき乗(2-1, 2-2…)で表します。

表:小数部の重み(2進数)

重み
0.1(2進の1桁目)2-1 = 0.5
0.012-2 = 0.25
0.0012-3 = 0.125
0.00012-4 = 0.0625

表の説明
2進小数は 0.5, 0.25, 0.125…みたいに「2で割る世界」なので、
10進の 0.1(= 1/10)はピッタリ終わりません。結果として 無限に続く小数になります。

図:有限で終わる例 / 終わらない例

だから起きる:丸め(四捨五入)と誤差

浮動小数点は、仮数に入る桁数が有限なので、入らない部分は 丸め が起きます。
その結果、次のようなことが起きます。

  • 0.1 を入れたつもりが、内部的には 0.100000000000000005…みたいな近い値になる。
  • その近い値同士を足したり引いたりすると、ズレが見えることがある。

サンプル:誤差を目で確認しよう

ここでは 0.1 + 0.21.0/10.0 を10回足すで、誤差が見えるプログラム例です。

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

// 浮動小数点の誤差を確認する
#include <stdio.h>

int main(void)
{
    double a = 0.1;
    double b = 0.2;
    double c = a + b;

    printf("小数はピッタリ表せないことがあります。\n");

    printf("\n【0.1 + 0.2 の結果】\n");
    printf("0.1 + 0.2 = %.17f\n", c);
    printf("0.3       = %.17f\n", 0.3);

    printf("\n【1.0/10.0 を10回足す】\n");
    double s = 0.0;
    for (int i = 0; i < 10; i++) {
        s += 1.0 / 10.0;
    }
    printf("合計 = %.17f\n", s);

    return 0;
}

このプログラムで分かること

  • 0.1 + 0.2 が、表示桁数を増やすと 0.30000000000000004 みたいに見えることがある。
  • 1/10 を10回足しても、ピッタリ 1.0 にならず、わずかな誤差が残ることがある。
  • これはバグというより「浮動小数点の仕様」なので、対策の仕方を学ぶのが大事です。

登場する命令の書式と役割

printf 関数

書式

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

何をする命令?
画面(標準出力)に文字や数値を表示します。

今回よく使う変換指定子(表示方法)

指定子用途
%fdouble を小数で表示%.17f で小数点以下を17桁表示
%Lflong double を小数で表示%.21Lf など

表の説明

  • %.17f は「小数点以下17桁まで表示」という意味です。
  • 表示桁数を増やすと、普段隠れている誤差が見えやすくなります。

for 文

書式

  • for (初期化; 継続条件; 更新) { 繰り返す処理 }

何をする命令?
指定回数の繰り返し処理を行います。今回の例では 10 回足し算しています。

実務で困らないための考え方

表:よくある落とし穴と対策

ありがちなミスなぜ起きる?よく使う対策
小数を == で比較する誤差でピッタリ一致しない差の絶対値が十分小さいかで判断する(許容誤差)
お金の計算を double でやる0.1 系がズレやすい最小単位(円、セン)を整数で持つ
足し算を大量に繰り返すと誤差が増える丸めが積み重なる計算順序の工夫、long double の検討

表の説明
浮動小数点は「誤差が出る前提」で設計します。
用途に応じて「許容誤差」や「整数化」を使い分けると、現場でも安定します。

図:浮動小数点の内部のイメージ(ざっくり)

図の説明

  • 符号は正負
  • 指数部はスケール(10^9 の 9 に相当する部分)
  • 仮数部は有効数字(1.23457 の部分)
    この配分(何ビット割り当てるか)は型と処理系に依存します。

使い分けの指針(覚え方)

  • 基本は double
  • メモリ優先なら float
  • 精度最優先なら long double(ただし環境差があるので注意)