LoginSignup
24
12

More than 5 years have passed since last update.

数値計算を行う場合、アルゴリズムを変えてみるとぐっと速度が上がることがあります。

階乗とは

念のため説明しておきますが、「$x$の階乗」は「1から$x$までの数をかけ合わせた数」のことで1、「$x!$」と書き表します。ビックリマークがついているように、急速に大きくなっていきます。

なお、あっという間に64ビット整数も超えてしまうので、多倍長整数を前提とします。

掛け算の性質

どうあがいても大量の掛け算は避けられないので、まずはその性質について吟味しておきます。

以前Rubyで行った試験の結果から、「掛け算の所要時間は大きい方の桁数×小さい方の桁数の平方根に比例する」ことが分かりました(Karatuba法やToom-3法を使う場合には、ほぼ共通する結果だと思います)。

ということで、「できるだけ同じ大きさの数同士で掛け算を繰り返す」ようにするだけでも、かなり高速化しました。

2の成分

高速な計算を行う上で、「2の累乗」と「奇数成分」を分離して計算を進めることが有力な方法となります。というのも、コンピューターが2進法で動くものなので、「2の累乗をかける」ことは単なる「ビットシフト」というごく軽い演算となるからです。

そして、$n! = 2^x\times (2k+1)$ というように分けた場合、$x$は「$n$から、『$n$を2進法で表したときの1の数』を引いたもの」となります(この事実自体は、数学的帰納法で証明できます)。「1の数」はx64に専用命令が積まれるぐらいに最適化がなされているので、階乗の2の累乗成分は高速に計算可能です。

奇数パートの計算法その1 素因数分解

いちばんシンプルに考えて、「階乗を素因数分解してしまう」という方法があります。例えば、「100!に含まれる3の累乗はどれだけか」を考えてみると、

  • 1~100で、3で割り切れる数…100/3→33個
  • 1~100で、$3^2$で割り切れる数…33/3→11個
  • 1~100で、$3^3$で割り切れる数…11/3→4個
  • 1~100で、$3^4$で割り切れる数…4/3→1個

ということで、33+11+4+1=49となります。これは100まですべての数を当たらなくてもいいので、効率的に各素因数について計算できます。

そのうえで、累乗して掛け合わせていくのですが、これまた累乗してからかけるより、バイナリ法などで全部の素数について一気に累乗&掛け算とするほうが高速になります。

この手法は、素数テーブルの生成時間を無視できるならかなり高速です。

奇数パートの計算法その2 奇数以上に分解しない

では、素因数分解のコストを払いたくないという状況では、どのような方法を使えるでしょうか。素因数分解しない「奇数」単体で考えても、同じものは複数回登場します。

  • 100/2=50→51~99までの奇数は1回だけ
  • 50/2=25→27~49の奇数は2回(27、27×2)
  • 25/2=12.5→13~25の奇数は3回(13、13×2、13×4)
  • 12/2=6→7~11の奇数は4回(7、14、28、56)
  • 6/2=3→5は5回(5、10、20、40、80)
  • 3/2=1→3は6回(3、6、12、24、48、96)

このように分類したところで、また適宜掛け合わせれば、そこそこの速度で動きます。


  1. なお、$0!=1$ という約束になっています。 

24
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
12