【问题标题】:Uniformity of random numbers taken modulo N取模 N 的随机数的一致性
【发布时间】:2012-10-17 18:07:26
【问题描述】:

[0, n) 中选择随机数的一种常见方法是取 rand()n 的结果:rand() % n。然而,即使可用的rand() 实现返回的结果是完全一致的,当RAND_MAX + 1 没有时,结果 [0, n) 数字的一致性应该不会有问题除以 n?例如。假设RAND_MAX 是 2,而 n 是 2。那么在 3 个可能的 rand() 输出中:0、1 和 2,当我们使用它们取模 n。因此输出根本不会是均匀的。

在实践中这是一个真正的问题吗?在 [0, n) 中选择从rand() 输出统一派生的随机数的更好方法是什么,最好不使用任何浮点运算?

【问题讨论】:

标签: c random uniform


【解决方案1】:

这取决于:

  • RAND_MAX 的值
  • 你的 N 值

让我们假设您的 RAND_MAX 为 2^32。如果 N 相当小(假设为 2),则偏差为 1 / 2^31 - 或者太小而无法注意到。

但是如果 N 稍微大一点,比如 2^20,那么偏差是 1 / 2^12,或大约 4096 中的 1。大很多,但仍然很小。

【讨论】:

  • 相反,我认为答案是正确的。我们假设一个 PRNG 生成具有完美分布的数字。问题是,我们关心偏见吗?我试图提供一种量化偏见的方法,以便提问者可以自己确定他是否可以忍受。这都是非常语言非特定的。
  • 有些系统的RAND_MAX0xffff,导致很多更大的偏差。
  • 更糟。 Visual C++ 实现具有 RAND_MAX==0x7FFF,是 MS-DOS 上的 16 位 MSC 3.0 遗留下来的。
  • @slashingweapon 你能指导我链接/资源以获得计算偏差的正式方法吗?
【解决方案2】:

你是对的,rand() % N 不是精确均匀分布的。确切地说,重要程度取决于您想要的数字范围和您想要的随机性程度,但是如果您想要足够的随机性以至于您甚至会关心它,那么您无论如何都不想使用rand()。获取一个真正的随机数生成器。

也就是说,要获得真正的随机分布,请修改 2 的下一个幂并进行采样,直到获得所需范围内的一个(例如,对于 0-9,使用 while(n = rand()%0x10 > 10);)。

【讨论】:

  • +1 用于解决方法,但通常rand() 的低位具有非常差的熵。使用高位会更聪明。
  • @Kevin:您是否在判断 rand() 的任何特定实现,即现代 glibc 中的实现?
  • @ToddLehman 在我的系统(OSX 10.10)上,低位肯定不统一。在命令行上运行此命令以获得实时更新计数:pastebin.com/D5r7we3H
  • @ToddLehman:我的意思是我的怀疑是大多数实现都使用原始 LCG 输出,而不仅仅是“核心”的 LCG。
  • @ToddLehman:我认为你的希望落空了。 rand 非常糟糕(由于srand 的签名,它最多可以生成UINT_MAX 可能的序列)并且试图使其“更好”只会鼓励人们使用它。滚动您自己的体面(非加密)PRNG 只是几行代码,而这正是您应该做的——除了序列质量之外,它还为您提供有用的属性,例如缺乏全局状态、可恢复性和跨平台相同序列-种子。
【解决方案3】:

您可以采取的一种方法如下:

知道N 的值,您就可以创建R_MAX = ((RAND_MAX + 1) / N) * N; 以保持一致性。

所以你可以做你的自定义rand()函数:

int custom_rand(int mod) {
    int x = rand();
    const int R_MAX = ((RAND_MAX + 1) / mod) * mod;    

    while (x > R_MAX) { // discard the result if it is bigger
        x = rand();
    }

    return (x % mod);
}

【讨论】:

  • 如果 rand_max 是 2^32-1 怎么办?
  • RAND_MAX == INT_MAX 时(很常见)。 RAND_MAX + 1 --> 未定义的行为-(可能是INT_MIN)。
  • 我认为你实际上想要R_MAX = (RAND_MAX / N) * N;while (x >= R_MAX),否则你有产生更多零的偏见,因为R_MAX % mod == 0。此外,do { x = rand(); } while (x >= R_MAX) 在这里会更好,因为这样您就不会写两次 x = rand();
【解决方案4】:

将余数(% 不是 C 中的“模”运算符)用于缩小范围内的统一随机数有两个问题。首先是对较小的数字有轻微的偏见(如上所述),其次是典型的 PRNG 在低位中往往不太随机。我似乎记得 Knuth (计算机编程的艺术,第二卷,半数字算法)以及声称(在从 MIX 转换为 C 之后)rand()%2 是随机单个位的不良来源。最好选择 (rand() > RAND_MAX/2) (或者测试一个高位,如果 RAND_MAX 接近 2 的幂。)

其余的应该足够好,可以在小间隔内随意使用。避免它进行模拟。实际上,对于大型模拟或“蒙特卡洛”计算,完全避免使用 rand()。实现往往有一个大约 2^32 或更小的周期。在 2 GHz 以上的处理器上进行超过 40 亿次试验并不难。

【讨论】:

  • 典型的 LCG(线性同余生成器)在低位确实不太随机,是的。例如,当模数为 2 的幂时,乘以奇数并添加奇数 总是 会反转最低有效位。但这并不意味着典型的 PRNG 有这个问题。所要做的就是运行 LCG 两次并进行位移和异或以很好地混合事物,或其他技巧。任何提供rand() 在低位随机性较低的 C 库都严重损坏。
  • @ToddLehman:来自 C11 规范,在 rand() 函数描述的脚注中:“295) 无法保证产生的随机序列的质量,并且已知某些实现会产生具有令人痛苦的非随机低位位的序列。具有特殊要求的应用程序应使用已知足以满足其需求的生成器。所以,这样的 rand() 函数并没有被 ISO 严重破坏。令人痛心,是的,但没有坏掉。
猜你喜欢
  • 1970-01-01
  • 2019-08-10
  • 1970-01-01
  • 1970-01-01
  • 2011-06-26
  • 1970-01-01
  • 2010-10-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多