浮動小数点の計算誤差

 唐突に浮動小数点の計算誤差って一体何なんだろう、ということをネットで調べてみましたメモ。勿論コンピュータに詳しい人なら皆知っているであろうことなんですが、僕はさっぱり知らないので調べてみました。そういえば興味本位で受けた基本情報処理の試験にもこんなトピックがあったような気もします(テキスト斜め読みしただけ・・・)。次回は BigDecimal について調べるつもりです。
 Wikipedia によると、計算誤差には以下の種類があるそうです。

  • 丸め誤差:どこかの桁で端数処理をした場合に発生する誤差
  • 打ち切り誤差:計算を続ければ精度が良くなるにも関わらず、計算を途中で打ち切った場合に発生する誤差
  • 情報落ち:絶対値の大きな数と絶対値の小さな数を加減算したとき、絶対値の小さな数が無視されてしまうこと
  • 桁落ち:値がほぼ等しく丸め誤差を持つ数値同士を減算した結果、有効数字が減少すること

浮動小数点について

 さて、まず浮動小数点って何なのか、から調べてみます。
 浮動小数点は指数形式の数値ことで、例えば「1230」は「1.23E+03」と表現されます。これは「1.23*10^3」という意味。「1230」という数字の見た目の小数点位置と内部表現の小数点位置が異なるため、「浮動小数点」と呼ばれるそうです(つまり、内部表現によって小数点位置が動く)。反対に固定小数点は位置が動きません。固定小数点の方が分かり易いのに浮動小数点を使う理由は、広い範囲の数値を少ない桁数で表現出来るから。コンピュータは桁数が少ない方が計算が速いから、浮動小数点を使う方が有利なんですね。
 浮動小数点は、基本的に「符号」「仮数」「基数」「指数」によって表現されます。例えば「-1.23*10^3」なら、「-」は符号、「1.23」は仮数、「10」は基数、「3」は指数です。「(符号)仮数×(基数の指数乗)」が浮動小数点の表現方法です。
 浮動小数点にも色々な規格があるそうですが、ExcelJava など、メジャーどころではIEEE754という規格が使われているそうで、この規格では基数は「2」とします。つまり、数値を「(符号)仮数×(2の指数乗)」として表現します。データ形式には単精度と倍精度、更にそれぞれの桁数を拡張した拡張精度が存在します。

  • 単精度:全部で32ビット。符号1ビットに仮数23ビット、指数8ビット。
  • 倍精度:全部で64ビット。符号1ビットに仮数52ビット、指数11ビット。

 Java だと short が単精度浮動小数点数、double が倍精度浮動小数点数になりますね!単精度と倍精度は表現出来る範囲が違うだけで、仕組みは全く同じらしいです。単精度で表現出来る仮数は10進数で7桁まで、倍精度だと15桁までになるとのこと。
 では、例えば「2.5」を倍精度浮動小数点として表現してみます。「2.5」を2進数に変換すると「10.1」になります。これは「10.1*2^0」です。IEEE754では仮数部は「1以上2未満」に正規化する必要があるので、正規化して「1.01*2^1」にします(仮数部を2で割って、指数部に2を掛けています)。次に、指数部にバイアス値1023を足します。IEEE754では、単精度の場合は127、倍精度の場合は1023がバイアス値で、これはマイナスの値を取りうる指数部をプラスの値に補正するためのものらしいです。
 「1+1023=1024」を2進数に変換すると、これは2の10乗なので「10000000000」になります。仮数部は、整数部分1を省いた残りを格納します。整数部を省くのは、IEEE754では仮数部は常に1以上2未満に正規化されるため、敢えて記憶する必要が無いからです(復元するときは整数部を補う必要があるはず)。整数部を格納しないようにすると使えるビットが1つ増えるため、省略することになっているそうです。なので、小数部「01」を、右側はゼロで埋めて52ビット分を仮数部とします。これで浮動小数点が何なのかちょっと分かりました!

丸め誤差について

 丸め誤差は流石の僕でも知っていますが、コンピュータの演算でも丸め誤差が発生するのは有名です。でも、何で浮動小数点の計算で丸め誤差が発生するんでしょう?
 例えば「0.1」は2進数では表現出来ません。「0.0001100110011...」と続いていくだけです。こうなると、仮数部は決められた52ビットには入りきらないのでどうしても丸めるしかありません。IEEE754には5種類の丸めアルゴリズムがありますが、通常は「最近接丸め」という丸めが行われます。この丸めによって、実際の「0.1」よりは少し大きな数になってしまいます(数値によっては少し小さくなります)。
 10進数から2進数への正確な変換は出来ない数字を10進数に戻すことを考えます。2進数から10進数への変換は正確に出来るので(0と1で表現された値を2のX乗して足すだけだから)、丸められた結果が正確に得られます。こうして誤差が生まれます。

情報落ちについて

 例えば、仮数部に8ビット分の情報しか保持出来ない浮動小数点演算で「1.1110000*2^50 + 1.1110000*2^30」を計算するとします。単純に足すことは出来ますが、結果を浮動小数点として表現する際は左項に比して非常に小さな値である右項は捨てられてしまいます。これが情報落ちです。「1.1110000*2^10 + 1.1110000*2^(-10)」ならば、計算結果「11110000000.0000000001111」の最初の8桁までしか情報として持っておくことができず、捨てられてしまうことになります。

桁落ちについて

 例えば、「1.23456789*10^2 - 1.23456780*10^2」のような計算を行なうと、計算結果は「9*10^-6」となり、有効数字の桁数は9桁から一気に1桁に減少してしまいます。これが桁落ちです。
 浮動小数点では内部的には常に有効数字の桁数を一定として扱っているため、上位の桁がゼロになると、正規化によってそれを詰め、以下の桁にゼロが強制的に挿入されるので、下位の桁が信頼できないものになります。