include Math # 2を底にした対数を取り切り捨てる def log2(x) (log(x)/log(2)).floor end # 16ビットの浮動小数点形式への変換 # 大きさ3の配列で # [符号(0: 正, 1: 負), 指数部(1-62), 仮数部(0-1023)] # 指数部が63の時はNaN(Not a Number 表現できる範囲外の数) # 指数部が0の時は0 def toFloat16(f) if f<0 sign=1 f=-f; else sign=0 end if f==0 [0,0,0] else exp=log2(f) if(exp+31<=0) [sign,0,0] elsif(exp+31>=63) [sign,63,0] else f=f/2**exp [sign,exp+31,(f* 2**10).floor-1024] end end end # NaNに対応する定数 NAN=0.0/0.0 # 16ビットの浮動小数点形式からの変換 def fromFloat16(x) if(x[1]==0) (1-2*x[0])*0.0 elsif(x[1]==63) NAN else (1-2*x[0])* 2.0**(x[1]-31) * (x[2]+1024) * 2.0**-10 end end # Float16同士の加算 def add(x,y) toFloat16(fromFloat16(x)+fromFloat16(y)) end # Float16同士の減算 def sub(x,y) toFloat16(fromFloat16(x)-fromFloat16(y)) end # Float16同士の積算 def mul(x,y) toFloat16(fromFloat16(x)*fromFloat16(y)) end # Float16同士の除算 def div(x,y) toFloat16(fromFloat16(x)/fromFloat16(y)) endこれを,ファイルにダウンロードして保存し,irbの中から
irb >> load "./float16.rb" load "./float16.rb" => true >> toFloat16(0.1) toFloat16(0.1) => [0, 27, 614] >> fromFloat16([0,28,614]) fromFloat16([0,28,614]) => 0.199951171875 >> fromFloat16(add(toFloat16(0.1),toFloat16(0.2))) fromFloat16(add(toFloat16(0.1),toFloat16(0.2))) => 0.2998046875のようにして使うことができる.
fromFloat16([0,62,1023])を実行してみれば良い.
16ビット浮動小数点表現では丸め誤差を容易に体験できる. たとえば,0.4は一度16ビット浮動小数点表現で表して,Rubyの浮動小数点表現に戻してみると,
fromFloat16(toFloat16(0.4)) => 0.39990234375
のようになる.そのため,誤差なく表現できていないことが分かる.一方,0.25は
fromFloat16(toFloat16(0.25)) => 0.25
のように元に戻るので,「誤差はあるがたまたま64ビット表現でも区別できな いほど」というレアなケースを考えなければ,誤差無く表現できていると判断 できる.
以下の数の中で,16ビット浮動小数点表現で表した時に誤差無く表現できない数は何か?
スライド「桁落ち誤差の例」を見て,以下の「自然対数のx=1における微係数を求める」プログラムを動かしてみて途中から 誤差が増えていくことを確認しなさい.
def f(x) return Math.log(x); # f(x)=ln(x) end x=1.0; for i in 1..15 h =0.1**i #h=0.1, 0.01, ..., 1e-15 df=(f(x+h)-f(x))/h err=(df-1.0).abs; print "h=",h,"\t f'(",x,")=",df," err=",err,"\n" end
以下の「sin(x)のx=0における微係数を求める」プログラムを動かしてみて,さ きほどの例との違いを観察しなさい.この差の原因を考えてみなさい(正確に答えるには当初考えたよりも深い考察が必要な問題だと分かったので,答えられる範囲で解答するように).
def f(x) return Math.sin(x); # f(x)=ln(x) end x=0.0; for i in 1..15 h =0.1**i #h=0.1, 0.01, , 1e-15 df=(f(x+h)-f(x))/h err=(df-1.0).abs; print "h=",h,"\t f'(",x,")=",df," err=",err,"\n" end
dy/dx=1/2y
という常微分方程式を考える.y(1)=1という初期条件のもとで,y(2)の 値を求めることを試みる(こたえは√2になるはずである).オイラー法で求め るプログラムは
def euler(n) y = 1.0 h = 1.0/n for i in 0..n-1 y = y + (1.0/(2*y))*h end y end
のようになる.一方,Runge-Kutta法では
def runge_kutta(n) y = 1.0 h = 1.0/n for i in 0..n-1 k1= (1.0/(2*y))*h k2= (1.0/(2*(y+k1)))*h y=y+(k1+k2)/2.0 end y end
のようになる.
nの値を1から10倍ずつ変化させていって,結果が√2と小数点以下何桁一致するかを 調べてみましょう.
for i in (0..6) print " euler(10**",i,")=",euler(10**i),"\n" print "runge_kutta(10**",i,")=",runge_kutta(10**i),"\n" end