素数列挙マクロ

第7講 配列の学習

第3話 1次元配列の利用による素数列挙マクロ

Dim s(100000000) As Long
Dim cn As Long
Private Sub CommandButton1_Click()

  Dim n As Long, i As Long
  Dim h As Double, o As Double

  h = Timer

  cn = 0
  n = Cells(6, 1)
  For i = 2 To n
    If f(i) = 1 Then
      Cells(6 + Int(cn / 20), 2 + (cn Mod 20)) = i
      cn = cn + 1
    End If
  Next

  Cells(7 + Int(cn / 20), 2) = "素数個数"
  Cells(7 + Int(cn / 20), 3) = cn

  
o = Timer
  Cells(8 + Int(cn / 20), 2) = "計算時間"
  Cells(8 + Int(cn / 20), 3) = o - h


End Sub


Function f(n As Long)

  Dim r As Double

  If n = 2 Then
    
s(cn) = n
    f = 1
    Exit Function
  End If

  If n Mod 2 = 0 Then
    f = 0
    Exit Function
  End If

  r = Sqr(n)
  
For j = 0 To cn - 1
    If s(j) > r Then
      s(cn) = n
      f = 1
      Exit Function
    End If
    If n Mod s(j) = 0 Then
      f = 0
      Exit Function
    End If
  Next

End Function

では、解説していきましょう。
VBの技術に関することと、プログラムの内容に関することをそれぞれ説明します。
今回は、1回1回の判定をFuncitonプロシージャfに行わせています。
なので、
Dim s(100000000) As Longはグローバル配列でなければなりません。
Funcitonプロシージャfの中でしか利用しないので、Funcitonプロシージャfの中で宣言してローカル配列にしても良さそうに思えますが、
ところが、
Dim s(100000000) As LongをFuncitonプロシージャfの中に入れると、
a1とエラーしてしまいます。
そして、デバッグをクリックすると、a2と原因が判明します。
言い忘れていましたが、
実行エラーした場合は、a3
リセットボタンa4を押さないと、次に実行できません。

リセットボタンa5を押すと、a6の黄色がなくなり、再び実行できるようになります。
グローバル配列にすると、このエラーは出ないのに、何故ローカルにするとエラーしてしまうのでしょうか。
実はとても重要なことです。
前に、プロシージャやプロシージャ内のローカル変数・ローカル配列は、プロシージャが呼び出されているときだけ、
メモリが割り当てられます、という話をしました。プロシージャの処理が終了すると、割り当てられていたメモリは解放されて、
他の実行中のプロシージャやローカル配列などのメモリに置き換わります。
つまり、プロシージャやローカル変数は、プロシージャの処理の終了とともに消滅してしまうのです。
ですから、1回目にFuncitonプロシージャfが呼び出されて、s(0)に2が代入されても、終了とともに配列自体が消滅してしまいますので、
2回目に呼び出され、
  
For j = 0 To cn - 1
    If s(j) > r Then
      s(cn) = n
      f = 1
      Exit Function
    End If
    If n Mod s(j) = 0 Then
      f = 0
      Exit Function
    End If
  Next
の1回目のループが行われとき、s(0)は0なのです。1回目のときにs(0)=2の処理が行われていますが、
1回目のプロシージャが終了と同時に消滅してしまっていますので、
同じ配列のように見えて、再度生まれた新しい配列なのです。
ローカル変数やローカル配列は、プロシージャの生成消滅とともに、
生成消滅を繰り返します。
したがって、新たに生まれた、つまり赤ん坊である配列はまだ何の処理もされていません。
なぜなら、2回目のFuncitonプロシージャfは、f(3)として実行されいますので、
  If n = 2 Then
    
s(cn) = n
    f = 1
    Exit Function
  End If

  If n Mod 2 = 0 Then
    f = 0
    Exit Function
  End If
は実行されず、
  
For j = 0 To cn - 1
    If s(j) > r Then
      s(cn) = n
      f = 1
      Exit Function
    End If
    If n Mod s(j) = 0 Then
      f = 0
      Exit Function
    End If
  Next
の1回目のループを迎えているからです。
ですから、s(0)は何もされていない白紙の状態です。
VBでは、変数に代入処理が1回も行われていないとき、変数の値は0にするという約束になっています。
これをデフォルト(初期設定)は0であるといいます。
ですから、s(0)=0なのです。
したがって、n Mod s(j) で、3を0で割ったときの余りを求めさせようとしていることになってしまい、
a70で除算されましたと、エラー表示が出るわけです。
0では割れませんね。
それに対して、配列
s(100000000)をグローバル配列にしておけば、プログラムの実行中、配列s(100000000)はメモリに常駐します。
したがって、ローカル変数のように生成消滅を繰り返しません。
1回目のFuncitonプロシージャfの処理でなされたs(0)=2は生きております。
n Mod s(j)は、3を2で割ったときの余りになります。今回は、2で割るので問題はありません。
1回1回の判定を、Funcitonプロシージャfでやらせているので、cnも
s(100000000)もグローバルにしなければならないのです。

プロシージャfで次のようにまとめて処理するなら
Private Sub CommandButton1_Click()

  Dim n As Long

  cn = 0
  n = Cells(6, 1)
  f (n)

End Sub


Sub f(n As Long)

  Dim r As Double
  Dim i As Long, j As Long
  
Dim s(100000000) As Long
  Dim cn As Long

  Dim h As Double, o As Double

  h = Timer

  For i = 2 To n
    If i = 2 Then
      s(cn) = i
      Cells(6 + Int(cn / 20), 2 + (cn Mod 20)) = i
      cn = cn + 1
      GoTo owari
    End If

    If i Mod 2 = 0 Then GoTo owari

    r = Sqr(n)
    For j = 0 To cn - 1
      If i Mod s(j) = 0 Then GoTo owari
    Next
    s(cn) = i
    Cells(6 + Int(cn / 20), 2 + (cn Mod 20)) = i
    cn = cn + 1
owari:
  Next

  Cells(7 + Int(cn / 20), 2) = "素数個数"
  Cells(7 + Int(cn / 20), 3) = cn
  o = Timer
  Cells(8 + Int(cn / 20), 2) = "計算時間"
  Cells(8 + Int(cn / 20), 3) = o - h

End Sub
ローカル変数・ローカル配列でよいのです。

重要なことをもう一度繰り返します。プロシージャとその中のローカル変数・ローカル配列は、
プロシージャが呼び出されているときだけ、メモリ上にあり、プロシージャの処理が終わると、
メモリ上から消えます。つまり、消滅するのです。
そして、新たにプロシージャが呼び出せたときは、新たに変数や配列は生まれ直します。
輪廻なら、生まれ変わってもかつての痕跡はありますが、
この生まれ変わりは、かつての痕跡を何も残しません。
だから、新たに生まれた赤ちゃん(変数、配列)は自分が何度目の生を受けたのかさえ知りません。
というより、常にはじめて生まれたと思っているわけです。
プロシージャが処理を終えたとき、変数・配列は完全に消滅するのです。

次は、素数判定の内容について解説しましょう。
素数列挙マクロ』との違いは、『素数列挙マクロ』は素数であるか判定するのに、
  r = Sqr(n)
  For j = 3 To r Step 2
    If n Mod j = 0 Then
      f = 0
      Exit Function
    End If
  Next
  f = 1
その整数の平方根以下の奇数で割っています。
例えば、631が素数であるか判定するのに、
3,5,7,9,11,13,15、17,19,21,23,25で割って、判定していますが、
実は、9,15,21,25は不要です。
例えば、3で割り切れなければ、9で割り切れないのは明らかです。
3の倍数でなく、5の倍数でなければ、15の倍数でないのも明らかです。
631が素数であるかどうかを判定するには26未満の素数のみの
3,5,7,11,13,17,19,23で割っていけばよいのです。
素数列挙マクロ』では、やらなくてもよい9,15,21,25での割り算も実行していて無駄があったわけです。
素数列挙マクロ改良版』では、判明した素数をs(0)=2,s(1)=3,s(2)=5,s(3)=7,s(4)=11,s(5)=13,s(6)=17,s(7)=19,s(8)=23
と蓄えておいて、それで割って判定しているのです。

では、
    If s(j) > r Then
      s(cn) = n
      f = 1
      Exit Function
    End If
の意味は何でしょうか。
631の平方根は、約25.1ですから、23まで割っていけばよいわけです。
s(9)=29では割る必要がありません。ただ、何個目の素数が631の平方根を超えるかわかりません。
それで、上の5行が入れてあるのです。

次の話では、2次元配列の利用を考えます。

第2話へ 第4話へ

004


vc++講義へ
vb講義へ
VB講義基礎へ
初心者のための世界で一番わかりやすいVisual C++入門基礎講座へ
初心者のための世界で一番わかりやすいVisual Basic入門基礎講座へ

数学研究室に戻る