【问题标题】:What is the effective seed range for Ruby's rand?Ruby 的兰特的有效种子范围是多少?
【发布时间】:2013-12-03 07:00:17
【问题描述】:

Ruby 将 PRNG 实现为“经过修改的 Mersenne Twister,周期为 2**19937-1”。 1

我理解 MT 的方式是它对 2^32 个不同的种子进行操作。让我困惑的是Random.new(seed) 接受任意大的数字,例如Random.new(2**100)

但是,我无法找到(逻辑)冲突:

Random.new(1).rand(10**5) == Random.new(2**32-1).rand(10**5) => false
Random.new(1).rand(10**5) == Random.new(2**32).rand(10**5) => false
Random.new(1).rand(10**5) == Random.new(2**32+1).rand(10**5) => false

鉴于我们想利用 MT 的最大种子范围,即我们希望使用尽可能多的不同种子,同时仍然避免与两个不同的种子发生冲突,那么什么种子范围可以实现这一点?

我尝试了解 Ruby 的随机实现内部发生了什么,但没有走得太远。 https://github.com/ruby/ruby/blob/c5e08b764eb342538884b383f0e6428b6faf214b/random.c#L370

【问题讨论】:

  • 它在内部使用 624 个 32 位整数的向量(我 认为 - 至少这是 MT 的默认实现会使用的)。您链接的代码将大整数拆分为一个由 32 位整数组成的数组,该数组提供初始状态向量。
  • 注 624 * 32 = 19968 。 . . “种子”也是 MT 的“状态”
  • @NeilSlater:等等。这是否意味着 Random.new(1) 将在某个时候开始生成与 Random.new(1000) 相同的序列?
  • 我想知道rb_to_int(vseed);是否做了一些规范化?
  • 是的,有一个重复的序列,种子只是在不同的位置拾取。但是,这些州通常不像小种子那样紧密。在实践中您不太可能看到碰撞 - 如果您只是将生成器设置为从 srand(1)srand(1000) 运行并每秒获得十亿个结果,那么当两者之间存在重叠时,我们都已经死了序列。可用空间很大。这与知道“我在序列中的位置”不同,后者是关于看到足够的变化来识别状态。

标签: ruby random mersenne-twister random-seed


【解决方案1】:

Mersenne Twister 序列的长度为2 ** ( 624 * 32 - 1 ) - 1,种子值用于为 PRNG 设置与该序列中的位置直接相关的内部状态。

最容易找到的重复似乎是每个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 扩展中访问此函数。

然后通过函数init_by_array 将压缩整数加载到 MT 的内部状态。这是一个看起来相当复杂的函数 - 打包的种子值并没有按字面意思写入状态,而是逐项生成状态,将提供的数组值相加,使用各种异或、加法和交叉引用以前的值(这里的 Ruby 源代码还添加了打包数组的索引位置,注释为“非线性”,我认为这是对标准 MT 的引用修改之一)

请注意,MT 序列的大小小于2 ** ( 624 * 32 ) - 我在这里显示的 repeat_every 值一次跳过 2 个序列,但最容易找到重复种子值,因为很容易看出它是如何将内部状态设置为完全相同的(因为种子的数组表示中的前 624 个项目是相同的,这就是以后使用的全部内容)。其他种子值也会产生相同的内部状态,但这种关系是一个复杂的映射,它将 19938 位空间中的每个整数与另一个为 MT 创建相同状态的整数配对。

【讨论】:

    猜你喜欢
    • 2022-01-12
    • 1970-01-01
    • 1970-01-01
    • 2016-12-15
    • 2017-04-30
    • 1970-01-01
    • 1970-01-01
    • 2016-04-10
    • 1970-01-01
    相关资源
    最近更新 更多