1. 0.1を 3かい たすと どうなる?
1. 0.1を3回足すと、0.3と「同じ」ではなくなる
1. 0.1+0.2を実行すると何が返るか
0.1に 0.2を たします。こたえは 0.3ですよね。ところが、プログラムで 計算させると、0.30000000000000004と 出ることが あります[2]。
0.1を 3回 たして、0.3と くらべさせてみます。すると コンピュータは「同じでは ない」と 返して きます[2]。
コンピュータが こわれて いるのでしょうか。Pythonと いう プログラムの 道具の せつめいには、「Pythonの まちがいでも、書いた 人の まちがいでも ない」と あります[2]。では、何が おきて いるのでしょう。
2. 3ぶんの1が 0.333…なのと、にて いる?
2. 3分の1を0.333…と書くのと、同じ事情
2. 10進の1/3と2進の0.1 ── 終わらない小数は、進法ごとに別の数
1を 3で わると、0.333…と 3が ずっと つづきます。10進の 小数では、3分の1は ぴったり 書けません。
だれも これを こわれて いるとは 言いません。「書ききれない 数」だと 知って いるからです。
コンピュータは、10進では なく 2進と いう 書きかたで 数を もちます。2進は 0と 1だけで 数を 書く やりかたです。じつは この 2進だと、10進の 0.1が 書ききれない 数に なります[1]。1/3と 同じで、けたを いくつ 使っても ぴったりには なりません[2]。
10進で みじかい 0.1が、2進では 書ききれない。それは、どんな 数なのでしょう。
1÷3は、10進法では0.333…と3が永遠に続き、ぴったりの小数では書けません。それでも私たちは、「3分の1」という分数で持てば困りません。
コンピュータは小数を2進法で持ちます。2進法は0と1だけで数を書く方法で、小数点の右の桁は、1/2、1/4、1/8、…と半分ずつ小さくなっていきます。ここに落とし穴があります。10進で有限の0.1は、2進では無限に繰り返す表現になります[1]。Pythonの公式チュートリアルも、0.1は2進の桁をどれだけ使ってもぴったりには表せないと、1/3の例とあわせて説明しています[2]。
どの数が終わって、どの数が終わらないのか。0.1の中身を、桁にばらして見てみます。
10進法で1/3を書くと0.333…と終わらない。10進で書ききれない数は存在し、その性質は進法によって決まる。逆向きの例が0.1で、10進では有限の小数なのに、2進では無限に繰り返す表現になる。Goldbergの論文は、2進のとき0.1が2つの浮動小数点数の間に挟まり、ちょうどには表せないことを述べる[1]。Pythonの公式チュートリアルも、2進の桁をいくら使っても0.1は2進の分数としてぴったりにならない、と1/3との比較で説明する[2]。
つまり「0.1だけが特別に壊れる」のではなく、「10進で短い数が2進でも短いとは限らない」ということだ。どの数が終わり、どの数が終わらないのか、桁を展開して確かめる。
3. ぴったり 書ける 数と、書けない 数
3. ぴったり書ける数と、書けない数の見分け方
3. 分母に5を含むか ── 0.5・0.25・0.125と0.1の差
2進で 小数点の 右の くらいは、2分の1、4分の1、8分の1…と 半分ずつ 小さく なります。
だから 0.5(2分の1)は 2進で 0.1、0.25(4分の1)は 0.01、0.125(8分の1)は 0.001と、ぴったり 書けます。
ところが 0.1は 10分の1です。分母の 10には、2では わりきれない 5が ふくまれて います。そのため、2進では 0.000110011001100…と、「0011」を くりかえして 終わりません[1]。
ずれる 数は 0.1だけでは ありません。0.5や 0.25の ように ぴったり 書ける 数も あります。では、終わらない 数を、コンピュータは どう しまって おくのでしょう。
2進で有限の小数になるのは、1/2、1/4、1/8のように、分母が2を掛け合わせた数になる分数です。0.5は2進で0.1、0.25は0.01、0.125は0.001と、ぴったり書けます。
0.1は1/10で、分母の10は2×5です。2を何回掛け合わせても5は出てこないので、2進では終わりません。実際に2進で書くと0.000110011001100…となり、0.0のあとに「0011」が際限なく繰り返されます。仮数部分で書けば、1.100110011…×2−4です[1]。
ずれる小数は0.1だけではありませんが、すべての小数がずれるのでもありません。0.5や0.25は、コンピュータの中でもぴったりです。では、終わらない数は、有限の入れ物にどう収めるのでしょうか。
2進で小数点以下の桁の重みは1/2、1/4、1/8、…と半分ずつ減る。したがって、有限桁の2進小数で書けるのは、分母が2の累乗になる分数だ。0.5=1/2は2進で0.1、0.25=1/4は0.01、0.125=1/8は0.001とちょうど表せる。
これに対して0.1=1/10は、分母が2×5で5を含むため、2の累乗の分母にはできない。2進展開は0.000110011001100…で、「0011」の繰り返しになり、正規化すると1.100110011…×2−4だ[1]。ここまでの結論は、すべての小数がずれるのではなく、2の累乗を分母にできない小数がずれる、ということになる。では、無限の繰り返しを、有限のビット列にどう収めるのか。
4. コンピュータの 中の 0.1は、ほんの すこし 大きい
4. 53桁に丸めると、0.1は少しだけ大きくなる
4. 倍精度の53桁と、3602879701896397÷255
コンピュータが 数を しまう 場所には、かぎりが あります。多くの プログラムでは、2進で 53けたまでが 目安です[3]。
ノートの はしに 書ききれない 3の つづきを、「ここまで」と 区切るのに にて います。0.1の くりかえしは、53けたで あらわせる 数の うち、いちばん ちかい 数に まるめられます。
この まるめた 数は、3602879701896397を 2を 55回 かけた 数で わった 分数です[2]。10進に なおすと、0.1000000000000000055…。ほんの すこしだけ、0.1より 大きい 数です[2]。
ただし 本当に ずれるのは、ずっと 先の 小さな けたです。その 小ささを 見て みましょう。
倍精度と呼ばれる形式は、1つの数を64ビットで持ちます。符号に1ビット、指数に11ビット、仮数に52ビットを使い、先頭に省かれた1が付くので、有効な桁は2進で53桁になります[3][4]。JavaScriptの数も、この形式です[3]。
0.1の無限の繰り返しは、53桁で表せる数のうち、いちばん近い数に丸められます。その結果、コンピュータの中の0.1は、約分した形で3602879701896397÷255という分数になります。10進に直すと0.1000000000000000055511151231257827…で、0.1よりわずかに大きい数です[2]。小さい側ではなく、大きい側にずれている点に注意してください。
ずれの大きさは、0.1との差でおよそ5.6×10−18です。ここは、小数点以下17個の0のあとに、やっと数字が現れる大きさです。
倍精度(binary64)は64ビットで、符号1・指数11・仮数52ビット、先頭の暗黙の1を加えて有効53桁になる[3][4]。JavaScriptのNumber型もこの形式だ[3]。0.1の無限の繰り返しは53桁で表せる数のうち最も近い数に丸められる。
Pythonで保存される0.1は、3602879701896397÷255に等しい。10進展開は0.1000000000000000055511151231257827…で、0.1より大きい側にずれる[2]。差は0.1との引き算で約5.55×10−18だ。単精度(有効24桁)の例では、0.1は1.10011001100110011001101×2−4に近い値で近似される[1]。桁数が変われば、保存値も変わる。
この差が実用上どの程度の大きさか、次に確かめる。
5. ずれは、どれくらい 小さいの?
5. ずれは小さいのに、足し算で顔を出す
5. 約15〜17桁の精度と、マシンイプシロン
この 数の しくみで 正しい けたは、10進で 15〜17けたほどと いわれます[3]。1の となりの 数との すきまは、0.0000000000000002ほどです[3]。
つまり ふだんの 計算では、ずれは 見えません。ただ、0.1も 0.2も ほんの すこしずつ ずれて いるので、たすと その ずれが 重なります。そのため、0.3に いちばん ちかい 数とは べつの 数に なります[2]。それで 0.30000000000000004と 出るのです。
ずれは 小さい のに、「同じかどうか」を くらべると 見えて しまいます。ほかの コンピュータでも、同じ ことが おきるのでしょうか。
倍精度で正しく表せる桁は、10進で約15〜17桁です[3][4]。「1」の次に表せる数との差は約2.2×10−16で、JavaScriptではNumber.EPSILONという名前で、2.220446049250313e-16という値が用意されています[3]。この値は「1のすぐ近くで、表せる数どうしがどれだけ離れているか」を表します。ふつうに表せる範囲の数なら、いちばん近い数に丸めたときの誤差の割合は、この値以下におさまるとされています[1]。ただし、これは割合の話で、誤差そのものの大きさの上限ではありません。
だから、ふだんの計算でずれが目に入ることはほとんどありません。ところが0.1も0.2も、それぞれ少しずつずれた数で保存されています。足すとずれが合わさり、0.3に最も近い数とは別の数になることがあります。それが、0.30000000000000004と表示される理由です[2]。
整数にも限界があります。−253から253まで(約9000兆)の整数は、ぴったり表せます。これを超えると、表せる数の間隔が広がり、253から254の間では偶数しか表せません[4]。ずれは、数が大きくなるほど粗くなります。
倍精度の有効桁は、10進で約15〜17桁だ[3][4]。Number.EPSILONは2.220446049250313e-16で[3]、正規化された範囲では、最近接丸めの相対誤差はマシンイプシロン以下に収まる[1]。EPSILONは1付近での数の間隔であり、絶対誤差の上限ではない。したがって、1個の数を保存するだけなら、誤差は有効桁の末尾に出るにすぎない。
ところが0.1も0.2もそれぞれ丸められているため、足し算の結果はそれぞれの丸め誤差の影響を受ける。その結果が0.3に最も近い倍精度の数と一致しない場合がある。0.30000000000000004はその例だ[2]。これは誤差の累積の一例であり、計算の種類や順序によって誤差の出方は変わる。
整数についても限界がある。−253から253までの整数はちょうど表せるが(253=9007199254740992、約9.0×1015)、これを超えると隣り合う表現可能な数の間隔が広がる。253から254では偶数しか表せない[4]。
6. おなじ きまりなら、答えが そろうのは なぜ?
6. 計算のやり方を決めた規格 ── IEEE 754
6. IEEE 754と、ウィリアム・カハンの役割
Pythonでも JavaScriptでも、数の しまいかたは 同じ 形が もとに なって います[3]。これは「IEEE 754(アイトリプルイー ななひゃくごじゅうよん)」と いう きまりが あるからです。しまいかたと けいさんの しかたが そろって いれば、おなじ けいさんは おなじ こたえに なります。
この きまりは、数の もちかたと 計算の やりかたを きめます。1985年に さいしょに 出て、2008年と 2019年に 直されました[5]。まるめかたも きまって いて、ふつうは「いちばん ちかい 数」を えらびます[5]。
ウィリアム・カハンと いう 人は、1985年の きまりを 作る 大きな やくわりを はたしました。1989年に チューリングしょうという 大きな しょうを もらって います[6]。
では、1円も ずれては こまる お金の 計算は、どうするのでしょう。
コンピュータの小数の計算は、IEEE 754という規格で決められています。1985年に最初の規格が出版され、2008年と2019年に改訂されました[5]。規格の標準の丸め方は、「いちばん近い数へ。ちょうど中間なら偶数側へ」です[5]。
IEEE 754の形式と丸め方がそろっていて、計算の条件も同じなら、同じ計算は同じ答えになります。PythonもJavaScriptも倍精度の同じ形式を使っているため[3]、0.1+0.2のずれは、言語を変えても同じ形で現れることが多いのです。
この規格の作成に中心的な役割を果たしたのが、ウィリアム・カハンです。カハンは1970年代後半から1980年代初めにかけて、IEEE 754-1985の作成で中心になり、1989年にはACMチューリング賞を受賞しました[6]。
ずれが「規格どおりの結果」だと分かると、次の疑問が出ます。1円もずれてはいけないお金の計算は、どうするのでしょうか。
浮動小数点数の表現と計算はIEEE 754で規定され、1985年に初版が出版され、2008年・2019年に改訂された。標準の丸めは「最近接、同距離なら偶数側」である[5]。Pythonの0.1+0.2とJavaScriptのNumberは、いずれも倍精度の形式をもとにしているため[3]、同種の結果が出やすい。ただし、言語や環境ごとに数値の扱いが完全に同じとは限らない。
規格作成の中心にいたのがウィリアム・カハンで、1970年代後半から1980年代初めにIEEE 754-1985の作成を主導し、1989年にACMチューリング賞を受けた[6]。ここまでで分かったのは、ずれが規格で決まった丸めの結果だということだ。では、1円単位で合わせたいお金の計算ではどうするのか。
7. お金の 計算は、どう すれば ずれないの?
7. お金の計算では、整数か十進型で「ずれ」を避ける
7. 十進型と整数による金額計算
おみせの お金の 計算で 1円でも ずれると、こまります。そこで くふうが あります。
ひとつは、お金を 「円」の 数、つまり 整数で 数えることです。10円玉が 3まい なら 30円と 数えれば、小数を 使わないので ずれません[7]。
もうひとつは、10進の まま 計算する しくみを 使う やりかたです。Pythonでは、その しくみで 0.1を 3回 足して 0.3と くらべると「同じ」と 返って きます[7]。
ずれる 計算も、ずれない 計算も、道具を えらべば 使い分けられます。
お金のように、ぴったり合わせたい計算には、2つの工夫があります。1つは、金額を「円」や「セント」の単位の整数で持つ方法です。小数を使わなければ、2進のずれは起きません[7]。
もう1つは、十進型(decimal)と呼ばれる専用の型を使う方法です。10進のまま計算するため、Pythonではデシマルで0.1を3回足した値が0.3と等しくなります[7]。ずれる計算は、悪者ではありません。科学の計算のように、速くて大きな数まで扱える2進の形式が向く場面は多くあります。目的に合わせて、道具を選ぶのです。
金額のように、結果がぴったり合うことを求められる計算には、専用の十進型(decimal)を使うか、金額を円やセント単位の整数で持つという対処がある[7]。Pythonでは、Decimalで0.1を作って3倍した値が、Decimalの0.3と等しくなる。10進の桁をそのまま保つ設計なので、2進の丸めが入らない。
2進の浮動小数点数は、広い範囲の数を速く扱うために選ばれた表現であり、10進の端数をぴったり持つことが目的ではない。何を保証したいかで、型を選ぶ必要がある。
8. かみと えんぴつで、0.1を 2進に して みよう
8. 筆算で、0.1の2進数を作ってみる
8. 出口 ── 2倍する手順で循環を確かめる
かみと えんぴつだけで、0.1を 2進に できます。やりかたは かんたんです。
① 数を 2ばいする。② 1より 大きく なったら「1」と 書き、1を ひく。1より 小さければ「0」と 書く。③ これを くりかえす。
0.1なら、0.2(0)→0.4(0)→0.8(0)→1.6(1、のこりは 0.6)→1.2(1、のこりは 0.2)…と なります。0.2が もういちど 出たら、あとは さっきと 同じ ことの くりかえしです。ずっと 0、0、1、1が つづきます。
つぎに 0.5で やって みましょう。2ばいすると ちょうど 1.0で、のこりは 0。1けたで 終わります。0.25は どうなるでしょう。
もっと 知りたく なったら、Pythonの せつめい[2]や、Goldbergの ろんぶん[1]を、おうちの 人と ひらいて みてください。えいごの ページです。
0.1を2進に直す手順を、紙と鉛筆でやってみましょう。①数を2倍する。②1以上になったら「1」と書いて、1を引く。1未満なら「0」と書く。③これを繰り返す。
0.1なら、0.2(0)→0.4(0)→0.8(0)→1.6(1、残り0.6)→1.2(1、残り0.2)となり、残りの0.2が再び出てきます。ここから先は同じことの繰り返しなので、0、0、1、1が延々と続きます。
次に0.5と0.25でも試してください。0.5は1回の2倍でちょうど1になり、残りが0になって終わります。0.25がどう終わるかは、自分で確かめてみましょう。
この記事の一次資料は英語ですが、Pythonの公式チュートリアル[2]や、浮動小数点を解説したGoldbergの論文[1]は、数式が少ない部分から読めます。
0.1を2進に展開する手順は、「2倍して整数部分を桁に取り、小数部分で続ける」だ。0.1→0.2(0)→0.4(0)→0.8(0)→1.6(1、残り0.6)→1.2(1、残り0.2)と進み、0.2が再び現れた時点で循環が確定し、0.0001100110011…となる。同じ手順で0.5は1回で、0.25は2回で終わる。2進で終わる数と終わらない数は、この手順でも見分けられる。
資料を読むなら、浮動小数点全般はGoldbergの論文[1]、Pythonでの実例はPython公式チュートリアル[2]、型の仕様はMDN[3]、金額計算の考え方はFloating-Point Guide[7]が、いずれも英語で読める。規格の作成者としては、カハンのカリフォルニア大学バークレー校のページ[6]がある。
しらべた もとの じょうほう
参考にした情報源
参考にした情報源と、その使い方
出典について:浮動小数点の論文、Python公式チュートリアル、MDN、規格や人物の解説ページ、金額計算のガイドを使った。何に使ったかは、それぞれの項目の最後に書いている。
- David Goldberg, What Every Computer Scientist Should Know About Floating-Point Arithmetic(Oracle掲載版)。https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html (0.1の2進での表し方、丸め誤差の大きさについて)
- Python Docs, Floating Point Arithmetic: Issues and Limitations。https://docs.python.org/3/tutorial/floatingpoint.html (0.1+0.2の結果、0.1の保存値、「バグではない」という説明について)
- MDN Web Docs, Number。https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number (倍精度の形式、桁数、Number.EPSILONについて)
- Double-precision floating-point format(Wikipedia)。https://en.wikipedia.org/wiki/Double-precision_floating-point_format (有効桁と、整数をぴったり表せる範囲について)
- IEEE 754(Wikipedia)。https://en.wikipedia.org/wiki/IEEE_754 (規格の出版年・改訂年と、標準の丸め方について)
- UC Berkeley EECS, William M. Kahan。https://www2.eecs.berkeley.edu/Faculty/Homepages/kahan.html (カハンの役割とチューリング賞について)
- The Floating-Point Guide。https://floating-point-gui.de/basic/ (お金の計算で十進型や整数を使う工夫について)
なおした ところ
更新履歴
更新履歴(改版の記録)
- 初版を公開。
このサイトでは、公開した記事の本文は原則として書き直しません。誤りが見つかったときや、内容が古くなったときだけ手を入れ、その理由をこの欄に残します。