【问题标题】:Fast generation of random numbers that appear random快速生成看似随机的随机数
【发布时间】:2010-11-05 17:01:31
【问题描述】:

我正在寻找一种有效的方法来生成人类会认为是随机的数字。基本上,我认为这是避免 0 或 1 位的长序列。我希望人类能够查看位模式,并且功耗非常低的 CPU 应该能够每秒计算近千个。

我可以想到两种不同的概念来做到这一点,但我找不到一种有效的方法来实现它们。

  1. 生成一个固定位数为 1 的随机数。对于 32 位随机数,这需要多达 31 个随机数,使用 Knuth 选择算法。有没有更有效的方法来生成一个设置了一些位数的随机数?不幸的是,0000FFFF 看起来不是很随机。

  2. 某种形式的“部分”密度看起来会更好 - 但我想不出一个明确的方法 - 我想通过每个块,并计算如何距离理想密度还差得很远,试着增加下一个块的位密度。这听起来很复杂。

希望有另一种我没有考虑过的算法。在此先感谢您的帮助。

[编辑] 我应该更清楚我的要求 -
(a) 有没有一种有效的方法来生成随机数,而不需要“长时间”运行单个位,其中“long”是一个可调参数?
(b) 关于什么会使数字看起来不那么随机的其他建议?

【问题讨论】:

  • 我想知道如果你用二进制显示,人们是否真的会认为典型的随机数生成器产生的结果是“非随机的”。
  • 我希望您知道排除“长期运行”会显着降低随机性。
  • 显然,他不是在寻找随机数。他试图让数字“看起来”比随机更随机。
  • 它可以是一组“批准的数字”(我在回答中提出的)中的真正随机样本。

标签: algorithm language-agnostic random


【解决方案1】:

一种简单的方法是一次生成一个比特,并使用一个调整参数来控制每个新比特与前一个比特匹配的概率。通过将概率设置为低于 0.5,您可以生成不太可能包含长时间重复位的序列(并且您可以调整这种可能性)。设置 p = 0 给出重复的 1010101010101010 序列;设置 p = 1 给出一个全 0 或全 1 的序列。

这里有一些 C# 来演示:

double p = 0.3; // 0 <= p <= 1, probability of duplicating a bit

var r = new Random();
int bit = r.Next(2);    

for (int i = 0; i < 100; i++)
{
    if (r.NextDouble() > p)
    {
        bit = (bit + 1) % 2;
    }        

    Console.Write(bit);                
}

这可能对您的需求来说太慢了,因为您需要生成一个随机双精度才能获得每个新的随机位。相反,您可以生成一个随机字节并使用每对位来生成新位(即,如果两者都为零,则保持相同的位,否则翻转它,如果您对固定 的等价物感到满意p = 0.25)。

此外,仍然有可能获得长序列的重复位,您只是降低了这样做的可能性。

【讨论】:

    【解决方案2】:

    不敢相信没有人提到这一点: 如果您想要 2N 次重复的最长运行(周期):

    PeopleRandom()
    {
        while(1)
        {
            Number = randomN_bitNumber();
            if(Number && Number != MaxN_BitNumber)
                return Number;
        }
    }
    

    与使用 32 位等 rand 相比,这在投掷次数方面提供了更好的结果

    优点:

    • 您只在 2/2^N 的时间内折腾值。
    • N 越大,结果越好。

    由于未将中间位为 1 的值拆分的值的数量正好是一半,因此如果您可以容忍较大的最大运行少于一半的时间,则您可以选择更大的 N .

    【讨论】:

      【解决方案3】:

      查看GSL。我相信它有一些功能可以满足您的需求。它们至少保证是随机位串。我不确定他们是否会看起来很随意,因为那更像是一个心理问题。

      【讨论】:

        【解决方案4】:

        线性反馈移位寄存器有多种变体,例如shrinkingself-shrinking,它们根据另一个LFSR 的输出修改一个LFSR 的输出。

        这些尝试创建随机数的设计,其中连续两位相同的概率为 0.5,连续三位相同的概率为 0.25,依此类推。

        当出现一系列相似位时,应该可以链接两个 LFSR 以抑制或反转输出 - 第一个 LFSR 使用传统的原始多项式,并将第一个的输出馈送到第二个。第二个移位寄存器较短,没有原始多项式。相反,如果它的所有位都相同,它会被用来反转输出,因此没有运行可以超过第二个移位寄存器的大小。

        显然这破坏了输出的随机性——如果你连续有 N 位,下一位是完全可预测的。乱用另一个随机源的输出来确定是否反转输出会破坏第二个移位寄存器 - 您将无法检测到它与仅一个随机源之间的差异。

        【讨论】:

          【解决方案5】:

          由于您最关心游程长度,您可以生成随机游程长度而不是随机位,以便为它们提供您想要的精确分布。

          随机二进制数据的平均运行长度当然是 4(n/(2^(n-1)) 的总和),模式平均值为 1。这里有一些随机位(我发誓这是一次运行,我没有选择一个值来说明我的观点):

          0111111011111110110001000101111001100000000111001010101101001000
          

          看到那里有 8 个运行长度。这并不特别令人惊讶,因为运行长度 8 应该大约每 256 位出现一次,而我已经生成了 64 位。

          如果由于运行长度过长而对您来说这不是“随机的”,则生成具有您想要的任何分布的运行长度。在伪代码中:

          loop
              get a random number
              output that many 1 bits
              get a random number
              output that many 0 bits
          endloop
          

          您可能希望从流中丢弃一些初始数据,或随机化第一位,以避免出现第一位始终为 1 的问题。第 N 位为 1 的概率取决于如何你“得到一个随机数”,但对于任何达到“短但不太短”的运行长度的东西,它很快就会接近 50%,因为没有区别。

          例如“获取随机数”可能会这样做:

          get a uniformly-distributed random number n from 1 to 81
          if n is between 1 and 54, return 1
          if n is between 55 and 72, return 2
          if n is between 72 and 78, return 3
          if n is between 79 and 80, return 4
          return 5
          

          这个想法是,长度为 N 的游程的概率是长度为 N-1 的游程概率的三分之一,而不是二分之一。这将给出更短的平均运行长度和最长的 5 次运行,因此对您来说“看起来更随机”。当然,对于习惯于处理一系列抛硬币的人来说,这不会“看起来很随意”,因为他们会认为运行时间太短了。您还可以通过统计测试轻松判断数字 N 的值与数字 N-1 的值相关。

          此代码至少使用 log(81) = 6.34“随机位”来生成平均 1.44 位的输出,因此比仅生成均匀分布的位要慢。但它不应该比大约 7/1.44 = 慢 5 倍多得多,并且 LFSR 开始时相当快。

          【讨论】:

            【解决方案6】:

            如果你想避免长时间运行,那么简单的事情怎么样:

            #include <cstdlib>
            
            class generator {
            public:
               generator() : last_num(0), run_count(1) { }
            
               bool next_bit() {
                  const bool flip = rand() > RAND_MAX / pow( 2, run_count);
                                           // RAND_MAX >> run_count ? 
                  if(flip) {
                     run_count = 1;
                     last_num = !last_num;
                  } else
                     ++run_count;
            
                  return last_num;
               }
            private:
               bool last_num;
               int run_count;
            };
            

            运行时间越长,运行的可能性就越小。如果您想要更长的运行时间,您也可以执行 RAND_MAX / 1+run_count

            【讨论】:

              【解决方案7】:

              这就是我检查数字的方式:

              const int max_repeated_bits = 4;  /* or any other number that you prefer */
              
              int examine_1(unsigned int x) {      
                for (int i=0; i<max_repeated_bits; ++i) x &= (x << 1);
                return x == 0;
              }
              
              int examine(unsigned int x) {
                return examine_1(x) && examine_1(~x);
              }
              

              然后,只生成一个数字x,如果eximate(x)返回0,则拒绝它并重试。获得连续超过 4 位的 32 位数字的概率约为 2/3,因此每个数字需要大约 3 个随机生成器调用。但是,如果您允许超过 4 位,它会变得更好。比如说,连续获得超过 6 位的概率只有大约 20%,所以每个号码只需要 1.25 次调用。

              【讨论】:

                【解决方案8】:

                随机数通常长序列的 1 和 0,所以我不确定我是否完全理解为什么你不能使用简单的 linear congruential generator 并移入或移出多少位你需要。它们速度极快,肉眼看起来非常随机,您可以选择在您需要的任何正范围内产生随机整数的系数。如果您需要 32 个“随机查找”位,只需生成四个随机数并从每个位中取出低 8 位。

                您根本不需要实现自己的,因为在大多数语言中,随机库已经实现了一个。

                但是,如果您确定需要特定的 1 密度,则始终可以从一个设置了所需数量 1 的数字开始

                int a = 0x00FF;
                

                然后使用bit twiddling hack 对该数字中的位进行位级混洗。

                【讨论】:

                  【解决方案9】:

                  这就是我要做的。我会使用一个像 00101011100101100110100101100101 这样的数字,并且每次都会随机旋转它。

                  但是您确定典型的伪随机生成器不会这样做吗?你试过了吗?无论如何,你不会有很多长的 0 和 1 字符串。

                  如果您要使用库随机数并且担心设置的位太多或太少,there are cheap ways of counting bits

                  【讨论】:

                    【解决方案10】:

                    linear feedback shift register 可能会做你想做的事。

                    根据更新的问题进行编辑:您应该查看shuffle bag,尽管我不确定它可以运行多快。另请参阅question

                    【讨论】:

                    • LFSR 似乎生成低位密度结果(例如,00..0001 是 LFSR 的有效输出?
                    • 正是我要建议的。您可能可以非常简单地在汇编中实现它,以实现一些额外的快速 prn 生成。
                    • @James: When the proper feedbacks bits are chosen an lfsr will cycle through all possible values in the given bit size (except 0).哪个数字先出现完全取决于您的种子。
                    • 我怀疑除了随机源之外我还需要一些东西来使分布偏向短比特序列。
                    【解决方案11】:

                    您需要确切地确定您决定某事物是否“看起来很随机”的规则。然后你使用一个随机数生成器,它可以为你的目的产生足够的“真正随机性”,每次它生成一个看起来不够随机的数字时,你就把那个数字扔掉并生成一个新的。

                    或者您直接生成一个“随机”位序列,并且每次随机生成器输出“错误”的下一位时(这会使它看起来不是随机的),您只需翻转该位。

                    【讨论】:

                    • 我一直在寻找的一半是对用于“感知随机性”的其他指标的洞察。至少,必须避免长序列的 0 或 1。似乎检查生成的数字可能比每个位位置的随机数更有效。
                    【解决方案12】:

                    我真的不知道你所说的“看起来”随机的位模式是什么意思。是否有一些算法来定义它是什么?一种方法可能是制定一个仅由那些对于您的目的而言足够随机的数字组成的数组,然后从该数组中随机选择元素并将它们推送到流中。你似乎试图做的事情对我来说似乎很奇怪,但可能注定要失败。如果您有两个 32 位数字,它们分别符合您的明显随机性标准,但是当并排放置时,会产生足够长的 0 或 1 流以看起来是组成的?

                    终于忍不住了。


                    【讨论】:

                    • “看起来随机”的位模式是(至少)最小化 0 或 1 的长序列的模式。 4 对每边的长序列 0 失败。
                    猜你喜欢
                    • 2020-05-23
                    • 2012-03-13
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-02-21
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-03-18
                    相关资源
                    最近更新 更多