今回は疑似乱数の生成方法についてです
解説動画はこちら
真の乱数列は本来、規則性も再現性もないものであるため
また過去に現れた数と同じ数が現れた際のその長さを
周期と言っています。
一般的には初期値(シード)と
演算法(アルゴリズム)を用いて生成します。
平方採中法 (middle-square method)
この方法は
これを繰り返して乱数列とする方法です。
Pythonでの実装例はこんな感じになります。
次のような漸化式による生成方法です。

元々Pythonのrandomなどの実装は
この手法を基にしているそうなので
実装する必要は無いのですが
中身の実装を真似るとこんな感じのコードになります
システム内で使用される乱数
(セッションID、認証トークン、パスワードのハッシュなど)が
予測されて攻撃されたり
暗号化や認証に使用される乱数
(例:暗号鍵生成やトークンの生成)が
予測可能だと、暗号が破られやすくなるでしょう。
またゲームやギャンブルサイトでの
不正行為にも使われてしまった例があったようです。
まあ、疑似乱数を1から生成するコードを実装する機会は
ほとんど無いかもしれませんが、アルゴリズムの採択によっては
それがシステム上のリスクになりえる事は
考えておいても良いんじゃ無いでしょうか
今回は疑似乱数についてのお話でした。
それでは。
解説動画はこちら
乱数と疑似乱数
今回は疑似乱数についてのお話です
一般的に乱数というものは
予測できない数値のことで
今回は疑似乱数についてのお話です
一般的に乱数というものは
予測できない数値のことで
原子核の放射崩壊により放出された放射線のカウント
のように予測が出来ない数値のことです
一方で疑似乱数とは
コンピューターで生成した一定の規則に基づく
乱数のような値のことで
一方で疑似乱数とは
コンピューターで生成した一定の規則に基づく
乱数のような値のことで
真の乱数列は本来、規則性も再現性もないものであるため
本来は確定的な計算によって求めることはできないです
擬似乱数は計算によって作るので
生成法と内部状態が判れば予測も可能ではあります。
生成法と内部状態が判れば予測も可能ではあります。
また過去に現れた数と同じ数が現れた際のその長さを
周期と言っています。
疑似乱数の作成手法
一般的には初期値(シード)と
演算法(アルゴリズム)を用いて生成します。
平方採中法 (middle-square method)
1946年頃、数学者ノイマンによって提唱された手法です。
この方法は
適当に初期値を決める 例 : 1234
その数を 2 乗した値の中央にある必要な桁数を採って
次の乱数とする
これを繰り返して乱数列とする方法です。
Pythonでの実装例はこんな感じになります。
# ノイマンの平方採中法による乱数生成 def middle_square_method(seed, num_samples=10, num_digits=4): random_numbers = [] current_value = seed for _ in range(num_samples): num_digits = len(str(current_value)) # 2乗して中央の桁を取り出す squared_value = current_value ** 2 squared_str = str(squared_value).zfill(2 * num_digits) # 中央の桁を取り出す start = len(squared_str) - num_digits - 2 middle_digits = squared_str[start: start + 4] current_value = int(middle_digits) random_numbers.append(current_value) return random_numbers # 初期値とパラメータ設定 seed = 1234 # 初期値(任意の4桁の数) random_nums = middle_square_method(seed) print(random_nums)
線形合同法 (linear congruential method)
次のような漸化式による生成方法です。

周期の最大は m で
パラメータ次第では周期が短くなり
予想ができてしまう手法です。
予想ができてしまう手法です。
class LCG: def __init__(self, seed=1, a=1664525, c=1013904223, m=2**32): self.a = a self.c = c self.m = m self.state = seed def next(self): self.state = (self.a * self.state + self.c) % self.m return self.state / self.m # 使用例 seed = 1320 lcg = LCG(seed=seed) for _ in range(10): print(lcg.next())
0.7476371766533703
0.0075369239784777164
Xorshift
George Marsagliaが2003年に提案した手法です
ビット演算を用いた高速な擬似乱数生成法で
基本的な計算ステップは次の3つです。
state ^= (state << a) state ^= (state >> b) state ^= (state << c)
a, b, c は定数(一般的には a=13, b=17, c=5)
^= はビット単位の XOR を行って
結果を state に代入です。
名前の由来は
周期がメルセンヌ素数(2^19937 - 1)であることからだそうです
•出力:32ビットの整数
結果を state に代入です。
class XorShift32: def __init__(self, seed=2463534242): self.state = seed def next(self): x = self.state x ^= (x << 13) & 0xFFFFFFFF x ^= (x >> 17) x ^= (x << 5) & 0xFFFFFFFF self.state = x & 0xFFFFFFFF return self.state / 0xFFFFFFFF # 使用例 xorshift = XorShift32(seed=123456789) for _ in range(5): print(xorshift.next())
0.6321277193799912
0.5212643641329521
メルセンヌ・ツイスタ (Mersenne Twister : MT)
1996年に松本眞と西村拓士によって
国際会議で発表された手法です。
国際会議で発表された手法です。
名前の由来は
周期がメルセンヌ素数(2^19937 - 1)であることからだそうです
•出力:32ビットの整数
•長所:
•非常に長い周期(2^19937 - 1)
•高次元(623次元)でも均一な分布を持つ
•高速(特にビット演算が中心)
以下の3ステップで計算されています。
1.初期化(シードによる状態配列の構築)
2.状態配列の更新(Twist)
3.出力整形(Tempering)
元々Pythonのrandomなどの実装は
この手法を基にしているそうなので
実装する必要は無いのですが
中身の実装を真似るとこんな感じのコードになります
class MT19937: # 初期化(シードによる状態配列の構築) def __init__(self, seed): self.w, self.n, self.m, self.r = 32, 624, 397, 31 self.a = 0x9908B0DF self.u, self.d = 11, 0xFFFFFFFF self.s, self.b = 7, 0x9D2C5680 self.t, self.c = 15, 0xEFC60000 self.l = 18 self.f = 1812433253 self.MT = [0] * self.n self.index = self.n self.lower_mask = (1 << self.r) - 1 self.upper_mask = (~self.lower_mask) & 0xFFFFFFFF self.MT[0] = seed for i in range(1, self.n): self.MT[i] = (self.f * (self.MT[i - 1] ^ (self.MT[i - 1] >> (self.w - 2))) + i) & 0xFFFFFFFF # 状態配列の更新(Twist) # 複数のビットを結合して新しい状態を作り出す処理 def twist(self): for i in range(self.n): x = (self.MT[i] & self.upper_mask) + (self.MT[(i + 1) % self.n] & self.lower_mask) xA = x >> 1 if x % 2 != 0: xA ^= self.a self.MT[i] = self.MT[(i + self.m) % self.n] ^ xA self.index = 0 # 出力整形(Tempering) # 出力する前にビットをシャッフルして統計的性質を良くする def extract_number(self): if self.index >= self.n: self.twist() y = self.MT[self.index] y ^= (y >> self.u) & self.d y ^= (y << self.s) & self.b y ^= (y << self.t) & self.c y ^= (y >> self.l) self.index += 1 return y & 0xFFFFFFFF def random(self): return self.extract_number() / 0xFFFFFFFF
まとめ
コンピューターでは真の乱数を生成することは
非常に困難ですが乱数に近い値を計算で求める事は出来ます
非常に困難ですが乱数に近い値を計算で求める事は出来ます
しかし乱数生成アルゴリズムが分かってしまった場合には
リスクが発生することがあります。
リスクが発生することがあります。
システム内で使用される乱数
(セッションID、認証トークン、パスワードのハッシュなど)が
予測されて攻撃されたり
暗号化や認証に使用される乱数
(例:暗号鍵生成やトークンの生成)が
予測可能だと、暗号が破られやすくなるでしょう。
またゲームやギャンブルサイトでの
不正行為にも使われてしまった例があったようです。
まあ、疑似乱数を1から生成するコードを実装する機会は
ほとんど無いかもしれませんが、アルゴリズムの採択によっては
それがシステム上のリスクになりえる事は
考えておいても良いんじゃ無いでしょうか
今回は疑似乱数についてのお話でした。
それでは。
コメントする