【问题标题】:glibc rand function implementationglibc rand 函数实现
【发布时间】:2013-09-09 03:43:31
【问题描述】:

我正在阅读带有 glibc 源代码的 c standard library rand() function implementationstdlib/random_r.c,第 359 行

int
__random_r (buf, result)
            struct random_data *buf;
            int32_t *result;
{
  int32_t *state;

  if (buf == NULL || result == NULL)
    goto fail;

  state = buf->state;

  if (buf->rand_type == TYPE_0)
    {
      int32_t val = state[0];
      val = ((state[0] * 1103515245) + 12345) & 0x7fffffff;
      state[0] = val;
      *result = val;
    }
  else
    {
      int32_t *fptr = buf->fptr;
      int32_t *rptr = buf->rptr;
      int32_t *end_ptr = buf->end_ptr;
      int32_t val;

      val = *fptr += *rptr;
      /* Chucking least random bit.  */
      *result = (val >> 1) & 0x7fffffff;
      ++fptr;
      if (fptr >= end_ptr)
        {
          fptr = state;
          ++rptr;
        }
      else
        {
          ++rptr;
          if (rptr >= end_ptr)
            rptr = state;
        }
      buf->fptr = fptr;
      buf->rptr = rptr;
    }
  return 0;

 fail:
  __set_errno (EINVAL);
  return -1;
}

我不明白(buf->rand_type != TYPE_0)时random_r如何生成随机数,请解释一下?谢谢。

【问题讨论】:

  • 在我看来就像一个标准的老式线性同余生成器(谷歌搜索)。不是一个好的算法,但可以用于简单的使用。

标签: c random glibc


【解决方案1】:

glibcrand() 有两种不同的生成器实现:

  1. 一个简单的线性同余生成器 (LCG),由以下等式定义:

    val = ((state * 1103515245) + 12345) & 0x7fffffff

    & 0x7fffffff 丢弃最不随机的最高位)

    这是一个非常简单的单态 LCG。它有一些缺点。最重要的是,因为它是一个单一的状态生成器,它不会在每个单独的rand() 调用上生成一个完全伪随机数。它真正的作用是以伪随机顺序遍历整个范围 (2^31)。这有一个意味深长的含义:当您获得某个数字时,意味着您将在当前时期不会再次获得该数字。您将在接下来的 2^31 rand() 电话中再次获得该号码,不早也不晚。

    此生成器在glibc 源中称为TYPE_0

  2. 稍微高级一点的附加反馈生成器。该生成器有许多状态,这意味着它不具有上述“遍历属性”。您可以在同一时期内两次(或多次)获得相同的号码。

    您可以找到该算法的精彩描述here

    此生成器在glibc 源中称为TYPE_1TYPE_2TYPE_3TYPE_4

    回到你的问题,这就是它产生价值的方式:

    seeding_stage() // (code omitted here, see the description from above link)
    
    for (i=344; i<MAX; i++)
    {
        r[i] = r[i-31] + r[i-3];
        val = ((unsigned int) r[i]) >> 1;
    }
    

    您问题中else 之后的代码就是上面的代码,但以不同的方式编写(使用指向包含先前值的数组的指针)。

使用哪个生成器取决于使用initstate() 函数设置的初始状态的大小。第一个(LCG)生成器仅在状态大小为 8 字节时使用。当它更大时,使用第二个生成器。当您使用srand() 设置种子时,状态的大小默认为 128 字节,因此使用第二个生成器。您在问题中引用的 glibc 源文件中的所有内容都用 cmets 编写。

【讨论】:

    【解决方案2】:

    如果其他人需要对 GNU C 库的 srand()/rand() 函数进行简单的重新实现,这个 C# 类会准确地再现生成的随机数。 unchecked 关键字是明确允许整数溢出。 (基于 Piotr Jurkiewicz 的回答。)

    public class GnuRand
    {
        private uint[] r;
        private int n;
    
        public GnuRand(uint seed)
        {
            r = new uint[344];
    
            unchecked
            {
                r[0] = seed;
                for (int i = 1; i < 31; i++)
                {
                    r[i] = (uint)((16807 * (ulong)r[i - 1]) % 2147483647);
                }
                for (int i = 31; i < 34; i++)
                {
                    r[i] = r[i - 31];
                }
                for (int i = 34; i < 344; i++)
                {
                    r[i] = r[i - 31] + r[i - 3];
                }
            }
    
            n = 0;
        }
    
        public int Next()
        {
            unchecked
            {
                uint x = r[n % 344] = r[(n + 313) % 344] + r[(n + 341) % 344];
                n = (n + 1) % 344;
                return (int)(x >> 1);
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-12-07
      • 2010-11-13
      • 1970-01-01
      • 1970-01-01
      • 2020-09-17
      • 1970-01-01
      • 2013-10-13
      • 2021-02-13
      相关资源
      最近更新 更多