久々にかなりピンチな状況になりました
今週水曜日に出張に行くのだがホテルが空いていない。
正確に言うと高いホテルしか空いていない。
仕方がないのでネットカフェに泊まるしかなさそうだ。
しかもそれも空いている保証がないという恐ろしさ。
さらに天気予報では雨となっている。かなりまずいかも。
学生時代は余裕だったけどこの年でしかも仕事で大丈夫だろうか。
なあ、何とかなるだろうと相変わらず楽観的です。
ベルヌーイ数をテンプレートメタプログラミングで頑張ってみた。
ベルヌーイ数の説明は大変なので省きます。wikipediaとかに詳しく書いてあるのでそちらを参照してください。
とりあえず、計算する際には以下の漸化式が便利だそうです。
ここで縦の括弧はコンビネーションです。
今回はこれをそのまま使おうと思います。
まずはテンプレートメタプログラミングではなく普通のプログラミングの場合
int combination(int n, int m) { if (m == n || m == 0){ return 1; } else{ return n * combination(n - 1, m - 1) / m; } } fraction bernoulli(int n) { if (n == 0){ return fraction(1, 1); }else{ fraction sum(0, 1); for (int k = 0; k < n; ++k){ sum += bernoulli(k) * combination(n + 1, k); } return sum / -(n + 1); } }
さて、ベルヌーイ数は有理数になるので出力は分数です。動的な変数の分数ライブラリは標準にはまだ存在しないので適当なライブラリをfractionという名前で定義させてください。
一つ目の関数がコンビネーションです。ちなみに計算には以下の公式を利用しています。
「なんでこんな公式をいちいち覚えているんだ?」と思われる人もいるかと思われるが以下のような方法で暗記している。
「あるn人のメンバーからk人の委員を選び、その中から1人委員長を選ぶ組み合わせ」
これだと公式の左辺になるが、「委員より委員長を先に一人選んでから残りk-1人の委員を選ぶ」という方法も考えられて、それが右辺になる。
さてこの式、20あたりまではまあ普通に計算できるが20を過ぎたあたりからだんだん重くなると思う。高速化するにはまず、ベルヌーイ数の性質である「1を除く奇数の場合は0になる」を使う方法がある。もう一つは、必ずnが低い値を計算するのでテーブル化する方法がある。後者を使う場合は実装を大きく変える必要があるが
さて本題に移ってこれをテンプレートメタプログラミングで頑張ってみます。動的な分数ライブラリは存在しないのになぜか静的な分数ライブラリはC++11から導入されています。理由はよく分かりませんがなんにせよこれを使わない手はありません。
template< size_t n, size_t m > struct combination{ enum{ val = n * combination< n - 1, m - 1 >::val / m }; }; template< size_t n > struct combination< n, 0 >{ enum{ val = 1 }; }; template< size_t n > struct combination< n, n >{ enum{ val = 1 }; }; template< int n > struct Bernoulli{ private: template< int nn > struct Sum{ typedef std::ratio_add< std::ratio_multiply< std::ratio< combination< n + 1, nn >::val, 1 >, typename Bernoulli< nn >::val >, typename Sum< nn - 1 >::val > val; }; template<> struct Sum< 0 >{ typedef std::ratio< 1, 1 > val; }; public: typedef typename std::ratio_divide< typename Sum< n - 1 >::val, std::ratio< -(n + 1), 1 > > val; }; template<> struct Bernoulli< 0 >{ typedef std::ratio< 1, 1 > val; };
計算方法は以下の通り。結果はstd::ratioの型で出てきます。
typedef Bernoulli< 10 >::val num; std::cout << num::num << " / " << num::den << std::endl; }
最初の3つはコンビネーション部分です。定番なテンプレートメタプログラミングなので特筆することはないかと思います。
さて面倒なのはベルヌーイ数本体です。何が面倒かというとforによるシグマ計算があります。テンプレートメタプログラミングでは変数が全て定数になるので変数を用意してそこに足していくという方法は使えませんので、ループを再起関数にして足し算していくのが定番ですね。privateに押し込めているsum構造体がそれに相当します。
さて、実際に計算したら意外とコンパイルが速かったです。なんでかな?ひょっとして静的だからテーブルがコンパイル中に出来てるのかな?まあ根拠のない想像ですが。
久々にテンプレートメタプログラミングでかなりてこずりました。いやどちらかというとratioでてこずったというか・・・。なれないライブラリは時間がかかります。でもまあ練習になったのでよしとします。
ちなみにこのプログラム、VS2012だとビルドが通りませんでした。理由はよく分かりませんがおそらくstd::ratio_divideにバグがあるのでは?だれか確認よろしくお願いします(ぇ