C言語のきほん|浮動小数点型と誤差のしくみ

小数は便利だけれど完全ではない。浮動小数点のしくみを知ると、C言語の計算結果がもっと納得できる。

C言語では、整数だけでなく、小数を含む数値も扱うことができます。
たとえば 3.14 や 0.1、1000.5 のような値を使いたい場面では、整数型ではなく浮動小数点型を使います。

浮動小数点型は、科学技術計算、グラフィックス処理、センサー値の計測、シミュレーションなど、実数を扱うさまざまな場面で使われています。
見た目には自然に小数を扱えてとても便利ですが、ここで大切なのは、浮動小数点数は実数を完全にそのまま保存しているわけではないということです。

コンピュータの内部では、すべての値は限られたビット数で表現されます。
そのため、小数も有限個のビットで近似的に表されます。
このしくみのおかげで、非常に小さな値から非常に大きな値まで幅広く扱える一方で、どうしても誤差が生まれることがあります。

たとえば、0.1 を何度か足した結果が、ぴったり期待した値にならないことがあります。
これはプログラムのバグというより、浮動小数点型の表現方法そのものに由来する現象です。
この性質を知らずに使うと、「計算が合わない」「比較がうまくいかない」と感じることがあります。

ここでは、C言語の浮動小数点型の種類、メモリ上での基本的な表現、そして誤差が発生する理由を、順番にやさしく整理していきます。
小数をただ便利な型として使うだけでなく、その内側で何が起きているのかを理解できるようになるのが目標です。

C言語の浮動小数点型には何があるのか

C言語の浮動小数点型には、主に次の3種類があります。

型名一般的なサイズの例扱える範囲の例精度の目安
float4バイト1.175494e-38 ~ 3.402823e+386桁程度
double8バイト2.225074e-308 ~ 1.797693e+30815桁程度
long double16バイト など3.362103e-4932 ~ 1.189731e+493218桁以上のことがある

ここでいう「精度」は、有効桁数の目安です。
つまり、どれくらいの桁数まで信頼しやすいか、という感覚に近いです。

ただし、ここで注意したいのは、これらのサイズや範囲は処理系に依存するということです。
C言語の規格では、

float ≦ double ≦ long double

という関係だけが基本になっていて、実際のサイズや精度は環境によって変わることがあります。

float、double、long double の違いを感覚的につかむ

この3つの型は、単に大きさが違うだけではなく、使いどころにも違いがあります。

float 型

float は単精度浮動小数点型です。
メモリ使用量を抑えやすいので、組込みシステムやメモリ制約のある環境で使われることがあります。

ただし、double よりも精度が低いため、細かい誤差が問題になる計算では不利になることがあります。

double 型

double は倍精度浮動小数点型です。
C言語では最も一般的に使われる浮動小数点型で、普通に小数を扱うならまず double を考えることが多いです。

精度と使いやすさのバランスがよく、日常的な計算や一般的な数値処理ではとても扱いやすい型です。

long double 型

long double は、double よりさらに高い精度や広い範囲を期待して使う型です。
ただし、環境によっては double と同じ精度で実装される場合もあります。

そのため、「long double だから必ず大幅に高精度」とは限らず、自分の環境で確認することが大切です。

浮動小数点数はなぜ「浮動」なのか

浮動小数点数という名前には意味があります。
これは、小数点の位置を固定せず、値に応じて動かしながら表現する方式だからです。

整数では、小数点を考える必要はありません。
でも小数を扱うときは、単に数字を並べるだけではなく、「どこに小数点があるか」を表す必要があります。

浮動小数点数では、数を大まかにいうと次のような要素で表します。

  • 符号
  • 仮数
  • 指数
  • 基数

コンピュータでは一般に2進数を使うため、基数は2になります。
そのため、実際のメモリ上では、主に次の3つを持つ形で表現されます。

  • 符号ビット
  • 指数部
  • 仮数部

小数点の位置を指数で調整することで、非常に小さい数も非常に大きい数も同じ仕組みで表せるようになっています。
これが「浮動小数点」という名前の由来です。

double 型の基本的な構造

一般的に、浮動小数点型は IEEE 754 というよく知られた形式に基づいて実装されることが多いです。
double 型は 64ビットで表されることが多く、基本的な構造は次のようになります。

部分ビット数
符号ビット1ビット
指数部11ビット
仮数部52ビット

それぞれの役割は次の通りです。

部分役割
符号ビット正か負かを表す
指数部小数点をどれだけ動かすかを表す
仮数部値そのものの細かい部分を表す

この構造によって、広い範囲の値を扱えるようになります。
ただし、仮数部のビット数には限りがあるため、どんな小数でも完全に表せるわけではありません。

この図は、double 型がどのような区画に分かれているかを視覚的に理解するための図です。
小数をそのまま保存しているのではなく、符号、指数、仮数という形で分けて持っていることがわかると、浮動小数点数の考え方がかなりつかみやすくなります。

浮動小数点型ではなぜ誤差が起きるのか

ここがとても大事なポイントです。
浮動小数点型に誤差が起きる理由は、とてもシンプルにいえば、使えるビット数が有限だからです。

実数は本来、無限に細かく続いていくものもたくさんあります。
でもコンピュータのメモリは有限なので、どこかで打ち切るしかありません。
その結果、完全な値ではなく、近い値に丸めて保存することになります。

この「近い値にして保存する」ことが、誤差の出発点です。

0.1 が正確に表せない理由

10進数の 0.1 は、私たちにとってはとても自然な数です。
でも、これを2進数で表そうとすると、きれいに終わりません。

0.1 を 2進小数にすると、次のように循環していきます。

0.00011001100110011...

このように同じ並びがずっと続くため、有限ビットでは途中で打ち切るしかありません。
その結果、コンピュータの中では「正確な0.1」ではなく、「0.1 に非常に近い値」が保存されます。

これが、浮動小数点型でよく起きる丸め誤差の代表例です。

循環2進小数とは何か

10進数では 1/3 が 0.3333... と続く循環小数になります。
これと似たことが2進数でも起きます。

2進数では、有限桁でぴったり表せる小数もありますが、そうでない小数も多くあります。
特に 0.1 のような値は、2進数にすると循環してしまいます。

0.1 を2進小数にしていく流れ

手順計算取り出す整数部
10.1 × 2 = 0.20
20.2 × 2 = 0.40
30.4 × 2 = 0.80
40.8 × 2 = 1.61
50.6 × 2 = 1.21
60.2 × 2 = 0.40

ここで 0.2、0.4、0.8、0.6 の流れがまた出てきます。
つまり、同じパターンが繰り返されることになります。

この図は、0.1 を2進数に変換すると終わらずに繰り返しが続くことを示すための図です。
これを見ると、「0.1 はコンピュータの中ではぴったり表せない」という事実が感覚的に理解しやすくなります。
そして、ここから丸め誤差が生まれることも自然につながって見えてきます。

丸め誤差とは何か

丸め誤差とは、表現しきれない数値を、最も近い値に丸めることで生じる誤差です。

たとえば 0.1 のように正確に表せない値を保存するとき、コンピュータは近い値を選んで格納します。
この時点で、ほんの少しだけ誤差が入ります。
それが計算の途中で積み重なることもあります。

丸め誤差のイメージ

本来の値実際に保存される値のイメージ
0.10.1 に非常に近い値
0.20.2 に非常に近い値
0.30.3 に非常に近い値

この「非常に近い値」は、見た目では同じに思えても、厳密には少し違っていることがあります。
そのため、計算結果に小さなずれが現れることがあります。

桁落ちとは何か

桁落ちは、ほとんど同じ大きさの浮動小数点数どうしを引き算したときに起こりやすい現象です。

たとえば、大きな値の中でごくわずかしか違わない2つの数を引くと、結果そのものは小さな値になります。
このとき、もとの数に含まれていた有効な情報が減ってしまい、結果の精度が悪くなることがあります。

桁落ちのイメージ

計算起こりやすいこと
1.23456789 - 1.23456788結果は小さいが、有効桁が減りやすい
1000000.1 - 1000000.0細かい差が見えにくくなることがある

桁落ちは、数学的には正しい式でも、コンピュータ上では精度の悪い結果になることがある代表例です。

情報落ちとは何か

情報落ちは、非常に大きな値と非常に小さな値を足したり引いたりするときに起こりやすい現象です。

たとえば、ものすごく大きな数に、とても小さな数を足しても、大きい数の桁に合わせて計算されるため、小さいほうが無視されたような結果になることがあります。

情報落ちのイメージ

計算起こりやすいこと
1000000000000.0 + 0.0001小さい値が反映されないことがある
1.0e20 + 1.01.0 の影響が見えなくなることがある

これは「足したはずなのに変わらないように見える」という形で現れることがあります。
とても不思議に感じますが、これも有限ビットで近似していることの影響です。

サンプルプログラム

浮動小数点の誤差を実感しやすいシンプルなプログラムです。

ファイル名:16_5_1.c

#include <stdio.h>

int main(void)
{
    /* 浮動小数点の誤差を確認する */
    double a = 0.1;
    double b = 0.2;
    double sum = a + b;

    printf("浮動小数点の計算を確認してみましょう。\n\n");
    printf("0.1 を表示すると %.17f です。\n", a);
    printf("0.2 を表示すると %.17f です。\n", b);
    printf("0.1 + 0.2 を表示すると %.17f です。\n", sum);

    return 0;
}

このサンプルプログラムでわかること

このプログラムでは、見た目には単純な 0.1 + 0.2 を計算しています。
でも、小数点以下をかなり細かく表示すると、ぴったり 0.30000000000000000 にならないことがあります。

これは、0.1 と 0.2 が内部では完全には表せず、近い値として保存されているためです。
つまり、計算ミスではなく、浮動小数点型の仕様から自然に出てくる結果です。

この例は、浮動小数点の誤差を最初に体感する題材としてとてもわかりやすいです。

浮動小数点型の範囲や精度は float.h で確認できる

浮動小数点型の最大値、最小値、精度は、float.h ヘッダで確認できます。
ここには、各型に対応するマクロが用意されています。

主なマクロ

最小値最大値精度
floatFLT_MINFLT_MAXFLT_DIG
doubleDBL_MINDBL_MAXDBL_DIG
long doubleLDBL_MINLDBL_MAXLDBL_DIG

これらを使えば、自分の環境でそれぞれの型がどのくらいの範囲や精度を持つかを確かめられます。

浮動小数点定数の型指定も大切

浮動小数点定数は、何も付けないと基本的に double 型として扱われます。
float や long double として扱いたい場合には、接尾語を使います。

浮動小数点定数の指定方法

書き方解釈される型
3.14159double
2.71828Ffloat
1.61803Llong double

このルールはとても大切です。
たとえば float の変数に値を入れたいつもりでも、接尾語を付けなければ、その定数自体は double として扱われます。

例:

double pi = 3.14159;
float x = 2.71828F;
long double phi = 1.61803L;

なお、long double の接尾語としては l も使えますが、小文字の l は数字の 1 と見分けづらいので、大文字の L を使うほうが読みやすいです。

float と double のどちらを使うべきか

これはよく迷うポイントですが、一般的には double を使うほうが安全です。

選び方の目安

向いている場面
floatメモリ節約を重視する場面、組込みなど
double一般的な数値計算、通常はこちらが無難
long doubleより高い精度が必要な場面

float は軽そうに見えますが、精度がかなり限られています。
そのため、特別な理由がなければ double を使うほうが安心なことが多いです。

浮動小数点数を比較するときは注意が必要

浮動小数点型では、誤差が含まれることがあるため、値をそのまま == で比較すると期待通りにならない場合があります。

たとえば、

  • 計算の結果が 0.3 のはず
  • でも内部では 0.30000000000000004 のような近い値になっている

ということが起こると、厳密な一致判定で失敗することがあります。

そのため、実際には「十分近いかどうか」で比較する考え方がよく使われます。
これはこの先、数値計算を学ぶときにとても大切になる視点です。

浮動小数点型を学ぶときに意識したいこと

このテーマでは、型の名前やサイズを覚えるだけでなく、次の考え方を持つことがとても大切です。

意識したいこと内容
小数は近似値として保存される実数を完全には表せないことがある
誤差は自然に発生するバグではなく表現上の性質であることが多い
float より double が安全なことが多い精度に余裕があるため
比較は慎重に行う厳密一致に頼りすぎない
環境依存もあるlong double の実装差に注意する

浮動小数点型はとても便利ですが、整数のように「いつもぴったり同じ」とは限りません。
この性質を理解して使えるようになると、数値計算の結果に対して落ち着いて判断できるようになります。