関数の速度(6)

atan2

とにかくこれを速くしたい。
場合分けはあとまわしにして、まずは、0 <= |y| <= xの範囲で考える。
前回のatanがそのまま使えて、


y /= x;
double y2 = y * y;
return y * (((256 * y2 + 5943) * y2 + 19250) * y2 + 15015)
/ (((1225 * y2 + 11025) * y2 + 24255) * y2 + 15015);

yを1に固定、xを1〜2に振ってみた。
そうすると、34nsと倍以上になった。


割り算が2回になったのがいけないのだろう。
演算の数は、加算6、乗算8、除算2である。これを[6,8,2]と表現しよう。


割り算を避けるには次のようにする。

 \frac{y(15015x^6+19250x^4y^2+5943x^2y^4+256y^6)}{x(15015x^6+24255x^4y^2+11025x^2y^4+1225y^6)}

これだと割り算が1回減って、高速化が期待できる。
実際のコードは、


double x2 = x * x;
double y2 = y * y;
return y * (15015 * x2 * x2 * x2 + 19250 * x2 * x2 * y2
+ 5943 * x2 * y2 * y2 + 256 * y2 * y2 * y2)
/ (x * (15015 * x2 * x2 * x2 + 24255 * x2 * x2 * y2
+ 11025 * x2 * y2 * y2 + 1225 * y2 * y2 * y2));

演算の回数は[6,28,1]で、15.9nsとなった。
掛け算がかなり多くなったが、次のようにすると減らせる。


double x2 = x * x;
double y2 = y * y;
return y * (((15015 * x2 + 19250 * y2) * x2
+ 5943 * y2 * y2) * x2 + 256 * y2 * y2 * y2)
/ (x * (((15015 * x2 + 24255 * y2) * x2
+ 11025 * y2 * y2) * x2 + 1225 * y2 * y2 * y2));

演算の回数は[6,22,1]で、14.6ns
さらにこうすると減らせる。


double x2 = x * x;
double y2 = y * y;
double y4 = y2 * y2;
double y6 = y2 * y4;
double a = 15015 * x2;
return y * (((a + 19250 * y2) * x2 + 5943 * y4) * x2 + 256 * y6)
/ (x * (((a + 24255 * y2) * x2 + 11025 * y4) * x2 + 1225 * y6));

演算の回数は[6,17,1]で、処理時間は上とほとんど変わらなかった。
場合分けを入れると、


double rotate_angle;
if(abs(y) <= abs(x)) {
if(0 < x) // [-π/4,π/4]
rotate_angle = 0;
else { // [-π,-3π/4], [3π/4,π]
y = -y;
x = -x;
rotate_angle = 0 <= y ? -M_PI : M_PI;
}
}
else {
double tmp = x;
if(0 < y) { // (π/4,3π/4)
x = y;
y = -tmp;
rotate_angle = M_PI / 2;
}
else { // (-3π/4,-π/4)
x = -y;
y = tmp;
rotate_angle = -M_PI / 2;
}
}

double x2 = x * x;
double y2 = y * y;
return y * (((15015 * x2 + 19250 * y2) * x2
+ 5943 * y2 * y2) * x2 + 256 * y2 * y2 * y2)
/ (x * (((15015 * x2 + 24255 * y2) * x2
+ 11025 * y2 * y2) * x2 + 1225 * y2 * y2 * y2))
+ rotate_angle;

これで35ns。
場合分けのほうが遅いのか。