はじめに
競プロの問題をPythonで解いているときに何気なくWhile文を使って書いていたらとあること
が起きてしまった.
事象確認
Pythonで以下のようにコードを書いたとしよう.
x = 0.0
while x != 1.0:
print("OK")
x += 0.1
OKは何回出力されるだろうか?
ではこの出力結果はどうか?
0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 1.0
一度結果と根拠を考えてからスクロールしてほしい,どのようになるかな?
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
結論を言うと結果は以下のようになる.
OKの数は,
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
...
ほんの一部分の出力にすぎません
0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 1.0
の結果は,
False
まあ記事のタイトル的に予想できただろう.OKは10回出力されるわけではなく無限ループが起きてしまった.また,0.1を10回足しても1.0ではないみたいだ.
なぜだろうか?
検証
強引に10回繰り返してみてx
の値を確認してみる.
x = 0.0
for _ in range(10):
x += 0.1
print(x)
この出力結果は
0.9999999999999999
??? 1.0
にならない??
理由
Pythonにおいて,浮動小数点数は一般的にIEEE 754倍精度浮動小数点数形式(64ビット)で表現される.この形式では,0.1を正確に表現することができない.0.1を2進数で表現すると,次のような無限循環小数になる.
0.1 (10進数) = 0.0001100110011001100110011001100110011001100110011... (2進数)
Pythonでは,この無限に続く2進数を64ビットの範囲で丸めて表現する.この丸めにより,わずかな誤差が発生する.
このような仕様により0.1を10回足しても1.0になるわけではないのだ.
この問題の本質は浮動小数点型の精度
にある.コンピュータにおける浮動小数点型の定義と計算をもう一度考え直してみるいい機会だと思う.
解決策
コメント欄から以下の解決策をいただきました.
引用元
@YottyPGさんのコメント
from decimal import Decimal
x = Decimal('0.0')
for _ in range(10):
x += Decimal('0.1')
print(x)
1.0
PythonのDecimalクラスを使用することで,正確な10進数の計算が可能になる.
浮動小数点値の0.1の代わりにDecimal('0.1')を使用することで,このコードでは10進数の精度で加算が行われることが保証される.Decimal('0.1')をxに加算するたびに正確に行われ,最終的な値は1.0になる.
Decimalクラスは,正確な10進数の計算が必要な場合に役立つ.
浮動小数点数で発生する可能性のある丸め誤差や精度の問題を回避することができる.
競プロの問題を解く際は浮動小数点型の表現に気をつけよう.