コンパイル時cos()
コンパイル時に計算してくれるcos()関数ってなんか面白そう?と思って作ってみました。
関数をテイラー展開してそれを演算してるだけです。
やー、lispと違って末尾再帰じゃなくても折りたたまれるのは楽でいいなぁ(笑)
template <typename ValueType, int exponent> class power { typedef ValueType real_type; public: static real_type value(const real_type base) { return base * power<real_type, exponent - 1>::value(base); } }; template <typename ValueType> class power<ValueType, 0> { typedef ValueType real_type; public: static real_type value(const real_type base) { return static_cast<real_type>(1); } }; template <int depth> struct factorial { enum { value = depth * factorial<depth - 1>::value }; }; template <> struct factorial<1> { enum { value = 1 }; }; template <typename ValueType, int max_depth, int depth> struct taylor_expansion_cosine_term { typedef ValueType real_type; static real_type value(const real_type& v) { const int n = max_depth - depth + 1; return (power<real_type, n>::value(-1.0) * power<real_type, n*2>::value(v)) / static_cast<real_type>(factorial<n*2>::value) + taylor_expansion_cosine_term <real_type, max_depth, depth - 1>::value(v); } }; template <typename ValueType, int max_depth> struct taylor_expansion_cosine_term<ValueType, max_depth, 1> { typedef ValueType real_type; static real_type value(const real_type& /*v*/) { return static_cast<real_type>(0); } }; template <typename ValueType, int calc_depth = 7> class cosine { public: typedef ValueType real_type; static real_type value(real_type arg) { return static_cast<real_type>(1) + taylor_expansion_cosine_term<real_type, calc_depth, calc_depth>::value(arg); } }; inline double cos(const double angle) { return cosine<double>::value(angle); } #include <iostream> #include <iomanip> const double pi = 3.14159265358979323846264338327; void list_cos() { for (double angle = -pi; angle <= pi; angle += 0.0001) std::cout << "angle: " << std::setw(11) << angle << " value: " << std::setw(11) << cos(angle) << std::endl; } void static_compute_cos() { std::cout << std::setw(11) << cos(pi/2.0) << std::endl; } int main() { list_cos(); std::cout << std::endl; static_compute_cos(); return 0; }
004012f0 <static_compute_cos()>: 4012f0: 55 push %ebp 4012f1: 89 e5 mov %esp,%ebp 4012f3: 83 ec 18 sub $0x18,%esp 4012f6: dd 05 08 00 44 00 fldl 0x440008 4012fc: a1 c0 33 44 00 mov 0x4433c0,%eax 401301: 8b 40 f4 mov 0xfffffff4(%eax),%eax 401304: 05 c0 33 44 00 add $0x4433c0,%eax 401309: c7 40 08 0b 00 00 00 movl $0xb,0x8(%eax) 401310: dd 5c 24 04 fstpl 0x4(%esp) 401314: c7 04 24 c0 33 44 00 movl $0x4433c0,(%esp) 40131b: e8 70 97 02 00 call 42aa90 <std::basic_ostream<char, std::char_traits<char> >::operator<<(double)>
4012f6: dd 05 08 00 44 00 fldl 0x440008
401310: dd 5c 24 04 fstpl 0x4(%esp)
浮動小数点スタックにロードしてからスタックに積むという多少余計なことはされていますが定数がそのまま使われています。
対してlist_cos()では・・・
00401330 <list_cos()>: 401330: 55 push %ebp 401331: 89 e5 mov %esp,%ebp 401333: 83 ec 28 sub $0x28,%esp 401336: dd 05 00 00 44 00 fldl 0x440000 40133c: dd 5d f8 fstpl 0xfffffff8(%ebp) 40133f: 80 75 ff 80 xorb $0x80,0xffffffff(%ebp) 401343: dd 05 28 00 44 00 fldl 0x440028 401349: dd 45 f8 fldl 0xfffffff8(%ebp) 40134c: e9 1c 01 00 00 jmp 40146d <list_cos()+0x13d> 401351: dd 45 f8 fldl 0xfffffff8(%ebp) 401354: b9 10 00 44 00 mov $0x440010,%ecx 401359: dd 05 60 00 44 00 fldl 0x440060 40135f: d9 c9 fxch %st(1) 401361: 89 4c 24 04 mov %ecx,0x4(%esp) 401365: d8 c8 fmul %st(0),%st 401367: c7 04 24 c0 33 44 00 movl $0x4433c0,(%esp) 40136e: dc c9 fmul %st,%st(1) 401370: dc 4d f8 fmull 0xfffffff8(%ebp) 401373: d9 c9 fxch %st(1) 401375: d8 0d 30 00 44 00 fmuls 0x440030 40137b: d9 c9 fxch %st(1) 40137d: dc 4d f8 fmull 0xfffffff8(%ebp) 401380: d9 c9 fxch %st(1) 401382: dd 5d f0 fstpl 0xfffffff0(%ebp) 401385: d9 c0 fld %st(0) 401387: dc 4d f8 fmull 0xfffffff8(%ebp) 40138a: d9 c9 fxch %st(1) 40138c: d8 35 34 00 44 00 fdivs 0x440034 401392: d9 c9 fxch %st(1)
... 以下、ずらずらと似たような演算が続いてます。
cosine
というわけでtemplateベースでのコサイン関数を用意しとけば即値ならコンパイル時に値が計算され、そうでない場合は演算ルーチンが用意されるようです。
実験にはMinGW g++ 3.4.5とMinGW g++-dw2 4.2.1を使いましたが両方とも展開できてます。
ただしVisual C++ 2008 Express Editionだとコンパイルオプションいじったりしてみたんですが再帰templateが展開されてませんでした。
StandardやProfessionalに比べてコンパイラの最適化性能落としてるのか、それとも元々展開できないのかは不明です(2003Pro以降 gccで生活してるんで買ってないんです・・・)。