計算の裏側

0.1 + 0.2が0.3にならないのはなぜか

Why 0.1 + 0.2 is not 0.3

0.1と0.2の和は、2つの候補のちょうど真ん中に落ちる。

0.1は2進では終わらない等比級数で、計算機は近い数に丸めて持つ。0.1 + 0.2の答えを決める丸め、2の累乗ごとに倍になる目盛り、和がずれる理由とカハンの加算、ゲームの原点まで。

公式
3 本
人物
ウィリアム・カハン、ジェローム・クーネン、デイビッド・ゴールドバーグ
場面

末尾の4はどこから来たか

0.1 + 0.2は0.3か。PythonでもJavaScriptでも、計算機に足させると返ってくるのは0.30000000000000004である。0.1も0.2も0.3も、計算機の中では2進の数に置き換わっていて、どれも少しずつずれているからだ。

ずれそのものは小さい。答えは本当の0.3より大きいだけで、1京分の1にも届かない。

それでも、末尾の4は偶然ではない。0.1と0.2を計算機が持つ値のまま正確に足すと、和は、計算機が表せる隣り合った2つの数のちょうど真ん中に落ちる。どちらへ寄せるかはIEEE 754という規格の丸め方が決めていて、選ばれたのは上の数だった。

同じ丸めは、数が大きくなるほど粗くなる。ゲームでよく使う単精度の数では、16,777,216に1を足しても値が変わらない。単精度の座標で物体を1秒60コマで動かすと、原点から524 kmより遠い場所では、秒速1.4 mで動かしたはずの物体が、1コマぶんの移動を丸めで失って止まったままになる。

考え方

2進では割り切れない0.1

10進の小数で最後まで書ききれるのは、分母を10の累乗にそろえられる分数だけである。1/4は25/100なので0.25と書けるが、1/3は分母に3が残り、0.333…がどこまでも続く。

2進の小数なら、書ききれるのは分母を2の累乗にそろえられる分数に限られる。1/10の分母には素因数5が入っているので、2進では終わらない。

実際に2進で書くと、0.1は0.0001100110011…となる。小数点のあとに0を1つ置いて、0011の4桁がくり返す。1区切り進むごとに値は1/16になるので、これは公比1/16の等比級数である。

計算機は、この終わらない並びを決まった桁数で丸めて持つ。倍精度の数は先頭から53桁を持つので、Pythonの文書によれば、0.1は分数として蓄えられる。10進に直すと0.1000000000000000055511151231257827021181583404541015625。0.1よりわずかに大きい。

それなのに、0.1と打てば画面には0.1と出る。

数を文字にするとき、同じ数に戻せる書き方のうち、いちばん短いものを選んでいるからだ。JavaScriptの言語仕様は桁数が最も少ない書き方を選ぶよう定めていて、Pythonも3.1からこの選び方になった。0.1 + 0.2の答えは、0.3を倍精度にした数とは別の数である。2つを書き分けるには17桁が要り、それが0.30000000000000004という表示になる。

図

0.1を表す32個のビット

0.1を単精度にしたときの32ビット。符号は0、指数は123で2の−4乗を表す。仮数の23ビットは1001のくり返しで、捨てる部分が最後の桁の半分を超えるので、右端の桁を1上げている。

デイビッド・ゴールドバーグが1991年の総説で、2進で24桁にした0.1として挙げたと同じ並びである。右端は、くり返しどおりなら0になる桁が1になっている。

式

符号と指数と仮数の計算

  1. 単精度と倍精度の数は、符号、指数、仮数で1つの数を表す。は先頭の1を含めた仮数の桁数で、単精度は、倍精度はになる。指数のバイアスは、単精度が127、倍精度が1023である。

  2. 0.1はなので、単精度の指数は。仮数に入りきらない部分を丸めると、単精度の0.1は次の分数に変わる。

  3. 倍精度では53桁で丸めるので、分子が大きくなる。は、にいちばん近い倍精度の数を表す。

  4. の範囲では、隣り合う数の間隔はどこでも同じである。は、の2を底とする対数の整数部分にあたる。この間隔をulpと呼ぶ。英語のunit in the last place(最後の桁の1単位)の頭文字だ。

  5. 間隔を値で割った比は、より大きく以下に収まる。いちばん近い数に丸めれば誤差は間隔の半分までなので、相対誤差はを超えない。

  6. 10進で何桁ぶんかは、にを掛けて求まる。単精度は約7桁、倍精度は約16桁になる。

  7. 倍精度の0.1と0.2を、丸めずに足す。0.2の分数は0.1の分数のちょうど2倍なので、和は0.1の分数の3倍になる。

  8. 和は1/4と1/2の間にあるので、倍精度で持てる数の間隔はである。を単位にした分子は奇数で、和は隣り合う2つの数のちょうど真ん中にある。

  9. 真ん中に落ちたときは、仮数の最後のビットが0になる側、つまり分子が偶数の側を選ぶ。0.3を倍精度にした数は分子が5404319552844595なので、答えはそれよりだけ大きい。は計算機の足し算を表す。

単精度で同じ計算をすると、正確な和はで、真ん中には落ちない。丸めた答えは、0.3を単精度にした数とぴったり重なる。

0.1 + 0.2 == 0.3が成り立つかどうかは、使う精度で変わる。

図

ちょうど真ん中に落ちた和

倍精度で0.1と0.2を足した正確な値は、隣り合う2つの数のちょうど真ん中にある。どちらにも同じ近さなので、分子が偶数の側へ丸め、0.3を倍精度にした数より1目盛り上になる。

偶数の側へ寄せる丸めは、IEEE 754の既定の丸め方である。JavaScriptの言語仕様も、同じ近さの候補が2つあれば仮数が偶数のほうを選ぶと書いている。

なぜ偶数なのか。ゴールドバーグの総説は、真ん中をいつも切り上げる丸めでは、同じ数を足して引く計算をくり返すうちに値が少しずつ上へずれていく例を挙げている。偶数への丸めなら、このずれは起きない。

考え方

2の累乗ごとに倍になる目盛り

単精度の数は、1から2の間に個、つまり8,388,608個が等しい間隔で並ぶ。2から4の間にも同じ個数が並ぶ。幅が倍になっても個数は変わらないので、2の累乗をまたぐたびに間隔が倍になる。

1のすぐ上では間隔が、1024のすぐ上では、16,777,216から上では2。16,777,217は単精度では表せないので、冒頭の16,777,216に1を足す計算は16,777,216のままになる。大きな数に小さな数を足すと小さいほうが消えるこの現象を、情報落ちという。

1からnまでの和を単精度のまま1つずつ足すと、情報落ちがはっきり出る。までは、和は公式と一致する。で正確な和16,782,321がを超え、しかも奇数なので表せず、16,782,320に丸まる。

そこからは、足すたびに丸めが入りうる。公式では50,005,000になるの和が、単精度では50,002,896になった。では、公式の500,000,500,000が499,941,376,000になり、差は約5,912万に広がる。

0.1を足し続けると、ずれはもっと早く大きくなる。整数の和はを超えるまで丸めずに済むが、0.1の和は3回目の足し算から丸めが入るからだ。単精度で100万回足すと100,958.34375になり、正しい値の100,000より約0.96%大きい。倍精度でも100,000.00000133288で、の誤差が残った。

シミュレーション

数直線を拡大して確かめる

見せ方「目盛りを拡大する」では、のまわりを拡大した数直線に単精度の数が目盛りとして並び、の右で間隔が倍になる。線の上を指でたどると、その数が単精度のどこへ丸まるかと、32ビットの並びが出る。

「0.1を足し続ける」と「1からnまで足す」は、100万回までの和を単精度、倍精度、単精度で使うカハンの加算という3通りで並べ、それまでの最大の相対誤差を描く。

「原点から遠い世界」では、 mの地点と原点のそばで、半径35 cmの車輪を秒速1.4 mで転がして並べる。を上げていくと、遠いほうの車輪だけが速まったり、回るだけで進まなくなったりする。

式

カハンの4行

  1. 和をに、丸めで失った分をに持ち、どちらも0から始める。次の項から、前の回に失った分を引いておく。

  2. 和に足す。が大きいと、の下の桁は丸めで消える。

  3. は、のうち実際に和に入った部分である。そこからを引くと、消えた部分の符号を変えた値が残る。

  4. 和を新しくする。は、次の回の手順1で足し戻される。

  5. ゴールドバーグの総説の定理8によれば、こうして求めた和は、各項を以内だけ変えた値の正確な和に、の程度の誤差を足したものになる。は、丸め1回あたりの相対誤差の上限である。

  6. 素直に足したときは、先に足した項ほど多くの丸めを受け、番目の項が近くまで変わりうる。

単精度のままを1つ持つだけで、0.1を100万回足した和は100,000になった。1から100万までの和は500,000,489,472で、正確な和500,000,500,000にいちばん近い単精度の数である。

ただし、計算機の足し算をふつうの数の足し算とみなすと、手順3はになってしまう。ゴールドバーグは、そう考えて式を簡単にするコンパイラーの最適化が、この手順を役に立たなくすると注意している。

歴史

1977年の会合から2019年の改訂まで

1965年1月、ウィリアム・カハンは『Communications of the ACM』の欄「Pracniques」に、1ページの記事「打ち切り誤差の低減についての追記」を寄せた。2026年に補正加算の誤差を解析した論文は、カハンの加算と呼ばれる手順を示した初期の仕事として、この記事を挙げている。

1976年、インテルは8086向けに、浮動小数点の計算を受け持つコプロセッサーの設計を始めた。担当のジョン・パーマーはカハンを顧問に迎え、2人でのちのi8087が従う計算の仕様をまとめた。カハンは1998年のインタビューで、「インテルは『最良の』演算を望んだ」と振り返っている。

1977年11月には、IEEEのもとで規格をつくる会合p754の2回目が、サンフランシスコ半島のホテルで開かれた。カハンは教え子のジェローム・クーネン、客員教授のハロルド・ストーンと規格の形の草案を書き、草案は3人の頭文字からK-C-Sと呼ばれた。DECは自社のVAXの形式を推した。0のすぐそばのすき間を非正規化数と呼ぶ小さな数で埋める「段階的アンダーフロー」をめぐって、議論は何年も続いた。

規格は、1985年3月21日にIEEEの規格委員会でIEEE 754-1985として承認された。カハンは同じインタビューで、「正式に認められる1年前に、IEEE 754-1985は事実上の標準になっていた」と語っている。

1991年2月25日、サウジアラビアのダーランで、パトリオットミサイルの防空システムがスカッドミサイルを追えず、スカッドは米陸軍の兵舎に落ちて28人の米国人が亡くなった。米国会計検査院(GAO)が1992年2月にまとめた報告によれば、システムは時刻を0.1秒単位の整数で数え、24ビットのレジスタで実数に直していた。動かし続けるほど時刻の誤差は大きくなる。報告の表では、100時間で誤差が0.3433秒、目標が次に現れると予測する範囲のずれが約687 mになる。

表には、1時間から100時間までの6つの誤差が載っている。0.1を2進の小数点以下23桁で切り捨てたときの誤差秒に、0.1秒を数えた回数を掛けて計算し直すと、6つとも小数点以下4桁まで一致した。

事故の翌月の1991年3月には、ゴールドバーグの総説が『ACM Computing Surveys』に載った。題は「計算機科学者がみな知っておくべき浮動小数点演算」である。規格は2008年と2019年に改訂され、いまの版は2019年6月13日に承認されたIEEE 754-2019で、2進に加えて10進の浮動小数点数も扱う。IEEEは2024年6月に、次の改訂の作業を認めている。

使いどころ

ゲームの原点と表計算の15桁

ゲームの座標。 ゲームエンジンGodotの文書によれば、物理と描画は浮動小数点数に頼っていて、原点から離れるほど精度が下がる。物体が「震える」ように見えたり、原点から遠いときだけ物理の不具合が出たりする。文書の表では、8192から16384の範囲で単精度の間隔は約0.001、32768から65536では約0.0039になり、これを超えるとふつうは倍精度が要るという。

原点を中心に8192 m四方までを歩いて回るオープンワールドなら、一人称のゲームでも精度は足りる、とも書いている。倍精度にするにはエンジンをprecision=doubleでビルドし直す。性能の低い機種では、原点を付け替える方法もある。

ほかのエンジンも手当てをしている。

Unreal Engine 5の文書によると、Unreal Engine 4では座標が32ビットの精度だったため世界の大きさが限られ、上限は21 kmだった。Unreal Engine 5は座標に64ビットの倍精度を使い、上限の既定は8,800万kmになった。UnityのHDRPは、描くときにカメラの位置を原点に置き直す方法を既定で使う。遠くの物体の面が同じ位置に重なって見える乱れを防ぐためだと、文書は説明している。

秒速1.4 mで歩く人を1秒60コマで動かすと、1コマの移動は2.33 cmになる。単精度の位置に毎コマ足していくと、原点から約2 kmを超えたところで秒速1.40625 m、131 kmを超えると0.9375 m、262 kmを超えると1.875 m、524 kmを超えると0になった。1コマの移動が間隔の半分に届かなければ、足しても位置は変わらない。半分を超えていても、移動は間隔の整数倍に丸まるので、速くなったり遅くなったりする。

表計算。 マイクロソフトの文書では、Excelは数を倍精度で持ち、有効桁数は15桁までとしている。文書の例では、セルに=(43.1-43.2)+1と入れて小数点以下15桁の指数表記にすると、0.9ではなく0.899999999999999と出る。Excel 97からは、足し算や引き算の答えが0かそれにごく近いとき、2進への変換で入った誤差を補正している。比べる前にROUND関数で桁をそろえる方法も、文書に載っている。

プログラム。 小数を==で比べず、差が許せる幅に入るかで比べる。Pythonでは0.1 + 0.2 == 0.3はFalseだが、math.isclose(0.1 + 0.2, 0.3)はTrueになる。この関数は、既定では相対でまでの差を同じとみなす。お金のように10進で正確に扱いたい値は、Pythonの文書が勧めるdecimalモジュールを使うか、金額を最小の単位の整数で持つ。

たくさんの値を足すなら、math.fsumは途中で失った桁を追い、丸めを最後の1回だけにする。いまのPythonでは、sumも途中の和を拡張した精度で持つので、sum([0.1] * 10) == 1.0はTrueになる。

自分で試すなら。

  1. ゲームの世界でいちばん原点から遠い座標について、間隔を求め、1コマでいちばん小さく動く量と比べる。間隔の半分より小さい動きは消える。
  2. 0.1を10回足して1と比べるプログラムを、単精度と倍精度で書く。倍精度は0.9999999999999999、単精度は1.0000001192092896になる。カハンの4行を加えると、単精度でもちょうど1になる。
  3. シミュレーションの「原点から遠い世界」でを17から19まで動かし、上の速さと比べる。

時間の刻みで誤差が積もる例はオイラー法とルンゲ=クッタ法に、ゲームが乱数を作る仕組みはゲームの乱数はどう作られているのかにある。

IEEE 754-2019の概要は、規格の演算の結果が、入力の値と演算の順序と結果の形式だけで一つに決まると書いている。規格に従う計算機なら、倍精度で0.1と0.2を足した答えは、いつも同じ0.30000000000000004になる。

参考文献

  1. IEEE, "IEEE Standard for Floating-Point Arithmetic", IEEE Std 754-2019 (2019). DOI 10.1109/IEEESTD.2019.8766229(IEEE SAの規格のページで、754-1985・754-2008・754-2019の承認日と概要を確認)
  2. IEEE, "IEEE Standard for Binary Floating-Point Arithmetic", IEEE Std 754-1985 (1985). DOI 10.1109/IEEESTD.1985.82928
  3. D. Goldberg, "What every computer scientist should know about floating-point arithmetic", ACM Computing Surveys 23(1) (1991), 5-48. DOI 10.1145/103162.103163(Oracleの『Numerical Computation Guide』の付録Dに載る再録版で確認)
  4. W. Kahan, "Pracniques: further remarks on reducing truncation errors", Communications of the ACM 8(1) (1965), 40. DOI 10.1145/363707.363723
  5. C. Severance, "An Interview with the Old Man of Floating-Point: Reminiscences elicited from William Kahan"(1998年2月20日、カハンのページで公開)
  6. U.S. General Accounting Office, "Patriot Missile Defense: Software Problem Led to System Failure at Dhahran, Saudi Arabia", GAO/IMTEC-92-26(1992年2月4日)
  7. L. Gao, F. Baidoo, "Dekker's floating point number system and compensated summation algorithms", arXiv:2602.19452 (2026)
  8. Python 3.15 documentation, "15. Floating-Point Arithmetic: Issues and Limitations"・ECMAScript 2027 Language Specification(草案)のThe Number TypeとNumber::toString
  9. Microsoft Learn, "Floating-point arithmetic may give inaccurate results in Excel"
  10. Godot Engine 4.7 documentation, "Large world coordinates"・Epic Games, "Large World Coordinates in Unreal Engine 5"・Unity HDRP 17.0 manual, "Camera-relative rendering"

姉妹サイト

株式会社Game が運営するメディアです。