Island Life

< 倒置 | 撮影 >

2007/06/05

浮動小数点数

もっと議論になるかと思ったけどあんまり反応が無いのかな。

浮動小数の世界

IEEE 754の64ビット倍精度浮動小数(いわゆるdouble)で

  • 正確に表現できる最大の整数はいくつか
  • 正確に表現できない最小の正整数はいくつか

まず素直な答え。正規化された正の倍精度浮動小数点数の値vは、 hidden bitを含めた仮数部の値をm、指数部の値をeとすると、

 v = m * 2^(e-1023-52)

但しm, eはいずれも整数で、以下の範囲を取る。

  • 2^52 ≦ m < 2^53
  • 1 ≦ e ≦ 2046

このことから、

  • eが1023+52=1075の場合、vは2^52 ≦ v < 2^53の範囲の値を1.0刻みで表現できる。
  • eが1074になれば、vは2^51≦ v < 2^52の範囲の値を0.5刻みで表現できる。
  • eが1073になれば、vは2^50≦ v < 2^51の範囲の値を0.25刻みで… 以下略。

一方、

  • eが1076になれば、vは2^53≦ v < 2^54の範囲の値を2.0刻みで表現できる。
  • eが1077になれば、vは2^54≦ v < 2^55の範囲の値を4.0刻みで以下略。

eが1075を越えるとvは常に整数になることに注意。これから、

  • 正確に表現出来る最大の整数は、e = 2046、m = 2^53-1の場合
    gosh> (* (- (expt 2 53) 1) (expt 2 (- 2046 1075)))
    179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368
    
  • 正確に表現出来ない最小の正整数は、vが2刻みになった直後の、刻みの目から 落ちる数。すなわち2^53+1。
    gosh> (+ (expt 2 53) 1)
    9007199254740993
    

…なのだけれど、特に最大の整数については違和感を覚える人も多いのではないか。 53bitということは10進数で16桁前後。有効数字がそれだけなのにいきなり300桁以上の 整数をぽんと出されても、その下の方の桁に意味はあるの? みたいな。

これは、浮動小数点数の運用に関わっている。定義上は、それぞれの浮動小数点数は 上式で定義される「正確な」値vを持つのだけれど、運用上、浮動小数点数は vを中心とする、ある程度幅を持った曖昧な値を表現するものとして扱われることが多い。

(そのため、浮動小数点数演算=誤差を含んだ不正確な演算、整数演算=正確な演算、 みたいな先入観を持たれることも多いが、溢れたビットを捨てちゃうという点では C言語のint演算とdouble演算はどっちも不正確だし、ビット溢れしない範囲の演算を している限りにおいてはどっちも正確。Schemeでは、正確な数値かどうかというのは 数値の表現法と直交する属性として定義されている (cf. Scheme:非正確な整数?))

さて、そういう非正確な数値の表現として浮動小数点数を見た場合、仮数部m、 指数部eの浮動小数点数は、次のような幅を持った値vを表現していることになる。

 (m-1/2) * 2^(e-1023-52) ≦ v ≦ (m+1/2) * 2^(e-1023-52)   ; mが偶数
 (m-1/2) * 2^(e-1023-52) < v < (m+1/2) * 2^(e-1023-52)   ; mが奇数

但しこれはIEEE754のデフォルトの丸め規則 (中間の値は偶数側に丸める) の場合。 また、mが2^52の場合は幅が切り替わる境界なのでちょっとだけ違ってくる。

 (m-1/4) * 2^(e-1023-52) ≦ v ≦ (m+1/2) * 2^(e-1023-52)   ; m == 2^52の場合

この見方を取った場合、そもそも「正確に表現できる整数」なんてあるのかって 話になるんだけれども、整数になることがわかっている操作 (例えば小数点や指数 表現を含まない数字を浮動小数点数として読み込んだ場合) の結果ならば、 上記の範囲内に含まれる整数がただひとつであれば、その整数値を以って 浮動小数点数が表現している整数と考えても良いだろう。つまり、"13" を 浮動小数点数として読み込んだ結果13.0は、やっぱり整数13だよね、と考えるわけ。

この立場を取ると、正確に表現出来ない最小の正整数は2^53になる。 なぜなら、2^53+1 を読み込ませた時もデフォルトの丸め規則によって 2^53と同じ表現になってしまうため、2^53の浮動小数点数表現を得ただけでは どちらの整数値か区別できないためだ。

gosh> (exact->inexact 9007199254740993)
9.007199254740992e15
gosh> (exact->inexact 9007199254740992)
9.007199254740992e15
gosh> (eqv? (exact->inexact 9007199254740993) (exact->inexact 9007199254740992))
#t

(「幅のある数」と「整数」という条件から考えた時、 9.007199254740992e15 は整数「9007199254740992」または「9007199254740993」を 表現している、と解釈できるため)

正確に表現出来る最大の整数は? 当初は2^53-1だと思ってたんだが、 丸め規則を考慮すると、例えば2^53+2は該当する整数値がひとつしかない (2^53+1は2^53へ、2^53+3は2^53+4へと丸められるため)。 このことから、こちらの答は 2^54-2となる。

gosh> (- (expt 2 54) 1)
18014398509481983
gosh> (exact->inexact (- (expt 2 54) 1))
1.8014398509481984e16
gosh> (- (expt 2 54) 2)
18014398509481982
gosh> (exact->inexact (- (expt 2 54) 2))
1.8014398509481982e16
gosh> (- (expt 2 54) 3)
18014398509481981
gosh> (exact->inexact (- (expt 2 54) 3))
1.801439850948198e16

Tag: Programming