乱数を発生させるRnd関数

中級VBA

乱数を発生させるRnd関数

 ExcelVBAで乱数を扱うにはRnd関数を使います。
 PCで乱数を扱う場合には、疑似乱数系列で計算で出された乱数なので純粋な乱数ではないのですが、シード値を変える事によって、乱数系列が変化します。
 シード値を変えるには Randomize を使います。

Rnd関数の使い方

 Rnd[(number)]

引数のnumberには数値を指定できます。numberは省略可能です。

・ > 0(省略時)— 乱数系列の次の乱数を返します
・ < 0     — 引数numberのシード値によって決定される同じ数値を返します
・ = 0     — 直前に生成した乱数を返します
省略した場合は、乱数系列の次の乱数を返します。>0と同じです。

Rnd関数は、シード値という乱数表の基となる値を使って出力されます。
シード値は、Randomize で変更することができます。

 通常の使い方としては、プロシージャの冒頭で Randomize を実行して、Rnd関数を引数なしで実行して乱数を得ます
(Rnd関数に正の値を引数として渡すのは無意味な気がします。)

Rnd関数使い方の注意点

 Rnd関数に負の数値を引数として渡すと、シード値が固定され同じ乱数表が使われるため再現性を持った乱数が出力されます。ただ、一旦、負の数値でRndが実行されると、その後、引数なし、正の数値を引数で渡しても、シード値が固定されたままの状態となるため、同じ乱数が使われるようになります。

 これを解除するためには、Randomize を実行してシード値を変更する必要があります。

最小値・最大値を指定した乱数発生

 Rnd 関数は、0 ~ 1 のランダムな小数を返しますが、最小値・最大値の範囲内で乱数を発生させたい場面は良くあります。これは定番の式があります。

 乱数 =  Int((最大値最小値 + 1) * Rnd + 最小値)

以下のサンプルコードを参考にしてみてください。

Sub サイコロ()
    Dim v As Long
    Dim 最大値 As Long
    Dim 最小値 As Long
    最大値 = 6
    最小値 = 1

    Randomize

    v = Int((最大値 - 最小値 + 1) * Rnd + 最小値)
    
    Debug.Print v
End Sub

0から9、1から10の間で乱数を得る場合は以下のようになります。

 0~9 までの乱数
v = Int( ( 9 + 1 ) * Rnd )

 1~10 までの乱数
v = Int( 10 * Rnd + 1)

最小値から最大値までをランダムに配置

1から10まで順番に並んだ配列をシャッフルしたい場合は、Rnd関数を使って自作する必要があります。
やり方としては色々な方法がありますが、ここではオーソドックスな方法を示します。

 方針としては、1つづつ乱数を発生させて配列にセットしていきますが、セットする際に既にその値がある場合は、別の乱数を発生させるという動作を繰り返します。

Sub 重複しない乱数リスト()
    Dim 最小値 As Long, 最大値 As Long
    Dim e As Long, cnt As Long, v As Long, i As Long
    Dim arr() As Long
    
    最小値 = -5
    最大値 = 5
    
    If 最大値 < 最小値 Then
        MsgBox "最大値、最小値が正しくありません。"
        Exit Sub
    End If
    
    e = 最大値 - 最小値 + 1
    
    ReDim arr(1 To e)
    
    Randomize
    cnt = 1
    Do While cnt < e + 1
LABEL:
        v = Int((最大値 - 最小値 + 1) * Rnd + 最小値)
        
        For i = 1 To cnt - 1
            If v = arr(i) Then GoTo LABEL
        Next i
        
        arr(cnt) = v
        cnt = cnt + 1
    Loop
    
    For i = 1 To e
        Debug.Print arr(i)
    Next i

End Sub

Randomizeはご利益があるのか?

Rnd関数を使って乱数を発生させるとき、同じ乱数表が使われているのでは?と思うと気が引けます。
ただ、Randomize を実行しなくても、それなりに乱数が発生しているので、果たして乱数らしい乱数になっているのかというのは調べようがありません。
そもそも Randomize はどのタイミングで実行するのが良いのでしょうか?

そこで、以下のテストコードを実行してみてください。

Sub RandomizeTest()
    Dim f As Single
    Dim num As Long
    
    ActiveSheet.Cells.Clear
            
    For num = 1 To 5
        Randomize
        
        f = Rnd
        Cells(num, 1).Value = f
        f = Rnd
        Cells(num, 2).Value = f
        f = Rnd(-1)
        Cells(num, 3).Value = f
        f = Rnd
        Cells(num, 4).Value = f
    
    Next num
    
End Sub

このコードを実行すると、C,D列は常に同じ値を示します。
Rndに負の値を引数を渡しているので、こうなるのは納得がいきます。

ただ、A,B列の値は、全て同じになることもあれば、途中で違う値になる事もあって不定なのです。
これはどういう事か!

 実はこの事で乱数発生が時間によるものだというのがわかります。
 つまり、Forループが早すぎて、シード値が同じになってしまうのでしょう。
 試しに、Forループ内にブレークポイントを置いてゆっくりとForを回すと値は毎回変化します。

 この事から、Randomize を頻繁に実行してもあまり意味がないという事がわかります
 普通は、プロシージャの冒頭で1回実行し、Forループ内でいちいち実行するという事はしないようです。

 それでも、速度よりランダム性を重視したいという方は、Sleep関数やWaitメソッドなどで時間調整する必要がありそうです。

タイトルとURLをコピーしました