MENU

「0.1 + 0.2 ≠ 0.3」の衝撃!Python小数の呪縛を`decimal`で解く

「0.1 + 0.2 = 0.3」。

…当たり前ですよね?小学生でも知っている計算です。私もそう思っていました。しかし、Pythonでこの計算をすると、驚くべき結果が返ってきます。

“`python

print(0.1 + 0.2)

0.30000000000000004

“`

一瞬、目を疑いました。「え、なんで?」「たかがこれくらいの誤差、気にすることないでしょ?」

そう、かつての私はそう考えていました。しかし、この「たかが」の誤差が、私のシステムを、そして会社の信頼を蝕むことになるなど、当時の私は知る由もなかったのです。

あれは数年前、私が開発を担当していたECサイトでの出来事でした。顧客から「購入履歴の合計金額が、なぜか数円ずれている」という問い合わせが入り始めたのです。最初は「計算ロジックにバグがあるのか?」と、何日も徹夜でコードを追いかけました。しかし、どこにも間違いは見つかりません。夜中に一人、真っ暗なオフィスで、モニターに映る0.30000000000000004という数字を睨みつけながら、「なぜだ、なぜだ…」と頭を抱えていた、あの絶望感は今でも忘れられません。

「まさか、こんな基本的な計算でバグるなんて…」「このままじゃ、会社の信用問題に発展する…」

あの時、私は浮動小数点数という「目に見えない呪縛」に囚われていたのです。

なぜ「0.1 + 0.2」は「0.3」にならないのか?

この奇妙な現象の根源は、コンピューターが小数を表現する方法にあります。

多くのプログラミング言語で使われている浮動小数点数(float型)は、内部的に2進数で小数を表現します。しかし、10進数で0.10.2といったキリの良い数字も、2進数では無限小数になってしまうことがあります。

例えば、10進数の1/30.3333...と無限に続くように、2進数でも0.10.0001100110011...と無限に続きます。コンピューターは有限のメモリしか持たないため、どこかで打ち切って「丸める」しかありません。この丸め処理によって生じるのが、あの微細な誤差なのです。

「たかが小数点以下の小さなずれでしょ?」

そう思うかもしれません。しかし、あなたがパティシエだとして、繊細なケーキのレシピに「砂糖30.1g、バター20.2g」と書かれているのに、「だいたい30gと20gでいいか」と目分量で混ぜていたらどうでしょう?最初はそれなりに美味しいケーキができるかもしれません。でも、100回、1000回と作り続けると、ある日、なぜか味が安定しない、膨らまない、焦げ付くといった問題が頻発します。それは、あなたが無視した「0.1gと0.2g」の誤差が積み重なった結果なのです。

同様に、この微細な誤差が積み重なると、金融計算での合計金額の不一致、科学シミュレーションの信頼性低下、条件分岐での比較演算のバグなど、重大な問題を引き起こす可能性があります。

浮動小数点数の「呪縛」からあなたを解放する`decimal`モジュール

私が絶望の淵にいた時、金融系のシステムに詳しい先輩エンジニアが、まるで救世主のように教えてくれたのがPythonのdecimalモジュールでした。

金銭計算や厳密な精度が求められる場面では、floatを使うな。decimalを使え

その言葉は、私の頭に稲妻のように響きました。decimalモジュールは、浮動小数点数とは異なり、10進数で数値を正確に表現し、計算を行います。これにより、前述のような丸め誤差を回避できるのです。

`decimal`の基本的な使い方

使い方は非常にシンプルです。ポイントは、Decimalオブジェクトを生成する際に、数値ではなく文字列で初期化することです。Decimal(0.1)のようにfloatで初期化すると、その時点でfloatの誤差が混入してしまうからです。

“`python

from decimal import Decimal

floatで計算した場合

float_result = 0.1 + 0.2

print(f”floatでの計算結果: {float_result}”) # floatでの計算結果: 0.30000000000000004

decimalで計算した場合

decimal_a = Decimal(‘0.1’)

decimal_b = Decimal(‘0.2’)

decimal_result = decimal_a + decimal_b

print(f”decimalでの計算結果: {decimal_result}”) # decimalでの計算結果: 0.3

“`

どうでしょう?decimalを使えば、期待通りの0.3という結果が得られます。この差は、特に金融システムや科学計算、あるいは厳密な比較が必要な場面で、天と地ほどの違いを生むのです。

「パフォーマンスが心配…」というあなたへ

中には「decimalfloatよりも処理が遅くなるんじゃないか?」「そこまで厳密な計算が必要な場面は少ない」と感じる方もいるかもしれません。確かに、decimalfloatに比べてわずかに処理コストが高くなる傾向があります。

しかし、考えてみてください。

目に見えない誤差によるバグの特定と修正にかかる時間、そしてそれが引き起こす潜在的な金銭的損失や信用の失墜と比べて、どちらがコストが高いでしょうか?

私自身の経験から言えば、正確性を犠牲にしてパフォーマンスを優先することは、砂上の楼閣を築くようなものです。まずは正確性を担保し、もしパフォーマンスがボトルネックになった場合に初めて最適化を検討すべきです。

また、decimalモジュールはPythonの公式ドキュメントでも、金銭計算や高精度な計算が必要な場面での利用が推奨されています。これは、IEEE 754という浮動小数点数の国際標準の特性を理解した上で、Pythonコミュニティが導き出した賢明な選択なのです。

その「たかが」が、あなたのシステムを蝕む

「0.1 + 0.2 = 0.30000000000000004」

この小さな数字の裏には、あなたのシステムを根底から揺るがしかねない、重大な問題が潜んでいます。目に見えない小さな不正確さが、やがて大きな破綻を招くという普遍的な真理は、プログラミングの世界でも例外ではありません。

decimalモジュールは、この浮動小数点数の「呪縛」からあなたを解放し、コードの信頼性を劇的に高める強力なツールです。正確な計量器(decimal)を使っていれば、毎回寸分違わぬ最高のケーキ(正確な結果)が作れるはずなのに、あなたはまだ「勘」で戦い続けますか?

今日から、あなたのコードにdecimalを取り入れてみてください。それは、未来の潜在的なバグを防ぎ、あなたのプログラマーとしての信頼性を盤石にする、最も賢い投資となるでしょう。

その「たかが」が、あなたのシステムを蝕む前に。

今こそ、decimalで正確な計算の世界へ足を踏み入れましょう。