Island Life

< ピアノレッスン103回目 | 保守的gcとスタック >

2013/07/26

実数部が0にならない話

「-2の1.5乗」は純虚数になるはずで、HP 35sでは実際そうなるのだけど、 プログラミング言語処理系では実数部に誤差が乗ってしまう、という話。 Gaucheもそうなる。

gosh> (expt -2 1.5)
-5.195564742431613e-16-2.82842712474619i

(expt x y)は、結果が「明らかに実数」でない場合は次のように計算している。

    x^y == e^{y * log(x)}
        == e^{y * log(|x|)} * e^{y * arg(x) * i}

xが負の実数ならarg(x) = πなので、

    x^y == e^{y*log(-x)} * e^{πyi}
        == e^{y*log(-x)}*cos(πy) + e^{y*log(-x)}*sin(πy)i

y = 1.5の時実部が0にならない理由は、1.5πをdoubleで正確に表現できないから cos(1.5π)が0にならないってことなんだけど、なんかそれもださい気がする。 1.5自体はdoubleで正確に表現できるんだし。

sinpi(x) = sin(x*π) といった関数があれば、 xがキリの良い数(1/2の倍数)の時に正確な値が出せそうだ。 浮動小数点数演算でもsin(0), cos(0)は常に正確なので、 1/2πの倍数が0にくるように入力を適切に変換してやればよい。 そんな感じで作ってみたのがこれ。

gosh> (for-each (^x (print (%sinpi x))) (lrange -2 2.001 1/8))
0.0
0.3826834323650898
0.7071067811865475
0.9238795325112867
1.0
0.9238795325112867
0.7071067811865475
0.3826834323650898
0.0
-0.3826834323650898
-0.7071067811865475
-0.9238795325112867
-1.0
-0.9238795325112867
-0.7071067811865475
-0.3826834323650898
0.0
0.3826834323650898
0.7071067811865475
0.9238795325112867
1.0
0.9238795325112867
0.7071067811865475
0.3826834323650898
0.0
-0.3826834323650898
-0.7071067811865475
-0.9238795325112867
-1.0
-0.9238795325112867
-0.7071067811865475
-0.3826834323650898
0.0
#<undef>

これを使うと実部の誤差はこの通り消える。

gosh> (expt -2 1.5)
0.0-2.82842712474619i

確か以前にもsin(nπ)あたりが綺麗に出ないのに残念な気分に なった覚えがあるんだけど、少なくともGaucheのソースでは 他にsinpiとかが使える場所は無い感じなので、試しに入れてはみたが キープするかどうかは微妙。しばらく様子見。

Tag: Gauche

Post a comment

Name: