真性乱数を取り出すpythonのモジュールです。
コンピュータで使える乱数は、2種類あり、周期をもつ疑似乱数と、TRNGから得られる真性乱数があります。他に、「完全乱数」というものも存在し、確率的にしか扱うことは出来ません。つまり、完全乱数はイデア界にしか存在しません。
python3では、乱数の利用は主にrandomモジュールを使いますが、これは、疑似乱数を発生するもので、真の乱数ではありません。しかし、x86_64はRDSEEDで、環境ノイズからの真性乱数を得ることができるので、これを使います。(普通RDSEEDは疑似乱数の種に使いますが)
/dev/randomは乱数を1バイト単位で読み出しますが、真の真性乱数ではなく、「エントロピーで初期化されたCSPRNGの出力」で、一度エントロピーで初期化されれば、多数のソースからのエントロピーで定期的(最大毎秒10回)に再シードされない限り、決定論的ですので、ハードウェアエントロピー源からRDSEEDを使って、毎回新規取得します。
「真性乱数」は「完全乱数」と定義が違います。
・真性乱数とは、原理的に予測不能で真に無秩序な乱数。コンピューターによって算術的に生成する疑似乱数に対していう。放射性元素の崩壊など、量子的なふるまいを利用する物理乱数が知られる。真乱数。- 小学館 - デジタル大辞泉より。
・完全乱数とは、完全にでたらめな順番で発生する数値の列を指し、理想の乱数とも呼ばれます。確率的にしか扱うことができません。 - Search Labs | AIによる。
定義が違いますが、真性乱数を完全乱数と看做して、同様に扱って良いでしょう。
完全乱数はユングの共時性(因果律を超えた奇妙な偶然の一致)があるので、物理的に言うと、EPRパラドックスがEPR相関になったこともあり、実験によると、Quantum Brain DynamicsがQuantum Dynamicsに干渉し、環境ノイズがそれに左右されてしまうため、現実世界で作るのは困難もしくは不可能かもしれません。もしくは、完全に偶然の産物かも知れません。真性乱数が完全乱数か否かであるかは、yes/noの2つの答があります。
「数学的に完全な乱数(真の乱数)」を発生させる方法は、数学だけでは原理的に不可能とされ(数学アルゴリズムは決定論的なので)、まだ見つかっていません。しかし、ある日、天才が現れ、無限を包含させ、実現させるかもしれません。
メソッドの説明
rand(n)はn*8ビット正の整数の範囲の真性乱数を返します。
rand_f(n)は、実用的に、[0,1]のfloat型の乱数を返しますが、分布は実数のように連続ではありませんので、真性乱数ではなくなっています。
randomint(k,n)は、実用的に0~k-1の整数乱数を返しますが、剰余計算を行っているため、真性乱数ではなくなっています。
/*
* rdseed.c — CPU の RDSEED 命令でハードウェアエントロピーを取り出す。
* コンパイル(FreeBSD/clang, Linux/gcc 共通):
* cc -O2 -mrdseed -shared -fPIC -o librdseed.so rdseed.c
* RDSEED 対応 CPU が必要(Intel Broadwell 以降 / AMD Zen 以降)。
*/
#include <immintrin.h>
#include <stdint.h>
/* 成功で1、エントロピー未準備で0(=要リトライ)を返す。 */
int rdseed64(uint64_t *out){
return _rdseed64_step((unsigned long long *)out);
}
コンパイル・ライブラリ化
cc -O2 -mrdseed -shared -fPIC -o librdseed.so rdseed.c
#!/usr/bin/env python3
#
# truerand.py
# CPU の RDSEED 命令から直接ハードウェアエントロピーを引く真性乱数モジュール。
# /dev/random(Fortuna)を経由せず、1バイトごとにエントロピー源から新規取得する。
# 要: RDSEED 対応 CPU と、同じディレクトリにコンパイル済みの librdseed.so。
# cc -O2 -mrdseed -shared -fPIC -o librdseed.so rdseed.c
#
import os
import ctypes
# --- RDSEED スタブの読み込み ---------------------------------------------
_here = os.path.dirname(os.path.abspath(__file__))
_lib = ctypes.CDLL(os.path.join(_here, "librdseed.so"))
_lib.rdseed64.argtypes = [ctypes.POINTER(ctypes.c_uint64)]
_lib.rdseed64.restype = ctypes.c_int
_RDSEED_RETRY = 1000 # RDSEED は一時的に失敗しうるのでリトライ上限を設ける
def _rdseed64():
"""RDSEED で 64bit のハードウェアエントロピーを1個取得して返す。"""
v = ctypes.c_uint64()
for _ in range(_RDSEED_RETRY):
if _lib.rdseed64(ctypes.byref(v)):
return v.value
raise RuntimeError("RDSEED が連続失敗しました(ハードウェアエントロピー枯渇?)")
# n byte の RAND_MAX を返す
def rand_max(n):
v = 0
for _ in range(n):
v = v*256 + 0xff
return v
# n byte の真性乱数(正の整数)を返す。1バイトごとに RDSEED から新規取得。
def rand(n):
r = 0
for _ in range(n):
b = _rdseed64() & 0xff # 一々エントロピー源から引く
r = r*256 + b
return r
# [0,1] の実数乱数(割り算のため厳密には真性乱数ではない)
def rand_f(n):
return rand(n) / rand_max(n)
# 0〜k-1 の整数乱数(剰余のため厳密には真性乱数ではない)
def randomint(k, n):
return rand(n) % k
if __name__ == "__main__":
b = 8
times = 20
print(f"{b*8}bit正の真性整数乱数")
for _ in range(times):
print(rand(b), end=" ")
print("\n実数[0,1]の範囲の実数乱数")
for _ in range(times):
print(rand_f(b), end=" ")
print("\n整数0〜99の乱数")
for _ in range(times):
print(randomint(100, b), end=" ")
print("")
実行結果
% truerand.py
64bit正の真性整数乱数
7593683692756404560 1137002372603596603 15790170822563052133 15398564476323138863 5666431897510622232 14727313056768347926 1591286415691868933 13756589528406737812 14017529931204693033 6126976806754524406 9625760106799710579 10221137030372070039 5789631471258037124 15844850515166960795 16310560720210454887 584665298658331905 11747899575069696907 17623763738758574929 2856509978569915829 7845507258779463291
実数[0,1]の範囲の実数乱数
0.9971038511422857 0.04623809284437578 0.25208883991654213 0.23197559278871627 0.21992506030041897 0.062286248874933114 0.972354488288027 0.9724443108477789 0.6121628351627036 0.9760755436977876 0.2878544810907126 0.6180388833588414 0.8617915343869584 0.38774012248377726 0.7124578572583314 0.20837745592535706 0.8668496736230473 0.5165770706713765 0.40389838880267426 0.5204816417932834
整数0〜99の乱数
62 75 92 2 90 7 97 96 96 59 23 14 61 55 12 94 88 17 13 53