本文へ移動
目次へ
0.1+0.2が、ぴったり0.3にならないことがあるの?:内容をイメージした挿絵
コンピューター

0.1+0.2が、ぴったり0.3にならないことがあるの?

計算が得意なコンピューターなのに、小数の足し算で小さなずれが見えることがあるよ。数字を保存する仕組みと、画面に表示する仕組みを分けて考えよう。

まず、ひとことで

よく使われる2進の浮動小数点数では、0.1を限られた桁でぴったり表せないんだ。近い値を保存し、計算の結果も丸めるので、小さなずれが生まれることがあるよ。数学の答えが変わったのではなく、数の表し方に理由があるんだ。

学校の足し算と、違う答えが出る?

0.1に0.2を足すと、数学の答えは0.3だね。ところが、Webページでも使われるプログラミング言語JavaScriptで、普通の数の型であるNumberを使ってこの式を計算すると、標準的な表示は0.30000000000000004になる。最後の方に、小さな違いが現れるんだ。これは、足し算の決まりが変わったという意味ではないよ。

理由を探すには、入力した文字、コンピューターが保存した数、画面に出た文字を分ける必要がある。全部をひとまとめに「数字」と呼ぶと、どこで違いが生まれたのか見えにくくなるんだ。ここでは、よく使われる2進の浮動小数点数という仕組みを調べるよ。全てのソフトや計算方法で、必ず同じ表示になるわけではないんだ。

小数点の右にも、位の決まりがある

私たちが普段使う10進法では、小数点の右の位は、順番に10分の1、100分の1、1000分の1を表すよ。例えば0.25なら、10分の1が二つと、100分の1が五つという意味だね。一方、0と1を使う2進法では、右へ一つ進むごとに、位の大きさが半分になる。2分の1、4分の1、8分の1、16分の1、という並びなんだ。

だから、2進法の0.101は、2分の1が一つ、4分の1はゼロ、8分の1が一つという意味になる。足すと8分の5で、10進法では0.625だよ。書かれた数字の列が変わっても、表している量は同じなんだ。最初の図では、どの位を使うかを色で示したよ。0.101を、10進法の千分の101として読まないことが大切だね。

2進法の小数点の右に、2分の1、4分の1、8分の1、16分の1の位を並べる。1、0、1、0を選ぶと、10進法の0.625と同じ量になる。
2進小数は、位が半分ずつになる

位を考えるための四桁の例。2進法の0.1010は、10進法の0.625と同じ値だよ。本物のbinary64のビット配置や桁数は、この図では再現していない。

出典資料に基づきEggplanetが作成した模式図

0.1は短い名前なのに、書き切れない?

ある数を短く書けるかどうかは、使う位の決まりによって変わるよ。10進法で短く書ける0.1は、2進法では0.0001100110011…のように、同じ並びが繰り返され、終わらないんだ。0と1が途中で壊れたわけではなく、10分の1という量を、半分、また半分という位の組み合わせで書こうとした結果だよ。

これは、3分の1を10進法の小数で書くと、0.333…と続くことに似ているね。0.33まで書いても、0.333まで書いても、ぴったり3分の1にはならない。ただし、分数の「3分の1」という書き方なら、同じ量を正確に表せる。数そのものに問題があるのではなく、どんな記号と位を使って、どこまで書くかが関わるんだ。

保存できる桁には、終わりがある

コンピューターに、一つの数のための無限に長い場所を用意することはできない。JavaScriptのNumberは、IEEE 754という規格のbinary64に基づく、2進の浮動小数点数だよ。これは限られたビット数で数を表す方式なんだ。0.1のように2進法では終わらない小数は、その方式で保存できる、近くの値に置き換えられるよ。

近い値で表すことを近似といい、使える桁に収まるように値を選ぶ処理を丸めという。後ろをただ切り捨てるだけとは限らず、近い方を選ぶなどの規則があるよ。浮動小数点という名前は、数の桁と、その大きさを決める情報を組み合わせ、広い範囲の数を扱うことに関わる。小数点以下を何桁かだけ保存する、という単純な箱とは違うんだ。

足す前、足した後、表示するとき

0.1と0.2は、この方式ではそれぞれ近似値として保存される。足し算では、その保存された二つの値を足し、結果も表せる値へ丸めるよ。つまり、正確な10分の1と10分の2を紙の上で足すのと、出発点から同じではないんだ。この例の結果は、同じ方式で保存された0.3とも少し違う値になるので、ぴったり等しいかという比較でも違いが出る。

さらに、画面には、保存された数を10進の文字列に変えたものが出る。表示の桁数を減らせば、結果が0.3に見える場合もあるよ。でも、短く表示することと、内部の値を正確な10分の3へ変えることは別なんだ。二つ目の図は、入力、保存、計算、表示の道を分けたよ。長い表示だけを見て、途中のどの段階で近似したのかを決めつけないでね。

10進の文字0.1と0.2を2進の近似値で保存し、加算して結果を丸め、10進の文字列で表示する順番。JavaScriptのNumberによる標準的な表示例は0.30000000000000004。
入力・保存・計算・表示を分けよう

JavaScriptのNumberで0.1+0.2を計算する流れの模式図。途中のビット列は省略し、最後の数は画面の標準的な表示例。保存された値の正確な10進展開を全桁書いたものではないよ。

出典資料に基づきEggplanetが作成した模式図

小数は、どれも必ずずれるの?

同じ方式でも、ぴったり表せる小数はあるよ。例えば0.5は2分の1、0.125は8分の1なので、2進法でも短く書ける。この二つの和0.625も正確に表せるんだ。「小数だから必ずずれる」でも、「コンピューターが気まぐれに間違える」でもないね。数の種類と、保存の方式と、行う計算を合わせて考えると、違いを説明できるよ。

別の表し方を選ぶこともできる。例えばPythonには、10進の数を扱うdecimalや、分子と分母で有理数を扱うfractionsという道具があるよ。最初から10進の文字列や整数の分子・分母で渡せば、0.1や3分の1を意図どおりに表す助けになる。ただし、すでに近似した値を後から渡すだけでは、最初の意図を自動で取り戻せない。10進の方式でも、3分の1の割り算などには丸めが必要になるんだ。

どれくらい近ければ、よいのかな?

長さや時間を計算する仕事では、「完全に同じか」だけでなく、「必要な精度に収まっているか」を調べたい場合もあるよ。そのときは、許せる差を決めて比較する方法がある。Pythonのmath.iscloseという道具も、数の大きさに対する割合や、値どうしの差を使って近さを調べる。どんな計算にも、同じ小さな数字を足せば解決する、という方法ではないんだ。

必要な精度は、何を求めたいかによって変わる。大きさの違う量を比べるときや、ゼロに近い値を見るときも、条件を考える必要があるね。また、丸め誤差は、通信中に0と1が入れ替わるデータ破損とは違う。ここまでの例では、機械が決められた方式どおりに動いていても、小さな差が生まれる。原因を分けることが、適した工夫を選ぶ第一歩なんだ。

どっとと考えよう:同じ量を、二通りで書く

紙に、2分の1、4分の1、8分の1、16分の1の四枚のカードを作ろう。使うカードを1、使わないカードを0で表すと、0.625にはどの並びが必要かな。次に、0.1を四枚だけでぴったり作れるか考えてみよう。この活動は位の決まりを学ぶためで、本物のbinary64の保存欄や丸め方を、四枚で再現するものではないよ。

もう一枚の紙には、3分の1を0.3、0.33、0.333までで書き、それぞれを三つ足した値を並べてみよう。最後に分数のまま三つ足した答えと比べると、近い値を使った影響が見えるね。これは10進法の紙の模型だけれど、「表したい量」と「書き切れた値」を分ける練習になるよ。答えを眺めるだけでなく、どんな約束で数を保存したかも確かめてみよう。

ことばのメモ

2進小数
小数点の右の位が、2分の1、4分の1、8分の1と半分ずつになる小数の表し方。
近似
表したい値そのものの代わりに、それに近い値を使うこと。
丸め
決めた桁数や表現方法に収まる値を、規則に従って選ぶこと。切り捨てだけとは限らない。
浮動小数点数
数の桁と大きさを決める情報を組み合わせ、限られた桁で広い範囲を表す方式。2進だけでなく10進の方式もある。
丸め誤差
丸めた値と、丸める前の正確な値との違い。
有理数
整数の分子と、ゼロではない整数の分母からなる分数で表せる数。

もとになった資料・もっと知りたい人へ

下記の資料をもとに、説明と考える問題を独自に構成しています。外部資料には英語のページもあります。どっとの画像は案内用のイラストで、観測写真や科学的な図ではありません。

分かりやすくするために、説明を短くしたり、たとえ話を使ったりしています。まちがいを見つけたらお問い合わせへお寄せください。制作方法は編集方針、いっしょに読むときのヒントは保護者・先生へで紹介しています。