· 5分で読了

数値的安定性と誤差

この記事は中国語から自動翻訳されたものです。翻訳によりニュアンスが失われている場合があります。

数値的安定性と誤差

(0.1 + 0.2) == 0.3 という式は、一見すると極めて当たり前のように思える。しかし、コンピュータにおける浮動小数点数の保持方法が原因で、多くのプログラミング言語においてこの等式は成り立たない。つまり、結果は false となる。

だが、もう一つの疑問が生じる。なぜ (0.5 + 0.25) == 0.75 という等式なら成り立つのか?

浮動小数点演算を行う際、多かれ少なかれ誤差は発生するものだ。ここでは、なぜ誤差が生じるのか、そして計算上で誤差を回避するために何ができるのかについて議論していく。

コンピュータにおける浮動小数点数の保持方法

コンピュータ内部で浮動小数点数はビット列として表現され、IEEE 754 によって標準化されている。単精度浮動小数点数は 32 ビット、倍精度は 64 ビットで構成される。その構造は、正負を表す符号部、仮数部(小数部)、指数部に分かれている。

  • 符号部(1ビット):0 は正の数、1 は負の数
  • 仮数部
  • 指数部
型サイズ指数部仮数部
単精度32bit8bit23bit
倍精度64bit11bit52bit

単精度を例にとると、指数部は 8 ビットあるため、表現可能な範囲は 0 〜 255 となる。しかし負の指数も表現する必要があるため、IEEE 754 の仕様ではバイアス値(オフセット)を加えることで、最終的に指数部に格納される数値を決定する。単精度の場合、バイアス値は 127 となる。

例えば、-14.75 を浮動小数点数で表現すると次のようになる:

  • 符号部:負の数なので 1
  • 指数部:まず 14.75 を2進数で表現すると 1110.11 となり、正規化すると 1.11011 * 2^3 となる。指数部は 3 に 127 を足して 130 となり、2進数表記では 10000010 となる
  • 仮数部:11011、残りのビットは 0

したがって、-14.75 の浮動小数点数表現は 1 10000010 11011000000000000000000 となる。000022

ここから、誤差がどこから生じるのかも理解できる。指数が負になると得られる数値はすべて 0 < x < 1 の範囲となるため、2^-x の組み合わせで表現できない数値は、限りなく近づけることはできても完全に一致させることはできない。冒頭で触れた 0.5 + 0.25 は、これらの数値が 2^-1 + 2^-2 と表せるため、計算上で誤差が生じないのだ。

なぜこのように格納するのか?

科学的記数法(Scientific notation)を用いることで、様々なスケールの数値を扱いながら、一定の精度を維持することができる。例えば 0.00000012345 や 1234567890000 は、科学的記数法を使えば 1.2345E-7 や 1.23456789E12 と表せる。コンピュータ内部では通常、浮動小数点形式で格納されるが、これは科学的記数法と似ており、10進数を2進数に置き換えたものに過ぎない。

実数は無限に存在するが、コンピュータの記憶領域には限りがある。そのため、どのような方法で格納しても必ず誤差は生じる。僕たちにできるのは、精度と表現可能な数値範囲との間でトレードオフを行うことだけだ。

有効数字

有効数字は、精度を判断するための指標となる。例えば 0.001 や 0.0135 といった数値は、1×10−31 \times 10^{-3} や 1.35×10−21.35\times10^{-2} と書き表すことができる。このとき 0.001 の有効数字は 1 桁、1.35 の有効数字は 3 桁となる。有効数字の桁数が多いほど、精度は高くなる。

Wikipedia には、分かりやすい判定ルールが記載されている:

  • すべてのゼロ以外の数字は有効である
  • ゼロ以外の数字の間にあるゼロは有効である
  • 先頭のゼロは常に無効である
  • 小数点を必要とする数値において、末尾のゼロ(最後のゼロ以外の数字の後に続くゼロ)は有効である
  • 小数点を必要としない数値において、末尾のゼロは有効である場合もあれば無効である場合もある。追加の記号や誤差情報に基づいて判断する必要がある。

Cancellation of significant digits(桁落ち)

絶対値が非常に近い2つの浮動小数点数同士で引き算を行うと、大半の数値が同一であるため多くの 0 が残り、有効数字が減少してしまう。この現象を cancellation of significant digits(桁落ち)と呼ぶ。

例えば、(1.234567890 - 1.234567889) の結果は本来 0.000000001 であるが、精度が不足している状況では 0 になってしまう可能性がある。

これは数値計算において特に注意すべき点だ。例として半角の公式を見てみよう:

sin⁡2(θ2)=1−cos⁡θ2\sin^2(\frac{\theta}{2}) = \frac{1-\cos\theta}{2}

角度が比較的小さいとき、cos は限りなく 1 に近くなるため、1 から引くと有効数字の喪失(桁落ち)が非常に起きやすくなる。例えば、角度が 1 度のときに半角の公式を使用すると(有効数字 6 桁の場合):

1−cos⁡1°2=1−0.9998472=0.0000765=7.65×10−5\frac{1-\cos1\degree}{2} = \frac{1 - 0.999847}{2} = 0.0000765 = 7.65 \times 10^{-5}

一方、右辺の公式を使わずに直接テーブル引きなどから計算すると:

sin⁡2(1°2)=0.008726532=0.00007615232=7.61523×10−5\sin^2(\frac{1\degree}{2}) = 0.00872653^{2} = 0.00007615232 = 7.61523 \times 10^{-5}

両者の間にかなり明確な誤差が出ていることがわかる。数値計算においては細心の注意が必要だ。桁落ちを防ぐにはいくつかのアプローチがある:

  • 絶対値が近い2つの数値同士での減算を極力避ける

  • 別の公式を用いて計算する(上述の半角公式の変形など)

    • 本質的には、絶対値が近い2つの数値同士で減算させないようにすることだ
  • 計算精度を上げる

結論

ある程度経験のあるエンジニアなら、浮動小数点演算に誤差が生じることや、0.1 + 0.2 != 0.3 となる理由はなんとなく知っているはずだ。この記事では小数の保持方法、IEEE 754、そして有効数字の喪失(桁落ち)について一歩踏み込んで掘り下げてみた。

半角の公式は高校の三角関数で頻出の問題であり、僕にとっても単に公式に当てはめるだけのものだった。

しかし、実際のアプリケーションではすべてコンピュータが計算しているし、現実世界には 30 度や 60 度といった計算しやすい数値ばかりが存在するわけでもない。そして、学校の先生が半角の公式に有効数字の問題が潜んでいることなど教えてくれるはずもないのだ。

関連記事

他のトピックを探索