
買い物のおつりを計算するとき、0.1+0.2は0.3です。算数としては疑う余地がありません。ところが、多くのプログラミング言語でそのまま計算すると、画面に「0.30000000000000004」と表示されることがあります。コンピュータが足し算を間違えたのでしょうか。
答えは少し違います。人間が考える小数と、コンピュータが内部に保存する小数の形が違うためです。この仕組みを知っておくと、表計算やプログラムで起きる「ほんのわずかな数のずれ」を落ち着いて扱えます。
0.1は、10進数では簡単でも2進数では難しい
ふだん私たちは10進数を使います。0.1は10分の1、0.2は10分の2です。10分の1という数を表すのに、10進数なら「0.1」と書けば十分です。
一方、多くのコンピュータは内部で2進数を使います。2進数の小数では、小数点の右側が順に2分の1、4分の1、8分の1、16分の1……という値を表します。たとえば「0.101」という2進小数は、2分の1と8分の1を足した8分の5、つまり10進数の0.625です。
ここで0.1を2進数で表そうとすると、小数部分が有限桁で終わらず、同じ並びを繰り返します。10進数で3分の1を「0.3333……」と書くのに似ています。コンピュータが保存できる桁数には限りがあるので、0.1そのものではなく、0.1に非常に近い別の数を記録することになります。
これは0.1という数が存在しないからではありません。表現するために選んだ「数の書き方」と「保存できる桁数」の組み合わせで起こる現象です。
実際の計算を確かめてみよう
Pythonで次の3行を実行すると、代表的な環境ではこうなります。
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
print(round(0.1 + 0.2, 1))
0.30000000000000004
False
0.3
1行目では、保存された近似値を使って足し算した結果が表示されています。2行目の==は、二つの値が内部で正確に等しいかを調べる記号です。表示上はどちらも0.3に見えても、計算の途中で生じた差が残っているため、等しくないと判定されることがあります。
3行目では、小数第1位までに丸めて表示しています。見た目を整えられますが、丸めたからといって、それ以前の計算から誤差が消えるわけではありません。丸める場所や方法によって結果も変わります。
0.1を10回足すと何が起きる?
0.1を10回足せば、数学ではもちろん1です。ところが、同じ近似値を繰り返し足すと、小さなずれが積み重なる場合があります。逆に、演算順序や式の書き方によっては、偶然きれいな値になることもあります。
ここで重要なのは「いつも間違う」と考えないことです。どの数を、どの形式で、どの順番で計算したかによって、誤差の現れ方は変わります。文章中の数値例を検証するときも、電卓の小数表示だけを見て内部の値が正確だと決めつけないほうがよいのです。
試すなら、0.1 * 10と、変数に0.1を10回繰り返して加えるループを比べてください。標準的な2進浮動小数点の環境では、前者が1.0、後者が0.9999999999999999のようになることがあります。なお、sum()のような集計機能は誤差を減らす工夫を持つ実装もあるため、単純なループと常に同じ結果になるとは限りません。数学的には同じ式でも計算手順が違えば末尾の桁が変わり得るのです。
お金の計算ならどうすればよい?
財布の中に小数点以下の無限の金額はありません。円だけを扱うなら整数で計算できます。1円の100分の1まで計算する必要がある場合も、どこまでの精度を扱うかを決めて、最小単位を整数に変換する方法があります。
たとえば、100円50銭に相当する値を計算するなら、10050という整数を使い、最後に100で割って表示する考え方です。ただし実際の決済では、通貨の最小単位や端数処理に個別の規則があるため、その規則に合わせる必要があります。
Pythonでは、10進数の小数を正確に扱うためのdecimal.Decimalという仕組みもあります。
from decimal import Decimal
x = Decimal('0.1')
y = Decimal('0.2')
print(x + y) # 0.3
ここで引用符が大切です。Decimal(0.1)と書いて浮動小数点の近似値を渡すと、元の誤差をそのまま持ち込むことがあります。
小数を比較するときの注意
測定値やシミュレーションなど、そもそも現実の値に測定誤差がある場面では、「完全に一致するか」より「必要な範囲で十分に近いか」が意味を持ちます。その場合は許容誤差を決めて比較します。
Pythonのmath.isclose()などがそのために使えます。ただし、どれくらいの差まで許すかは、使い道から決めるものです。テストの成績、小数の金額、科学実験の温度では、求められる厳密さが同じとは限りません。
丸める前の値同士を比較するのか、表示された値を比較するのかも区別します。小数第1位まで表示した二つの数が同じでも、内部の値は異なる可能性があるからです。
小数を含む計算では、目で見た結果が同じだからといって、内部の値まで同じとは限りません。たとえば0.1 + 0.2と0.3は、数学の数としては等しいですが、浮動小数点として保存された値は少し異なる場合があります。
反対に、違う計算式から同じ近似値に丸め込まれる場合もあります。そこで「値が等しいか」を調べる前に、何を確かめたいかを考えてみましょう。丸められた請求書の金額が一致すればよいのか、科学計算で許容範囲内に収まればよいのか、整数の個数を完全一致させたいのかによって、適切な比較方法が変わります。
たとえば商品の個数を数える場合に「5個に十分近いから5個として扱う」という比較は不自然です。一方、センサーが測った長さでは、測定精度より小さい差を厳密に比べても意味がないことがあります。どんな数値を、何のために比較するかが、計算方法の選択を決めるのです。
まとめ|数字がずれたら計算式だけでなく表現方法を疑う
0.1+0.2が0.3にならないように見えるのは、コンピュータが算数の原則を捨てたからではありません。2進数の有限桁では表せない値を近似して扱うため、小さな誤差が現れることがあるのです。
調べる順番は、①内部では何進数か、②どの精度で保存しているか、③どこで丸めたか、④何の目的で比較するかです。この4点が分かると、不可解な数字のずれも説明しやすくなります。

