浮動小数点数

小数を入力してみてください。下に続くのは、紙の上で計算するのと同じ手順を追った変換の過程です。そして一番上の行が、コンピュータが最終的に記憶するデータそのものになります。

インタラクティブシミュレーションを読み込んでいます...
レッスン

理論 — 浮動小数点数688語

浮動小数点数は、整数に2のべき乗を掛け合わせたものです。式にすると(−1)s × 1.f × 2E−1023となります。この形式において、Efにそれぞれ何ビットを割り当てるかが決められています。そして、このビットの配分ひとつで、表現できる数の範囲と、二つの数をどこまで細かく区別できるかの両方が確定するのです。

各記号の意味

s
符号ビットです。正であれば0、負であれば1になります。数値の中に組み込まれるのではなく独立して存在するため、この形式には+0と−0の両方があります。
E
指数部です。専用の符号を持たなくても負の数を表せるよう、実際の値に1023を足した形で保持されます。これで数値の範囲が決まりますが、隣り合う値の間隔を決定するのもまた、この範囲なのです。
f
仮数部です。正規化された数は必ず1から始まるため、その先頭の1は保存されず、それに続く52ビットが格納されます。この52ビットが精度のすべてであり、指数部がそこに精度を付け加えることはありません。
前提
対象の数が有限であり、かつ正規数であること。2−1022 を下回ると先頭の1が失われ、代わりに精度を手放すことで表現を続けます。その結果、倍精度における正の最小値は 4.94 × 10−324 となり、仮数部はわずか一ビットになります。
成り立たない場合
破綻するのは二箇所。どちらも実際のコードで起こります。別々の計算経路をたどった二つの値を = で比較すると、必ず失敗します。経路が違えば丸められ方も違うからです。また、長い足し算は足す順序に左右されます。リストを先頭から足すのと末尾から足すのとでは合計が一致しません。一番小さな項から先に足していくことで、失われる情報を最小限に抑えられます。

1000個のうちの八つ 🖖

0.001から1.000までの数のうち、全く誤差なく保持されるものはちょうど八つあります。0.125、0.25、0.375、0.5、0.625、0.75、0.875、そして1です。2進小数とは二分の一、四分の一、八分の一といった値の足し合わせです。一方で1000という分母には125という因数が含まれており、2の累乗でこれが打ち消されることは決してありません。0.375と入力すればツールは正確だと答えますが、0.376と入力するとそうはならないのです。

0.1 + 0.2 はちょうど中間に 🖖

保持された二つの値を足し合わせると0.3000000000000000166533453693773481063544750213623046875になります。そしてこれは、0.3の両隣にある二つの倍精度浮動小数点数の、正確に中間の値です。足し算自体に何の誤りもありません。ちょうど中間になった場合は、どうにかしてどちらかに決めなければなりません。ここで、最後のビットが偶数である隣の値を選ぶという規則が働きます。0.3より小さい倍精度浮動小数点数は仮数部が奇数ビットで終わり、大きい方は偶数ビットで終わるため、合計値は切り上げられます。0.30000000000000004という末尾の数字は、ここから来ているのです。

少ないビット数で、正しい答えを 🖖

同じ二つの数値で、binary32のボタンを押してみてください。0.1 + 0.2 は 0.300000011920928955078125 になりますが、これはその形式で0.3が記憶される値と完全に一致します。あの有名な計算の破綻は、和がちょうど中間点に落ちることで起こります。24ビットの精度では、中間点から間隔の四分の一だけ手前に落ちるため、本来期待していた数値へと丸められます。精度が半分になることで、欲しかった答えが手に入るのです。

2のべき乗ごとに間隔は2倍になる 🖖

0.0625から0.125までの間であれば、表現できる数値から次の数値までの距離は 1.39 × 10⁻¹⁷ となり、その範囲内で値が変動することは一切ありません。次の範囲へと踏み出すと、この間隔は2倍になります。グラフにある階段はまさにそれを示しており、2のべき乗ごとに一段ずつ上がっていきます。だからこそ、同じ形式で 10⁻³⁰⁰ と 10³⁰⁰ を両方とも表現できるにもかかわらず、9007199254740993 を表現できないのです。

全プロセスの詳細解説

  1. ある決済システムでは、ポンド単位の金額をbinary64で保持しています。£0.10と£0.20がどちらも本来よりわずかに大きい値として保存されること、そしてそれらを足しても£0.30にはならないことを示してください。また、決済エンジニアがこの問題にどう対処しているかも説明してください。

    1. 0.1を二進法で表すと、0.0001のあとに1001が無限に続きます。十分の一を作るには分母に5が必要ですが、二進法の小数には2しかありません。そのため、この展開が終わることは決してないのです。

    2. 2進数で53桁が保持され、残りの桁は丸めて捨てられます。ここでは切り上げが行われるため、保持される値は0.1を5.55 × 10⁻¹⁸だけ上回ります。ページ上の丸めのステップでは、その方向を矢印で示しています。

    3. 0.2はまったく同じ53桁で指数が1つ大きいだけなので、誤差もきっちり2倍の1.11 × 10⁻¹⁷になります。ボックスに0.2を入力して、二つのエラーカードを比べてみてください。

    4. この二つの保存された値を足し合わせると、0.3を挟む二つの倍精度浮動小数点数のちょうど中間に着地します。このように中間で並んだ場合は最後のビットが偶数になる方の隣接値が選ばれ、ここでは大きい方の値がそれに該当します。

    5. その結果、返ってくる値は0.3000000000000000444089209850062616169452667236328125となり、その後のすべての計算にこの値が引き継がれます。

    6. 今度は0.1と0.2の代わりに、10と20を入力してみてください。どちらも2⁵³をはるかに下回る整数であり、誤差なく保持されます。したがって10 + 20は30となり、どこにも情報の欠落は生じません。

    解答

    金額はポンドではなくペンスのような最小単位の整数として保持します。2⁵³までのすべての整数は正確に表現できるため、計算も正確になります。あとに残る丸め処理は、自分自身で意図したタイミングに書き込むものだけとなり、どこで丸められたかをはっきりと指し示すことができます。ツールを使えば、この両面を十秒で確認できます。0.1を入力するとエラーカードはゼロになりませんが、10を入力すればゼロになります。

例題

  • 0.1 - 0.1は 0.1000000000000000055511151231257827021181583404541015625 として記憶され、0.2はその2倍だけ大きくなります。それらの和は0.3の前後にある二つの数値のちょうど中間に位置するため、端数処理の規則が適用されて切り上げられます。
  • 0.375 - 2進数での展開は永遠に続くことなく、三桁で止まります。そのためツールはこの値を正確だと判定するのです。0.375は3/8であり、2進小数とはまさにこの八分の一を単位として構成されているからです。最後の桁を0.376に変えてみてください。展開は決して終わりません。
  • 32ビットでの0.1 - 同じ0.1を、53ビットではなく24ビットの仮数部で扱います。内部では0.100000001490116119384765625として保持され、ツールには10進数で7.22桁と表示されます。ここに0.2を足すと、答えは0.300000011920928955078125。これはまさに、この形式で0.3を保存した値そのものです。
  • 2⁵³ + 1 - 2⁵³ を超えると間隔は2になるため、整数が一つおきに抜け落ちていきます。9007199254740993 は二つの表現可能な数値のちょうど中間にあり、端数処理によって 9007199254740992 へと切り捨てられて −1 の誤差が生じます。そこに1を足しても、同じ理由から結果は変わりません。
  • 10¹⁶ + 1 - 10¹⁶ も1も正確に保持されますが、10¹⁶ + 1 の結果は 10¹⁶ に戻ってしまいます。ここでの間隔は2であるため、10000000000000001 は隣接する二つの数値の中間となります。0.1 + 0.2を切り上げた『最後のビットを偶数にする』規則が、今度はこの数値を切り捨てるように働きます。