先の記事のプログラムで、平方数の判定ということを考えている。
ある自然数nが平方数かの判定をプログラミングで厳密にやろうとすると、
例えばC言語風に書けば、
int m,n ;
/* なんらかの処理 */
m = sqrt(n);
if ( m*m == n ) {
/* nは平方数 */
} else {
/* nは平方数ではない */
}
の様にnのルートをとって、整数型変数に代入し、それを2乗したものと比較する。
数回と少ない判定であれば、これで問題はないのだが、片っ端から多くを判定するには、大きい数であればあるほど、平方数である確率は低いので無駄な処理が増えていく。
例えば、nを素因数分解して、素因数の個数を数えたとする。
平方数であれば、素因数の個数は偶数個である。
ただ、この方法では素因数分解に時間かかりすぎるので、使えない。
例えば、そもそも数が大きくなればなるほど平方数は少なくなるのだから、
m2 < n < (m+1)2
となるような、自然数mが見つかれば、nは平方数ではないとなる。
これも、mをすばやく見つけられればよいのだが、なかなか難しいのかもしれない。
では、どのようなことをするのが得策なのだろうか。
そこで登場するのが、数論、整数論である。
例えば、平方数の1の位は、0、1、4、5、6、9の6通りである。
逆に言えば、2、3、7、8ならば平方数ではない。
プログラム的には4通り書ける。
【パターン1】
m=n%10;
if ( m==0 || m==1 || m==4 || m==5 || m==6 || m==9 ) {
/* 平方数の可能性がある */
} else {
/* 平方数の可能性がない */
}
【パターン2】
m=n%10;
if ( m==2 || m==3 || m==7 || m==8 ) {
/* 平方数の可能性がない */
} else {
/* 平方数の可能性がある */
}
パターン1、2、どちらもor演算子で連ねているが、パターン1は6種、パターン2は4種の比較なので、パターン2の方が優秀であると言える。
【パターン3】
m=n%10;
if ( m!=0 && m!=1 && m!=4 && m!=5 && m!=6 && m!=9 ) {
/* 平方数の可能性がない */
} else {
/* 平方数の可能性がある */
}
【パターン4】
m=n%10;
if ( m!=2 && m!=3 && m!=7 && m!=8 ) {
/* 平方数の可能性がある */
} else {
/* 平方数の可能性がない */
}
パターン3と4を比べるならば、パターン4の方が比較が少ないので優秀と言えるが、パターン2と4を比べたらどうだろうか。
or演算子で連ねた場合、全ての比較が終わらなければならないのに対し、if文が終了しない。
and演算子で連ねた場合、前方から比較し、falseになればelseに行く。
つまり、andの方が結果的に処理が速くなるのではないだろうか。
まぁ、コンパイラが優秀であればという条件付きであるが。
どちらにしても、この方法であれば、4/10=40%を枝刈り出来ることを意味していることには変わりがない。
例えば、もう少し効率が良いものとして、
m=n%9;
if ( m!=0 && m!=1 && m!=4 && m!=7 ) {
/* 平方数の可能性がない */
} else {
/* 平方数の可能性がある */
}
で、5/9≒55.5555%を枝刈り出来る。
プログラミングということを踏まえれば、256の剰余を取るということも考えられる。
この場合、平方数の可能性があるのは、
{ 0, 1, 4, 9, 16, 17, 25, 33, 36, 41, 49, 57, 64, 65, 68, 73, 81, 89, 97, 100, 105, 113, 121, 129, 132, 137, 144, 145, 153, 161, 164, 169, 177, 185, 193, 196, 201, 209, 217, 225, 228, 233, 241, 249 }
の44種となり、(256-44)/256=212/256=82.8125%の枝刈りが出来る。
複数を組み合わせて、枝刈りの比率を高めることも考えられる。
では、どんな組み合わせが効率が良いのだろうか。
まず、奇数(2m-1)の剰余で調べると、
(2m-1)%
%1 { 0 } 1個
%3 { 0, 1 } 2個
%5 { 0, 1, 4 } 3個
%7 { 0, 1, 2, 4 } 4個
%9 { 0, 1, 4, 7 } 4個
%11 { 0, 1, 3, 4, 5, 9 } 6個
%13 { 0, 1, 3, 4, 9, 10, 12 } 7個
%15 { 0, 1, 4, 6, 9, 10 } 6個
%17 { 0, 1, 2, 4, 8, 9, 13, 15, 16 } 9個
%19 { 0, 1, 4, 5, 6, 7, 9, 11, 16, 17 } 10個
%21 { 0, 1, 4, 7, 9, 15, 16, 18 } 8個
%23 { 0, 1, 2, 3, 4, 6, 8, 9, 12, 13, 16, 18 } 12個
%25 { 0, 1, 4, 6, 9, 11, 14, 16, 19, 21, 24 } 11個
%27 { 0, 1, 4, 7, 9, 10, 13, 16, 19, 22, 25 } 11個
%29 { 0, 1, 4, 5, 6, 7, 9, 13, 16, 20, 22, 23, 24, 25, 28 } 15個
…
のようになり、
・奇素数であれば、m個
・合成数はあれば、m個未満
という性質が見えてくる。
256の剰余が82.8125%と効率が良いので、これをベースに、奇数を組み合わせて、組み合わせた効率を調べてみる。
母数を16ビット全体の0~65535で調査する。
%256 %3 58024/65536≒88.53759% 44+2=46個
%256 %5 58775/65536≒89.68353% 44+3=47個
%256 %7 59097/65536≒90.17486% 44+4=48個
%256 %9 60526/65536≒92.35534% 44+4=48個
%256 %11 59388/65536≒90.61889% 44+6=50個
%256 %13 59467/65536≒90.73944% 44+7=51個
%256 %15 61026/65536≒93.11828% 44+6=50個
%256 %17 59569/65536≒90.89508% 44+9=53個
%256 %19 59604/65536≒90.94848% 44+10=54個
%256 %21 61239/65536≒93.44329% 44+8=52個
%256 %23 59655/65536≒91.02630% 44+12=56個
%256 %25 60575/65536≒92.43011% 44+11=55個
%256 %27 60943/65536≒92.99163% 44+11=55個
%256 %29 59706/65536≒91.10412% 44+15=59個
…
%256 & %9、%256 & %15、のコスパが良いことがわかる。
更に追加してみると、
%256 %9 %3 60526/65536≒92.35534% 44+4+2=50個 ←無意味
%256 %9 %5 62526/65536≒95.40710% 44+4+3=51個
%256 %9 %7 62668/65536≒95.62377% 44+4+4=52個
%256 %9 %11 62797/65536≒95.82061% 44+4+6=54個
%256 %9 %13 62831/65536≒95.87249% 44+4+7=55個
%256 %9 %15 62526/65536≒95.40710% 44+4+6=55個 ←無意味
%256 %9 %17 62875/65536≒95.93963% 44+4+9=57個
%256 %9 %19 62894/65536≒95.96862% 44+4+10=58個
%256 %9 %21 62668/65536≒95.62377% 44+4+8=56個 ←無意味
%256 %9 %23 62915/65536≒96.00067% 44+4+12=60個
%256 %9 %25 63322/65536≒96.62170% 44+4+11=59個
%256 %9 %27 60943/65536≒92.99163% 44+4+11=59個
%256 %9 %29 92940/65536≒96.03881% 44+4+15=63個
…
%256 %15 %3 61026/65536≒93.11828% 44+6+2=52個 ←無意味
%256 %15 %5 61026/65536≒93.11828% 44+6+3=53個 ←無意味
%256 %15 %7 62952/65536≒96.05712% 44+6+4=54個
%256 %15 %9 62526/65536≒95.40710% 44+6+4=54個 ←無意味
%256 %15 %11 63068/65536≒96.23413% 44+6+6=56個
%256 %15 %13 63101/65536≒96.28448% 44+6+7=57個
%256 %15 %17 63143/65536≒96.34857% 44+6+9=59個
%256 %15 %19 63159/65536≒96.37298% 44+6+10=60個
%256 %15 %21 62952/65536≒96.05712% 44+6+8=58個 ←無意味
%256 %15 %23 63175/65536≒96.39739% 44+6+12=62個
%256 %15 %25 62225/65536≒94.94781% 44+6+11=61個 ←無意味
%256 %15 %27 62776/65536≒95.78857% 44+6+11=61個 ←無意味
%256 %15 %29 63192/65536≒96.42333% 44+6+15=65個
…
このように無意味に個数だけ増えるものが出てくるが、コスパが良いのは、%256 %9に3より大きな奇素数の剰余で組み合わせると良さそうです。
99%超えするような組み合わせは、
6項
%256 %9 %5 %7 %11 %13 64972/65536≒99.13940% 44+6+3+4+6+7=70個
%256 %9 %5 %7 %11 %17 64979/65536≒99.15008% 44+6+3+4+6+9=72個
%256 %9 %5 %7 %11 %19 64977/65536≒99.14703% 44+6+3+4+6+10=73個
%256 %9 %5 %7 %11 %23 64978/65536≒99.14855% 44+6+3+4+6+12=75個
%256 %9 %5 %7 %11 %29 64984/65536≒99.15771% 44+6+3+4+6+15=78個
%256 %9 %5 %7 %13 %17 64987/65536≒99.16229% 44+6+3+4+7+9=73個
%256 %9 %5 %7 %13 %19 64985/65536≒99.15924% 44+6+3+4+7+10=74個
%256 %9 %5 %7 %13 %23 64986/65536≒99.16076% 44+6+3+4+7+12=76個
%256 %9 %5 %7 %13 %29 64984/65536≒99.15771% 44+6+3+4+7+15=79個
7項
%256 %9 %5 %7 %11 %13 %17 65159/65536≒99.42474% 44+6+3+4+6+7+9=79個
8項
%9 %5 %7 %11 %13 %17 %19 %23 65019/65536≒99.21112% 6+3+4+6+7+9+10+12=57個
9項
%9 %5 %7 %11 %13 %17 %19 %23 %29 65166/65536≒99.43542% 6+3+4+6+7+9+10+12+15=72個
全部の組み合わせを調べたわけではないが、比率を取るか、個数を取るかのトレードオフだろう。
プログラミングは面白いね。
ではでは