Mersenne Twister 序列很 2 ** ( 624 * 32 - 1 ) - 1
长,种子值用于为 PRNG 设置与该序列中的位置直接相关的内部状态。
最容易找到的重复似乎是 every 2 ** ( 624 * 32 )
,并且可以显示如下:
repeat_every = 2 ** ( 624 * 32 )
start_value = 5024214421 # Try any value
r1 = Random.new( start_value )
r2 = Random.new( start_value + repeat_every )
r17 = Random.new( start_value + 17 * repeat_every )
r23 = Random.new( start_value + 23 * repeat_every )
r1.rand == r2.rand
# true
r17.rand == r23.rand
# true
或者试试这个:
repeat_every = 2 ** ( 624 * 32 )
start_value = 5024214421 # Try any value
r1 = Random.new( start_value )
r2 = Random.new( start_value + repeat_every )
Array.new(10) { r1.rand(100) }
# => [84, 86, 8, 58, 5, 21, 79, 10, 17, 50]
Array.new(10) { r2.rand(100) }
# => [84, 86, 8, 58, 5, 21, 79, 10, 17, 50]
重复值与 Mersenne Twister 的工作方式有关。MT 的内部状态是一个由 624 个 32 位无符号整数组成的数组。您链接的 Ruby 源代码将 Ruby Fixnum 打包到一个数组中 - 魔术命令是
rb_integer_pack( seed, buf, len, sizeof(uint32_t), 0,
INTEGER_PACK_LSWORD_FIRST|INTEGER_PACK_NATIVE_BYTE_ORDER );
但是,这并不是一件容易玩的事情,它是在 中定义的internal.h
,因此只有在您使用 Ruby 解释器本身时才能真正访问。您不能从普通的 C 扩展中访问此函数。
然后通过函数将压缩整数加载到 MT 的内部状态init_by_array
。这是一个看起来相当复杂的函数 - 打包的种子值并没有按字面意思写入状态,而是逐项生成状态,将提供的数组值相加,使用各种异或、加法和交叉引用以前的值(这里的 Ruby 源代码还添加了打包数组的索引位置,注释为“非线性”,我认为这是对标准 MT 的引用修改之一)
请注意,MT 序列的大小小于2 ** ( 624 * 32 )
- 我在这里显示的值repeat_every
是一次跳过 2 个序列,但它最容易找到重复的种子值,因为很容易看出它是如何准确设置内部状态的相同(因为种子数组表示中的前 624 个项目是相同的,这就是以后使用的全部内容)。其他种子值也将产生相同的内部状态,但这种关系是一个复杂的映射,它将 19938 位空间中的每个整数与另一个整数配对,从而为 MT 创建相同的状态。