正木です。


|[ruby-math:00794] Re: Rational#to_f
|From: keiju / rational.com (石塚圭樹)
|
|の 1023 はどういう意味ですか?

64 bit Float の構成は
符号   1 bit
指数部 11 bit
仮数部 52 bit
となっています。
したがって指数部は -1024 から 1023 までの値を取るわけですが、
-1023,-1024 の時以外は、例えば仮数部
1000000000000000000000000000000000000000000000000000
は上に 1 bit 補って
1.1000000000000000000000000000000000000000000000000000
を表します。
(ここから以下は数値実験からの私の推測です)
指数部が -1023 の時は Float の range を広げるために、上の規約を使わず
そのかわり指数を -1022 として 
0.1000000000000000000000000000000000000000000000000000
*2**(-1022)  # ここは十進数
を表します。この方式で
0.0000000000000000000000000000000000000000000000000001
*2**(-1022)
=2**(-1074)

まで表すことができます。
指数部 -1024 は Nan,Infinity と言った非数のために残してあると推測
されます。

2**(-1074) まで表現できるはずですが、
1.0/2**1074 #=> 0.0
とすると駄目で
(1.0/2**1023)/2**51 #=> 4.940656458412465e-324
とする必要があります。
私の最初の code が
      (self*2**(m-e)).round.to_f/2**(m-e)
となっていて m-e>=1024 のとき 0.0 になってしまうため、場合分けをした
わけですが、Math.ldexp を使うのならその意味ではこれは不必要なようです。
ただ指数が -1023 より小さい場合は有効桁数が違ってくるので、そのことを
考慮すると

class Rational
  def to_f # for intel 64 bit floating point
    m,n=52,1022
    return 0.0 if abs <= 1/2**(m+n+1)
    e=[abs.log2floor,-n].max - m
    Math.ldexp((self/2**e).round,e)
  end
end

とするのが一番確実です。
2行目の return 文は
(2**(-1075)).to_f==(2**(-1074)).to_f
となるのを防ぐためです。

ちなみに Math.ldexp は
Math.ldexp(0.75,-1074) #=> 4.940656458412465e-324
Math.ldexp(0.5,-1074) #=> 0.0
のように最後の場合は4捨5入しないで切り捨てます。

しかし絶対値が 2**(-1023) より小さい Float の仕様は Float の range
を広げるために考えられた仕様でそれほど精度は問題にならないことを考
えると非常に稀な case のために code を複雑にする必要はなく

class Rational
  def to_f # for intel 64 bit floating point
    e=abs.log2floor - 52
    Math.ldexp((self/2**e).round,e)
  end
end

で充分な気がします。

非常に稀な case というのは
0.01000000000000000000000000000000000000000000000000000 011
*2**(-1022)

が round のために
0.01000000000000000000000000000000000000000000000000000 1
*2**(-1022)

となり Math.ldexp に渡されたあとで更に切り上げられて
0.01000000000000000000000000000000000000000000000000001
*2**(-1022)

となるような case です。
(この場合 Math.ldexp は切り捨てる方を選ぶので実際には大丈夫です。
上記の最後の code でまづくなる実例はまだ見付けていません。)