aのn乗をmで割った余り
タイトルのやつを3種類書きました
以下ソースです
#include<stdio.h> #include<time.h> int exp_mod(int a,int n,int m) { long int res = 1; for(;n>0;n--) res = (res * a) % m; return (int)res % m; } int exp_mod_fixed(int a,int n, int m) { long int res = 1; if(n==0) return 1; if(n%2){ res = exp_mod(a,n/2,m) % m; res = res*res % m; res = res*a % m; }else{ res = exp_mod(a,n/2,m) % m; res = res*res % m; } return (int)res; } int exp_mod_binaly(int a,int n,int m) { long int res=1; long int exp = a; while(n > 0){ if(n & 1) res = (exp * res) % m; exp = (exp*exp) % m; n >>= 1; } return (int)res; } int main() { int a,n,m; clock_t start,end; printf(" base : \t"); scanf("%d",&a); printf("exponent : \t"); scanf("%d",&n); printf(" mod : \t"); scanf("%d",&m); start = clock(); printf(" result : \t %d\n",exp_mod(a,n,m)); end = clock(); printf("calc time : %.2f sec by exp_mod\n",(double)(end-start)/CLOCKS_PER_SEC); start = clock(); printf(" result : \t %d\n",exp_mod_fixed(a,n,m)); end = clock(); printf("calc time : %.2f sec by exp_mod_fixed\n",(double)(end-start)/CLOCKS_PER_SEC); start = clock(); printf(" result : \t %d\n",exp_mod_binaly(a,n,m)); end = clock(); printf("calc time : %.2f sec by exp_binaly\n",(double)(end-start)/CLOCKS_PER_SEC); return 0; }
実行例
base : 2526 exponent : 353 mod : 777 result : 174 calc time : 0.00 sec by exp_mod result : 174 calc time : 0.00 sec by exp_mod_fixed result : 174 calc time : 0.00 sec by exp_binaly base : 2012123499 exponent : 353255243 mod : 5352552 result : 795627 calc time : 4.67 sec by exp_mod result : 795627 calc time : 2.34 sec by exp_mod_fixed result : 795627 calc time : 0.00 sec by exp_binaly base : 2000000000 exponent : 2123942242 mod : 4255 result : 1020 calc time : 28.72 sec by exp_mod result : 1020 calc time : 14.23 sec by exp_mod_fixed result : 1020 calc time : 0.00 sec by exp_binaly
3種類の関数を動作させ、計測時間を印字させています。
なかなか面白い結果になりました。
上のexp_modは、「aのn乗をmで割った余りを求めたい!」と思ったときに誰もが真っ先に思いつくコードだと思います。計算の後から剰余をとっても先に剰余をとっても答えが変わらない性質を使っています。オーバーフローを防ぐために計算過程はlong intに格納しています。たかだかint型の掛け算、余り算なので、long intで収まります。しかし、上の実行例のように2000000000が入力されたときは単純に2000000000回ループが回り、計算時間が見ての通り大変なことになっています。
真ん中のexp_mod_fixedは、べき乗の演算がもつ特性を使っています。exp値が2で割れるとき、a/2は整数となり、a^exp = (a^exp/2)*(a^exp/2)と分解でき、2で割れないとき、a^exp = (a^exp/2)*(a^exp/2)*a(小数点切り捨て)とかけるのを利用して、再帰的にもとめています。exp値が指数関数的に減少するので計算効率はいいと思ってたんですけど、なんだかそうでもない感じですね...。個人的には好きなコードです。
下のexp_mod_binalyが今回の目玉です。あるexpを2進数表記にし、下のビットから見ていったときにnビット目が1だったらa^nをresにかける、というだけのシンプルなものです。aのn乗をmで割った余りが知りたいので、a^nをresにかけたものをmで割ったものを新しくresとしています。指数法則でべき乗同士の掛け算はexp値の足し算となることを利用しています。aが倍々に増えていくので、掛け算の回数も少なくてすみます。これはint型なので、たかだか32回の計算で終わります。
追記 (2017/07/08)
exp_mod_fixed関数に誤りがありました
int exp_mod_fixed(int a,int n, int m) { long int res = 1; if(n==0) return 1; if(n%2){ res = exp_mod_fixed(a,n/2,m) % m; res = res*res % m; res = res*a % m; }else{ res = exp_mod_fixed(a,n/2,m) % m; res = res*res % m; } return (int)res; }
元のコードだとexp_mod_fixed内でexp_modのほうを呼び出してしまっていたので実行が遅かったみたいです
上のコードで、しっかりexp_mod_fixed内で自分自身を呼び出す再帰構造になおしました。これで試してみると、
実行結果
base : 2000000000 exponent : 2109424214 mod : 4344113 result : 1763220 calc time : 27.95 sec by exp_mod result : 1763220 calc time : 0.00 sec by exp_mod_fixed result : 1763220 calc time : 0.00 sec by exp_binaly
やっぱ速い