初投稿なので、手探りで書いていきます。
元ネタの質問
「10桁のランダムな数字をそれまでに発生していない数字で」
http://www.excel.studio-kazu.jp/kw/20190522114232.html
質問の内容は10桁の乱数を発行するマクロが欲しいというもの。
それに対する回答は概ねすべて「乱数を作って被ったら作り直す」というもの。
10桁なのでそれでいいかなと思いつつも、
作り直すループをせずとも生成できないものかと思い調べたらすぐ見つかった。
「重複のない10桁の数字をIDとして採番するアルゴリズムを教えて下さい。」
http://q.hatena.ne.jp/1377468971
PHPかぁ。やり方を調べてVBAに作り変えてみよう
コンピュータの扱う自然数(2のべき乗を位数とする巡回群)のそれぞれの元は奇数定数をかけると必ず相異なる結果になる
んん?この話、どこかで聞いたことがある。どこかで…
数学ガールだ!これ数学ガールで見た!!
時計盤の数字を等間隔で線で結ぶと、特定の物は全部の数字を経由するってやつだ。
位数nの巡回群の元p(p<n, pとnは互いに素)には逆数が存在する
互いに素!!知ってる!数学ガールで見た!(しつこい)
フェルマーの最終定理の巻だったかしら。そうかこうやって使うんだ。
本の内容を実際に使えるとなんか嬉しい。
相違なる結果とやら
とりあえずやってみよう。
互いに素ということなので2と5で割り切れなければいいのよね。
Sub RndTest1()
For i = 1 To 100
x = (i * 777) Mod 100
Cells(i, 1) = x
Next
End Sub
あら、簡単。
なるほど、シャッフルができた。だがこれでは明らかに法則がモロ見えしている。
ここでビット逆順とかいうものをして3回繰り返せばいいわけだ。
ここで問題が2つ。
- 桁を増やすとすぐオーバーフローする
- ビット逆順?なにそれ?
オーバーフロー大好きなExcelさん
オーバーフローは10桁くらいどうにかなると思ったが、ちょっとした掛け算ですぐエラーになる。
doubleとか使えばいいかと思ったけど、そもそもmodなんかは 型宣言無視してオーバーフローする
あかん(あかん)
2つに分けてみよう。それで桁が溢れたら上の桁に足そう。
Sub RndTest2()
For i = 1 To 10
x1 = Int(i / 1000) + 1
x2 = i Mod 1000
For j = 1 To 3
x3 = (Int(x2 * 1.123) + x1 * 7789) Mod 1000
x2 = (x2 * 1123) Mod 1000
x1 = x3
Next
Cells(i, 1) = x1 * 1000 + x2
Next
End Sub
ちょっとややこしくなったけどうまく行った。ちなみに1123と7789は自分の好きな素数だ。
ビット逆順とは
要は2進数にして順番を逆にすればいいわけね。オーケーオーケー。
でもそんなVBA聞いたことがない。
(EXCEL2019にはビットなんちゃらとかいう数式が増えたらしいけど)
小一時間後、よく考えたら2進数にする必要ないね、と気づく。
そうだ!123456が654321になれば逆順なのでは!(暴論)
よし、こうしよう。
x3 = (Int(x2 * 1.123) + x1 * 7789) Mod 1000
x2 = (x2 * 1123) Mod 1000
↓
x3 = (x2 * 1123) Mod 1000
x2 = (Int(x2 * 1.123) + x1 * 7789) Mod 1000
計算するついでに上3桁と下3桁を入れ替えよう。つまり123456を456123にしたわけだ。
こういうのをビット回転というの?言わない?あっそう…
まぁなんかビット逆順を使わなくとも、2種類のシャッフルを何度かワチャワチャすれば
予想しづらい並び順に混ざりそうな気がしなくもなくなくない。
最終
こうなった。
Sub RndTest3()
For i = 1 To 100
x1 = Int(i / 1000) + 1
x2 = i Mod 1000
For j = 1 To 7
x3 = 1000 - (x2 * 1123) Mod 1000
x2 = (Int(x2 * 1.123) + x1 * 7789) Mod 1000
x1 = x3
Next
Cells(i, 1) = x1 * 1000 + x2
Next
End Sub
jのループ回数は、3では微妙に予想できる混ざり方したので7に増やした。
x3で1000から引いてみたのは、なんか混ざるかなぁと思い気分で追加した。
結局10桁と言いつつ6桁だけど、xを増やせば何桁にでもなると思う。
このへんで満足したので終わりにしようと思う。
枠外コメ
ちなみに枠外のコメに以下のようなものがあったが、こちらはビックリするほどわからなかった。
見なかったことにしよう。
M系列の疑似乱数なら同じ値の繰り返しになるので、適当なシード値とXORマスクを用意して
乱数生成 → 1億未満または11桁以上なら乱数生成に戻る → 乱数取出し → 生成した乱数をシードにして先頭に戻る