fc2ブログ
ヽ|∵|ゝ(Fantom) の 開発blog? ホーム » Java »【Java】1000000!のような巨大な階乗計算をする [任意精度整数(多倍長整数)]

【Java】1000000!のような巨大な階乗計算をする [任意精度整数(多倍長整数)]  


 桁あふれ(オーバフロー)をすぐしてしまうのはやはり階乗の計算。前回せっかく任意精度整数BigInteger の計算をまとめたので、簡単な例として100!~1000000! くらいの巨大な階乗を求めてみよう。コードの書き方はいくつかあると思うが、一番簡単で高速だった for ループを用いた方法で書いてみる。

●任意精度整数(多倍長整数)[BigInteger]での階乗計算
import java.math.BigInteger;

/**<h1>任意精度整数で、階乗 (n!) を求める</h1>
* <p>負の値は 1 (不正値) となる。</p>
* @param n : n! (負の値は不可)
* @return<b>BigInteger</b> : 階乗 (n!) [toString()で文字列になる]
*/
public static final BigInteger factorial(final int n) {
BigInteger sum = BigInteger.ONE;
for (int i = 2; i <= n; i++) {
sum = sum.multiply(new BigInteger(String.valueOf(i)));
}
return sum;
}


//メインでは...
int n = 10000;
BigInteger b = factorial(n);
System.out.println(b); //文字列[toString()]で表示

 コードに関する説明はあまり必要ないだろう。for ループで 2~n (1は変わらないので除外) までを掛けあわせているだけだ。ちなみに同じコードを long などのプリミティブ型で書くと、20! までしか計算できない。21! を計算するとオーバフローを起こし、負の値になってしまう(Java には unsigned もない)。double で計算すれば負の値は回避できるが、上から17桁より以降は丸められ、下の桁はすべて 0 になってしまう(指数形式で大まかな値で良ければ使える)。

 また、負の階乗は計算できない。特に例外処理を入れてないが、必要あれば、n < 0 のときに、ArithmeticException などをスローするように付け加えても良いだろう。

 あと、階乗計算のコード例として再帰を用いたものも多いが、あれは「再帰とはどういうものか?」というような学習用コードなので、実際の階乗計算のような、単純な計算には使わない方が良いだろう。再帰で書いた場合の欠点として、無駄にスタックメモリを喰うことと(つまり携帯[スマホ]などメモリの少ない端末には向かない)、実行時間がかかることが考えられる。ちなみに上記の階乗計算をそのまま再帰で書き直すと、13800! 以上の計算でオーバフロー(Stack Overflow)を起こし、計算時間は約2倍かかる

 参考までに私の環境(Windows10/Intel Core i5 x64 2.9GHz)での BigInteger の階乗計算にかかった時間を挙げておこう。オンラインジャッジでもあまり変わらない(オンラインジャッジの方が 100ms ほど速いくらい)。

100! で、0 [ms] (測定不可)
1000! で、3 [ms]
10000! で、46 [ms]
100000! で、2822 [ms]
1000000! で、356593 [ms] (約6分)

 非常に重いので、10万回を超える演算には注意した方が良いかもしれない。プロコンには向かないかも。もし、20! 以内の計算で良ければプリミティブ型で階乗計算した方が良いだろう(プロコン問題の場合は桁数がカットできる条件が含まれていたら、除算や剰余を使ってオーバフローを防げば100万回行ける)。測定してみたら、プリミティブ型の方が任意精度整数より約35倍速かった(笑)。また、任意精度符号付き10進数の「BigDecimal」の方が BigInteger より3割くらい速いようだ。

 計算結果は BigInteger.toString() で表示できるが、10000! を超えると非常に大きな値になるので、コンソールでは表示できない。値を確認したければ、ファイルなどに出力すると良いだろう。以前に作った「ローカルシステムにテキストファイルを保存」を使えば簡単に出力できる。

 実際に出力してみると、
100! (158桁)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

1000! (2568桁)
402387260077093773543702433923003985719374864210714632543799910429938512398629020592044208486969404800479988610197196058631666872994808558901323829669944590997424504087073759918823627727188732519779505950995276120874975462497043601418278094646496291056393887437886487337119181045825783647849977012476632889835955735432513185323958463075557409114262417474349347553428646576611667797396668820291207379143853719588249808126867838374559731746136085379534524221586593201928090878297308431392844403281231558611036976801357304216168747609675871348312025478589320767169132448426236131412508780208000261683151027341827977704784635868170164365024153691398281264810213092761244896359928705114964975419909342221566832572080821333186116811553615836546984046708975602900950537616475847728421889679646244945160765353408198901385442487984959953319101723355556602139450399736280750137837615307127761926849034352625200015888535147331611702103968175921510907788019393178114194545257223865541461062892187960223838971476088506276862967146674697562911234082439208160153780889893964518263243671616762179168909779911903754031274622289988005195444414282012187361745992642956581746628302955570299024324153181617210465832036786906117260158783520751516284225540265170483304226143974286933061690897968482590125458327168226458066526769958652682272807075781391858178889652208164348344825993266043367660176999612831860788386150279465955131156552036093988180612138558600301435694527224206344631797460594682573103790084024432438465657245014402821885252470935190620929023136493273497565513958720559654228749774011413346962715422845862377387538230483865688976461927383814900140767310446640259899490222221765904339901886018566526485061799702356193897017860040811889729918311021171229845901641921068884387121855646124960798722908519296819372388642614839657382291123125024186649353143970137428531926649875337218940694281434118520158014123344828015051399694290153483077644569099073152433278288269864602789864321139083506217095002597389863554277196742822248757586765752344220207573630569498825087968928162753848863396909959826280956121450994871701244516461260379029309120889086942028510640182154399457156805941872748998094254742173582401063677404595741785160829230135358081840096996372524230560855903700624271243416909004153690105933983835777939410970027753472000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

10000! (35660桁)
100000! (456574桁)
1000000! (5565709桁)
・・・
となる。

 ググってみると計算速度を上げるために、マルチコアやマルチスレッド、複数台の並列処理などをやっている例もあったが…、まぁ、別のアルゴリズムも含めて、色々やってみると面白いと思う。そういう研究結果を公開してくれると、勉強になるので非常に有り難い(笑)。


(関連記事)
【Java】プリミティブ型での階乗計算
【Java】任意精度整数(多倍長整数)で演算する
【Java】順列の数を求める
【Java】組合せの数を求める
【Java】パスカルの三角形を使って組合せを求める


関連記事
スポンサーサイト



category: Java

thread: プログラミング

janre: コンピュータ

tag: 算術関数  検証 
tb: 0   cm: --


トラックバック

トラックバックURL
→http://fantom1x.blog130.fc2.com/tb.php/198-f8796c75
この記事にトラックバックする(FC2ブログユーザー)

プロフィール

Social

検索フォーム

全記事一覧

カテゴリ

ユーザータグ

最新記事

リンク

PR

▲ Pagetop