SICP第一章の練習1.21〜1.28、素数問題とフェルマ検査。
問題
練習1.21:smallest-divisor 方法で、199、1999、19999 の最少因子を求める。
練習1.22:上記 smallest-divisor で素数を求める時の時間を計る。
練習1.23:改良されたsmallest-divisor。
練習1.24:フェルマ検査。
練習1.25:expmod に、fast-expt を直接使う場合。
練習1.26:square を使わない時のコスト。
練習1.27:Carmichael 数。
練習1.28:Miller-Rabin検査(騙されないフェルマ検査)。
解答
各練習の解答ソースを個別で書きます(単独で実行できない)。
*これからのソースは Tango ライブラリを利用します。コンパイルするには、Tango をインストールしなければ成りません。Tango については こちら を参照します。
練習1.21の解答
-
-
-
-
- float _sqrt;
-
- int smallestDivisor(int n) {
- _sqrt = sqrt(cast(float) n);
- return findDivisor(n, 2);
- }
-
- int findDivisor(int n, int testDivisor) {
- if (testDivisor > _sqrt) {
- return n;
- }
- if (n % testDivisor == 0) {
- return testDivisor;
- }
- testDivisor++;
- return findDivisor(n, testDivisor);
- }
-
- void main() {
- int ret;
-
-
- Stdout("---- 1.21 ----").newline;
- ret = smallestDivisor(199);
- Stdout.formatln("smallestDivisor(199) = {}", ret);
-
- ret = smallestDivisor(1999);
- Stdout.formatln("smallestDivisor(1999) = {}", ret);
-
- ret = smallestDivisor(19999);
- Stdout.formatln("smallestDivisor(19999) = {}", ret);
- }
import tango.io.Stdout; import tango.math.Math; import tango.time.StopWatch; /** * SICP練習1.21〜1.28。 */ /** * 練習1.21。 * smallest-divisorで最少因子を求める。 */ float _sqrt; StopWatch _sw; int smallestDivisor(int n) { _sqrt = sqrt(cast(float) n); return findDivisor(n, 2); } int findDivisor(int n, int testDivisor) { if (testDivisor > _sqrt) { return n; } if (n % testDivisor == 0) { return testDivisor; } testDivisor++; return findDivisor(n, testDivisor); } // 素数かどうか bool prime(int n) { int sd = smallestDivisor(n); return sd == n; } /** * 練習1.22。 * smallestDivisor の時間コストを計る。 */ bool timedPrimeTest(int n) { _sw.start(); if (prime(n)) { _sw.stop(); ulong elapsed = _sw.microsec(); Stdout.format("{} ***, elapsed: {}", n, elapsed).newline; return true; } _sw.stop(); Stdout.format("{}", n).newline; return false; } void searchForPrimes(int start) { int s; if (start % 2 == 0) { s = start + 1; } else { s = start; } int max = s * 10; for (int i = s; i += 2; i < max) { if (timedPrimeTest(i)) { return; } } } void main() { int ret; // for 1.21 Stdout("---- 1.21 ----").newline; ret = smallestDivisor(199); Stdout.format("smallestDivisor(199) = {}", ret).newline; ret = smallestDivisor(1999); Stdout.format("smallestDivisor(1999) = {}", ret).newline; ret = smallestDivisor(19999); Stdout.format("smallestDivisor(19999) = {}", ret).newline; // for 1.22 Stdout.newline; Stdout("---- 1.22 ----").newline; searchForPrimes(1000); searchForPrimes(10000); searchForPrimes(100000); searchForPrimes(1000000); }
実行結果。
---- 1.21 ----
smallestDivisor(199) = 199
smallestDivisor(1999) = 1999
smallestDivisor(19999) = 7 |
練習1.22の解答
-
-
-
-
-
-
- bool prime(int n) {
- int sd = smallestDivisor(n);
- return sd == n;
- }
-
-
- StopWatch _sw;
- bool timedPrimeTest(int n) {
- _sw.start();
- if (prime(n)) {
- _sw.stop();
- ulong elapsed = _sw.microsec();
- Stdout.formatln("{} ***, elapsed: {}", n, elapsed);
- return true;
- }
- _sw.stop();
- Stdout.(n).newline;
- return false;
- }
-
- void searchForPrimes(int start) {
- int s;
- if (start % 2 == 0) {
- s = start + 1;
- } else {
- s = start;
- }
- int max = s * 10;
- for (int i = s; i += 2; i < max) {
- if (timedPrimeTest(i)) {
- return;
- }
- }
- }
-
- void main() {
-
- Stdout.newline;
- Stdout("---- 1.22 ----").newline;
- searchForPrimes(1000);
- searchForPrimes(10000);
- searchForPrimes(100000);
- searchForPrimes(1000000);
- }
/** * 練習1.22。 * smallestDivisor の時間コストを計る。 */ // 素数かどうか bool prime(int n) { int sd = smallestDivisor(n); return sd == n; } // 時間を計る StopWatch _sw; bool timedPrimeTest(int n) { _sw.start(); if (prime(n)) { _sw.stop(); ulong elapsed = _sw.microsec(); Stdout.format("{} ***, elapsed: {}", n, elapsed).newline; return true; } _sw.stop(); Stdout.format("{}", n).newline; return false; } void searchForPrimes(int start) { int s; if (start % 2 == 0) { s = start + 1; } else { s = start; } int max = s * 10; for (int i = s; i += 2; i < max) { if (timedPrimeTest(i)) { return; } } } void main() { // for 1.22 Stdout.newline; Stdout("---- 1.22 ----").newline; searchForPrimes(1000); searchForPrimes(10000); searchForPrimes(100000); searchForPrimes(1000000); }
実行結果。
---- 1.22 ----
1003
1005
1007
1009 ***, elapsed: 3
10003
10005
10007 ***, elapsed: 7
100003 ***, elapsed: 19
1000003 ***, elapsed: 53 |
smallest-divisor で素数を求める時の時間コストは O(平方根(n)) です。10 の平方根は 3.16 です。上記結果、O(10,000)/O(1,000) = 7/3 = 2.33、O(1,000,000)/O(100,000) = 53/19 = 2.79。n が大きい時、予想結果に近いことが分かります。
練習1.23の解答
-
-
-
-
- int smallestDivisor2(int n) {
- _sqrt = sqrt(cast(float) n);
- return findDivisor2(n, 2);
- }
-
- int findDivisor2(int n, int testDivisor) {
- if (testDivisor > _sqrt) {
- return n;
- }
- if (n % testDivisor == 0) {
- return testDivisor;
- }
- if (testDivisor == 2) {
- testDivisor = 3;
- } else {
- testDivisor = testDivisor + 2;
- }
- return findDivisor2(n, testDivisor);
- }
-
- bool prime2(int n) {
- int sd = smallestDivisor2(n);
- return sd == n;
- }
-
- bool timedPrimeTest2(int n) {
- _sw.start();
- if (prime2(n)) {
- _sw.stop();
- ulong elapsed = _sw.microsec();
- Stdout.formatln("{} ***, elapsed: {}", n, elapsed);
- return true;
- }
- _sw.stop();
- Stdout(n).newline;
- return false;
- }
-
- void searchForPrimes2(int start) {
- int s;
- if (start % 2 == 0) {
- s = start + 1;
- } else {
- s = start;
- }
- int max = s * 10;
- int count = 0;
- for (int i = s; i += 2; i < max) {
- if (timedPrimeTest2(i)) {
-
- if (++count == 3) {
- return;
- }
- }
- }
- }
-
- void main() {
-
- Stdout.newline;
- Stdout("---- 1.23 ----").newline;
- searchForPrimes2(1000);
- searchForPrimes2(10000);
- searchForPrimes2(100000);
- searchForPrimes2(1000000);
- }
/** * 練習1.23。 * 改良されたsmallest-divisor */ int smallestDivisor2(int n) { _sqrt = sqrt(cast(float) n); return findDivisor2(n, 2); } int findDivisor2(int n, int testDivisor) { if (testDivisor > _sqrt) { return n; } if (n % testDivisor == 0) { return testDivisor; } if (testDivisor == 2) { testDivisor = 3; } else { testDivisor = testDivisor + 2; } return findDivisor2(n, testDivisor); } bool prime2(int n) { int sd = smallestDivisor2(n); return sd == n; } bool timedPrimeTest2(int n) { _sw.start(); if (prime2(n)) { _sw.stop(); ulong elapsed = _sw.microsec(); Stdout.formatln("{} ***, elapsed: {}", n, elapsed); return true; } _sw.stop(); Stdout(n).newline; return false; } void searchForPrimes2(int start) { int s; if (start % 2 == 0) { s = start + 1; } else { s = start; } int max = s * 10; int count = 0; for (int i = s; i += 2; i < max) { if (timedPrimeTest2(i)) { // 三つまで探す if (++count == 3) { return; } } } } void main() { // for 1.23 Stdout.newline; Stdout("---- 1.23 ----").newline; searchForPrimes2(1000); searchForPrimes2(10000); searchForPrimes2(100000); searchForPrimes2(1000000); }
実行結果。
---- 1.23 ----
1003
1005
1007
1009 ***, elapsed: 1
1011
1013 ***, elapsed: 2
1015
1017
1019 ***, elapsed: 2
10003
10005
10007 ***, elapsed: 3
10009 ***, elapsed: 3
10011
10013
10015
10017
10019
10021
10023
10025
10027
10029
10031
10033
10035
10037 ***, elapsed: 3
100003 ***, elapsed: 7
100005
100007
100009
100011
100013
100015
100017
100019 ***, elapsed: 7
100021
100023
100025
100027
100029
100031
100033
100035
100037
100039
100041
100043 ***, elapsed: 8
1000003 ***, elapsed: 30
1000005
1000007
1000009
1000011
1000013
1000015
1000017
1000019
1000021
1000023
1000025
1000027
1000029
1000031
1000033 ***, elapsed: 21
1000035
1000037 ***, elapsed: 22
|
改良される前(練習1.22)より、半分に近い早くなりました。他の判定を入れて、時間がかかるので、半分には行かないですね。
練習1.24の解答
-
-
-
-
-
- int expmod(int base, int exp, int m) {
- if (exp == 0) {
- return 1;
- }
- if (exp % 2 == 0) {
- return square(expmod(base, exp / 2, m)) % m;
-
- } else {
- return (base * expmod(base, exp - 1, m)) % m;
- }
- }
-
- int square(int n) {
- return n * n;
- }
-
- bool fermatTest(int n) {
- int a = Kiss.shared.toInt(1, n - 1) + 1;
- int ret = expmod(a, n, n);
- return ret == a;
- }
-
- bool fastPrime(int n, int times) {
- if (times == 0) {
- return true;
- }
- if (fermatTest(n)) {
- return fastPrime(n, times - 1);
- } else {
- return false;
- }
- }
-
- void searchForPrimes3(int start) {
- int s;
- if (start % 2 == 0) {
- s = start + 1;
- } else {
- s = start;
- }
- int max = s * 10;
- int count = 0;
- for (int i = s; i += 2; i < max) {
- _sw.start();
- if (fastPrime(i, 3)) {
- _sw.stop();
- ulong elapsed = _sw.microsec();
- Stdout.formatln("{} ***, elapsed: {}", i, elapsed);
-
- if (++count == 3) {
- return;
- }
- }
- _sw.stop();
- Stdout(i).newline;
- }
- }
-
- void main() {
-
- Stdout.newline;
- Stdout("---- 1.24 ----").newline;
- searchForPrimes3(10);
- searchForPrimes3(100);
- searchForPrimes3(1000);
- searchForPrimes3(10000);
- }
/** * 練習1.24 フェルマ検査。 * Scheme 言語では計算できるらしいですが。 * exp が大きい時(100000付近)、base の exp 乗はあまりにも大きくて、計算出来なくなる。 */ int expmod(int base, int exp, int m) { if (exp == 0) { return 1; } if (exp % 2 == 0) { return square(expmod(base, exp / 2, m)) % m; } else { return (base * expmod(base, exp - 1, m)) % m; } } int square(int n) { return n * n; } bool fermatTest(int n) { int a = Kiss.shared.toInt(1, n - 1) + 1; int ret = expmod(a, n, n); return ret == a; } bool fastPrime(int n, int times) { if (times == 0) { return true; } if (fermatTest(n)) { return fastPrime(n, times - 1); } else { return false; } } void searchForPrimes3(int start) { int s; if (start % 2 == 0) { s = start + 1; } else { s = start; } int max = s * 10; int count = 0; for (int i = s; i += 2; i < max) { _sw.start(); if (fastPrime(i, 3)) { _sw.stop(); ulong elapsed = _sw.microsec(); Stdout.formatln("{} ***, elapsed: {}", i, elapsed); // 三つまで探す if (++count == 3) { return; } } _sw.stop(); Stdout(i).newline; } } void main() { // for 1.24 Stdout.newline; Stdout("---- 1.24 ----").newline; searchForPrimes3(10); searchForPrimes3(100); searchForPrimes3(1000); searchForPrimes3(10000); }
実行結果。
---- 1.24 ----
13 ***, elapsed: 5
13
15
17 ***, elapsed: 4
17
19 ***, elapsed: 2
103 ***, elapsed: 3
103
105
107 ***, elapsed: 3
107
109 ***, elapsed: 3
1003
1005
1007
1009 ***, elapsed: 4
1009
1011
1013 ***, elapsed: 4
1013
1015
1017
1019 ***, elapsed: 4
10003
10005
10007 ***, elapsed: 5
10007
10009 ***, elapsed: 5
10009
10011
10013
10015
10017
10019
10021
10023
10025
10027
10029
10031
10033
10035
10037 ***, elapsed: 4
|
コストの増加は大体 O(log(n)) と成っていることが分かります。
練習1.25の解答
fast-expt(base, exp) が大きすぎな数になるため、上手く行かないでしょう。
練習1.26の解答
expmod(base, exp/2, m) * expmod(base, exp/2, m) の所は、余計な計算をしているため。
an = an/2 * an/2 = an/4 * an/4 * an/4 * an/4 * = ... = a * a * ... * a (n 回) となってしまい、コストは O(n) です。
練習1.27の解答
-
- * Carmichael 数。
-
- void carmichael(int n) {
- bool fermat = true;
- int ret;
- for (int i = 1; i < n; i++) {
- ret = expmod(i, n, n);
- if (ret != i) {
- fermat = false;
- break;
- }
- }
- if (fermat) {
- Stdout.formatln("{} ***", n);
- } else {
- Stdout(n).newline;
- }
- }
-
- void main() {
-
- Stdout.newline;
- Stdout("---- 1.27 ----").newline;
- carmichael(561);
- carmichael(1105);
- carmichael(1729);
- carmichael(2465);
- carmichael(2821);
- carmichael(6601);
-
- carmichael(6789);
- }
/** * 練習1.27。 */ void carmichael(int n) { bool fermat = true; int ret; for (int i = 1; i < n; i++) { ret = expmod(i, n, n); if (ret != i) { fermat = false; break; } } if (fermat) { Stdout.formatln("{} ***", n); } else { Stdout(n).newline; } } void main() { // for 1.27 Stdout.newline; Stdout("---- 1.27 ----").newline; carmichael(561); carmichael(1105); carmichael(1729); carmichael(2465); carmichael(2821); carmichael(6601); // 6789 は Carmichael でない carmichael(6789); }
実行結果。
---- 1.27 ----
561 ***
1105 ***
1729 ***
2465 ***
2821 ***
6601 ***
6789
|
完全に騙されましたね。6789は Carmichael 数でないため、フェルマ検査は騙されていない。
練習1.28の解答は割愛します。 |