我看到这个问题被问了很多,但从未见过真正具体的答案。所以我将在这里发布一篇文章,希望能帮助人们理解为什么在使用随机数生成器时会出现“模偏差”,比如rand()
在 C++ 中。
10 回答
rand()
一个伪随机数生成器也是如此,它在 0 和 之间选择一个自然数RAND_MAX
,这是一个定义在 中的常数cstdlib
(请参阅这篇文章以获得关于 的一般概述rand()
)。
现在如果你想生成一个介于 0 和 2 之间的随机数会发生什么?为了解释起见,假设RAND_MAX
是 10,我决定通过调用来生成 0 到 2 之间的随机数rand()%3
。但是,rand()%3
不会以相等的概率产生 0 和 2 之间的数字!
当rand()
返回 0、3、6 或 9 时, rand()%3 == 0
. 因此,P(0) = 4/11
当rand()
返回 1、4、7 或 10 时, rand()%3 == 1
. 因此,P(1) = 4/11
当rand()
返回 2、5 或 8 时, rand()%3 == 2
. 因此,P(2) = 3/11
这不会以相等的概率生成 0 和 2 之间的数字。当然,对于较小的范围,这可能不是最大的问题,但对于较大的范围,这可能会扭曲分布,使较小的数字产生偏差。
那么什么时候rand()%n
以相等的概率返回从 0 到 n-1 的数字范围呢?当RAND_MAX%n == n - 1
. 在这种情况下,除了我们之前的假设rand()
确实返回了一个介于 0 之间且RAND_MAX
概率相等的数字,n 的模类也将均匀分布。
那么我们如何解决这个问题呢?一种粗略的方法是不断生成随机数,直到获得所需范围内的数字:
int x;
do {
x = rand();
} while (x >= n);
但这对于 的低值是低效的n
,因为您只有n/RAND_MAX
机会获得范围内的值,因此您需要平均执行对 的RAND_MAX/n
调用。rand()
一种更有效的公式方法是取一些长度可被 整除的大范围n
,例如RAND_MAX - RAND_MAX % n
,不断生成随机数,直到得到一个位于该范围内的随机数,然后取模:
int x;
do {
x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));
x %= n;
对于较小的值n
,这很少需要多次调用rand()
.
作品引用和延伸阅读:
继续随机选择是消除偏见的好方法。
更新
如果我们在可被 整除的范围内搜索 x,我们可以加快代码速度n
。
// Assumptions
// rand() in [0, RAND_MAX]
// n in (0, RAND_MAX]
int x;
// Keep searching for an x in a range divisible by n
do {
x = rand();
} while (x >= RAND_MAX - (RAND_MAX % n))
x %= n;
上面的循环应该非常快,平均说 1 次迭代。
@user1413793 关于这个问题是正确的。我不打算进一步讨论这个问题,除了要说明一点:是的,对于 的小值n
和大的值RAND_MAX
,模偏差可以非常小。但是使用偏差诱导模式意味着每次计算随机数时都必须考虑偏差,并为不同的情况选择不同的模式。如果你做出错误的选择,它引入的错误是微妙的,几乎不可能进行单元测试。与仅使用适当的工具(例如arc4random_uniform
)相比,这是额外的工作,而不是更少的工作。做更多的工作并得到一个更糟糕的解决方案是糟糕的工程,尤其是在大多数平台上每次都做对很容易的情况下。
不幸的是,解决方案的实施都是不正确的或效率低于应有的水平。(每个解决方案都有各种注释来解释问题,但没有一个解决方案被修复来解决这些问题。)这可能会使随便的答案寻求者感到困惑,所以我在这里提供了一个已知良好的实现。
同样,最好的解决方案是arc4random_uniform
在提供它的平台上使用,或者为您的平台使用类似的范围解决方案(例如Random.nextInt
在 Java 上)。它会做正确的事情,而不会给您带来任何代码成本。这几乎总是正确的调用。
如果你没有arc4random_uniform
,那么你可以使用开源的力量来看看它是如何在更广泛的 RNG 之上实现的(ar4random
在这种情况下,类似的方法也可以在其他 RNG 之上工作)。
这是OpenBSD 的实现:
/*
* Calculate a uniformly distributed random number less than upper_bound
* avoiding "modulo bias".
*
* Uniformity is achieved by generating new random numbers until the one
* returned is outside the range [0, 2**32 % upper_bound). This
* guarantees the selected random number will be inside
* [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound)
* after reduction modulo upper_bound.
*/
u_int32_t
arc4random_uniform(u_int32_t upper_bound)
{
u_int32_t r, min;
if (upper_bound < 2)
return 0;
/* 2**32 % x == (2**32 - x) % x */
min = -upper_bound % upper_bound;
/*
* This could theoretically loop forever but each retry has
* p > 0.5 (worst case, usually far better) of selecting a
* number inside the range we need, so it should rarely need
* to re-roll.
*/
for (;;) {
r = arc4random();
if (r >= min)
break;
}
return r % upper_bound;
}
对于那些需要实现类似事情的人来说,值得注意的是对此代码的最新提交评论:
更改 arc4random_uniform() 以计算
2**32 % upper_bound
为-upper_bound % upper_bound
。简化代码并使其在 ILP32 和 LP64 架构上相同,并且通过使用 32 位余数而不是 64 位余数,在 LP64 架构上也稍微快一些。Jorden Verwer 在 tech@ok deraadt 上指出;djm 或 otto 没有反对意见
Java 实现也很容易找到(参见上一个链接):
public int nextInt(int n) {
if (n <= 0)
throw new IllegalArgumentException("n must be positive");
if ((n & -n) == n) // i.e., n is a power of 2
return (int)((n * (long)next(31)) >> 31);
int bits, val;
do {
bits = next(31);
val = bits % n;
} while (bits - val + (n-1) < 0);
return val;
}
定义
模偏差是使用模算术将输出集减少为输入集子集的固有偏差。通常,只要输入和输出集之间的映射不均匀分布,就会存在偏差,例如在输出集的大小不是输入集大小的除数时使用模算术的情况。
这种偏差在计算中特别难以避免,其中数字表示为位串:0 和 1。找到真正随机的随机源也非常困难,但超出了本文的讨论范围。对于此答案的其余部分,假设存在无限的真正随机位来源。
问题示例
让我们考虑使用这些随机位来模拟掷骰子(0 到 5)。有 6 种可能性,所以我们需要足够的位数来表示数字 6,也就是 3 位。不幸的是,3 个随机位会产生 8 种可能的结果:
000 = 0, 001 = 1, 010 = 2, 011 = 3
100 = 4, 101 = 5, 110 = 6, 111 = 7
我们可以通过取模 6 的值将结果集的大小精确地减小到 6,但是这会出现模偏差问题:110
产生 0,111
产生 1。这个 die 已加载。
潜在的解决方案
方法 0:
与其依赖随机位,理论上可以雇佣一支小部队整天掷骰子并将结果记录在数据库中,然后每个结果只使用一次。这与听起来一样实用,而且很可能无论如何都不会产生真正的随机结果(双关语)。
方法一:
一个简单但数学上正确的解决方案不是使用模数,而是丢弃产生的结果110
并111
简单地用 3 个新位重试。不幸的是,这意味着每次掷骰有 25% 的机会需要重新掷骰,包括每次重新掷骰本身。除了最微不足道的用途外,这显然对所有用途都是不切实际的。
方法二:
使用更多位:使用 4 位而不是 3 位。这会产生 16 种可能的结果。当然,在结果大于 5 的任何时候重新滚动会使情况变得更糟(10/16 = 62.5%),因此仅此一项也无济于事。
请注意,2 * 6 = 12 < 16,因此我们可以安全地取任何小于 12 的结果并减少该模 6 以均匀分布结果。必须丢弃其他 4 个结果,然后像以前的方法一样重新滚动。
一开始听起来不错,但让我们检查一下数学:
4 discarded results / 16 possibilities = 25%
在这种情况下,额外的 1 位根本没有帮助!
这个结果很不幸,但让我们用 5 位再试一次:
32 % 6 = 2 discarded results; and
2 discarded results / 32 possibilities = 6.25%
一个明确的改进,但在许多实际情况下还不够好。好消息是,添加更多位永远不会增加需要丢弃和重新滚动的机会。这不仅适用于骰子,而且适用于所有情况。
然而,正如演示的那样,添加一个额外的位可能不会改变任何事情。事实上,如果我们将滚动增加到 6 位,概率仍然是 6.25%。
这引出了两个额外的问题:
- 如果我们添加足够多的位,是否可以保证丢弃的概率会减小?
- 一般情况下多少位就足够了?
通用解决方案
谢天谢地,第一个问题的答案是肯定的。6 的问题是 2^x mod 6 在 2 和 4 之间翻转,这恰好是 2 的倍数,因此对于偶数 x > 1,
[2^x mod 6] / 2^x == [2^(x+1) mod 6] / 2^(x+1)
因此,6 是一个例外而不是规则。可以找到更大的模数,以相同的方式产生连续的 2 次方,但最终这必须回绕,并且丢弃的概率将降低。
在不提供进一步证明的情况下,通常使用所需位数的两倍将提供更小的、通常是微不足道的丢弃机会。
概念证明
这是一个使用 OpenSSL 的 libcrypo 提供随机字节的示例程序。编译时,请务必链接到-lcrypto
大多数人都应该使用的库。
#include <iostream>
#include <assert.h>
#include <limits>
#include <openssl/rand.h>
volatile uint32_t dummy;
uint64_t discardCount;
uint32_t uniformRandomUint32(uint32_t upperBound)
{
assert(RAND_status() == 1);
uint64_t discard = (std::numeric_limits<uint64_t>::max() - upperBound) % upperBound;
uint64_t randomPool = RAND_bytes((uint8_t*)(&randomPool), sizeof(randomPool));
while(randomPool > (std::numeric_limits<uint64_t>::max() - discard)) {
RAND_bytes((uint8_t*)(&randomPool), sizeof(randomPool));
++discardCount;
}
return randomPool % upperBound;
}
int main() {
discardCount = 0;
const uint32_t MODULUS = (1ul << 31)-1;
const uint32_t ROLLS = 10000000;
for(uint32_t i = 0; i < ROLLS; ++i) {
dummy = uniformRandomUint32(MODULUS);
}
std::cout << "Discard count = " << discardCount << std::endl;
}
我鼓励使用MODULUS
和ROLLS
值来查看在大多数情况下实际发生了多少重新滚动。持怀疑态度的人也可能希望将计算值保存到文件中并验证分布是否正常。
马克的解决方案(公认的解决方案)几乎是完美的。
int x; do { x = rand(); } while (x >= (RAND_MAX - RAND_MAX % n)); x %= n;
2016 年 3 月 25 日 23:16 编辑
马克艾默里 39k21170211
但是,它有一个警告,即在RAND_MAX
( RM
) 小于 1的任何情况下N
(其中N
= 可能的有效结果的数量)的倍数,都会丢弃一组有效的结果。
即,当“丢弃值的计数”( D
) 等于 时N
,它们实际上是有效集合 ( V)
,而不是无效集合 ( I
)。
N
造成这种情况的原因是,Mark 在某些时候忽略了和之间的区别Rand_Max
。
N
是一个集合,其有效成员仅由正整数组成,因为它包含有效的响应计数。(例如:设置N
= {1, 2, 3, ... n }
)
Rand_max
然而,是一个集合(根据我们的目的定义)包括任意数量的非负整数。
在它最通用的形式中,这里定义为Rand Max
所有有效结果的集合,理论上可以包括负数或非数值。
因此Rand_Max
,最好将其定义为“可能的响应”的集合。
然而N
,对一组有效响应中的值的计数进行操作,因此即使在我们的特定情况下定义,Rand_Max
它也将是一个比它包含的总数小一的值。
使用 Mark 的解决方案,值在以下情况下被丢弃:X => RM - RM % N
EG:
Ran Max Value (RM) = 255
Valid Outcome (N) = 4
When X => 252, Discarded values for X are: 252, 253, 254, 255
So, if Random Value Selected (X) = {252, 253, 254, 255}
Number of discarded Values (I) = RM % N + 1 == N
IE:
I = RM % N + 1
I = 255 % 4 + 1
I = 3 + 1
I = 4
X => ( RM - RM % N )
255 => (255 - 255 % 4)
255 => (255 - 3)
255 => (252)
Discard Returns $True
正如您在上面的示例中所见,当 X(我们从初始函数中获得的随机数)的值为 252、253、254 或 255 时,即使这四个值包含一组有效的返回值,我们也会丢弃它.
IE:当丢弃值的计数(I)= N(有效结果的数量)时,原始函数将丢弃一组有效的返回值。
如果我们将值 N 和 RM 之间的差异描述为 D,即:
D = (RM - N)
然后随着 D 的值变小,由于这种方法导致的不需要的重新滚动百分比在每个自然乘法处增加。(当 RAND_MAX 不等于质数时,这是值得关注的)
例如:
RM=255 , N=2 Then: D = 253, Lost percentage = 0.78125%
RM=255 , N=4 Then: D = 251, Lost percentage = 1.5625%
RM=255 , N=8 Then: D = 247, Lost percentage = 3.125%
RM=255 , N=16 Then: D = 239, Lost percentage = 6.25%
RM=255 , N=32 Then: D = 223, Lost percentage = 12.5%
RM=255 , N=64 Then: D = 191, Lost percentage = 25%
RM=255 , N= 128 Then D = 127, Lost percentage = 50%
由于 N 越接近 RM 所需的 Rerolls 百分比就会增加,因此根据运行代码的系统的约束和正在查找的值,这对于许多不同的值可能是有效的关注点。
为了否定这一点,我们可以做一个简单的修改如下所示:
int x;
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );
x %= n;
这提供了一个更通用的公式版本,它解释了使用模数来定义最大值的额外特性。
对 RAND_MAX 使用较小值的示例,该值是 N 的乘积。
标记'原始版本:
RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X >= (RAND_MAX - ( RAND_MAX % n ) )
When X >= 2 the value will be discarded, even though the set is valid.
通用版本 1:
RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X > (RAND_MAX - ( ( RAND_MAX % n ) + 1 ) % n )
When X > 3 the value would be discarded, but this is not a vlue in the set RAND_MAX so there will be no discard.
此外,在 N 应该是 RAND_MAX 中值的数量的情况下;在这种情况下,您可以设置 N = RAND_MAX +1,除非 RAND_MAX = INT_MAX。
循环方式你可以只使用 N = 1,但是任何 X 值都将被接受,并为你的最终乘数放入一个 IF 语句。但也许您的代码可能有正当理由在使用 n = 1 调用函数时返回 1...
因此,当您希望 n = RAND_MAX+1 时,最好使用 0,这通常会提供 Div 0 错误
通用版本 2:
int x;
if n != 0 {
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );
x %= n;
} else {
x = rand();
}
这两种解决方案都解决了当 RM+1 是 n 的乘积时会出现不必要的丢弃有效结果的问题。
当您需要 n 等于 RAND_MAX 中包含的总可能值集时,第二个版本还涵盖了边缘情况。
两者中的修改方法是相同的,并且允许更通用的解决方案来满足提供有效随机数和最小化丢弃值的需求。
重申:
扩展标记示例的基本通用解决方案:
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x;
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );
x %= n;
允许另一种 RAND_MAX+1 = n 场景的扩展通用解决方案:
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x;
if n != 0 {
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );
x %= n;
} else {
x = rand();
}
在某些语言(特别是解释型语言)中,在 while 条件之外进行比较操作的计算可能会导致更快的结果,因为无论需要多少次重试,这都是一次性计算。YMMV!
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x; // Resulting random number
int y; // One-time calculation of the compare value for x
y = RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n)
if n != 0 {
do {
x = rand();
} while (x > y);
x %= n;
} else {
x = rand();
}
使用模数有两个常见的抱怨。
一个适用于所有发电机。在极限情况下更容易看到。如果您的生成器的 RAND_MAX 为 2(不符合 C 标准)并且您只需要 0 或 1 作为值,则使用模数生成 0 的频率将是生成器生成 0 和 2 时的两倍生成 1(当生成器生成 1 时)。请注意,只要您不删除值,无论您使用的是从生成器值到所需值的映射,这都是正确的,其中一个的出现频率是另一个的两倍。
至少对于它们的某些参数,某种生成器的随机性较低,但遗憾的是这些参数具有其他有趣的特征(例如能够使 RAND_MAX 比 2 的幂小一)。这个问题是众所周知的,很长一段时间库实现可能会避免这个问题(例如,C 标准中的示例 rand() 实现使用这种生成器,但删除了 16 个不太重要的位),但有些人喜欢抱怨那你可能运气不好
使用类似的东西
int alea(int n){
assert (0 < n && n <= RAND_MAX);
int partSize =
n == RAND_MAX ? 1 : 1 + (RAND_MAX-n)/(n+1);
int maxUsefull = partSize * n + (partSize-1);
int draw;
do {
draw = rand();
} while (draw > maxUsefull);
return draw/partSize;
}
生成 0 和 n 之间的随机数将避免这两个问题(并且它避免了 RAND_MAX == INT_MAX 溢出)
顺便说一句,C++11 引入了归约和 rand() 之外的其他生成器的标准方法。
如果RAND_MAX
值为3
(实际上它应该远高于该值,但偏差仍然存在),那么从这些计算中可以看出存在偏差:
1 % 2 = 1
2 % 2 = 0
3 % 2 = 1
random_between(1, 3) % 2 = more likely a 1
在这种情况下,% 2
当您想要一个介于0
和之间的随机数时,您不应该这样做1
。你可以通过这样做得到一个介于0
和之间的随机数,因为在这种情况下:是 的倍数。2
% 3
RAND_MAX
3
另一种方法
有更简单的但要添加到其他答案,这是我的解决方案,以获得 和 之间的随机数0
,n - 1
因此n
不同的可能性,没有偏见。
- 编码可能性的数量所需的位数(不是字节)是您需要的随机数据的位数
- 从随机位编码数字
- 如果此数字为
>= n
,则重新启动(不取模)。
真正的随机数据不容易获得,所以为什么要使用比需要更多的位。
下面是 Smalltalk 中的一个示例,使用来自伪随机数生成器的位缓存。我不是安全专家,所以使用风险自负。
next: n
| bitSize r from to |
n < 0 ifTrue: [^0 - (self next: 0 - n)].
n = 0 ifTrue: [^nil].
n = 1 ifTrue: [^0].
cache isNil ifTrue: [cache := OrderedCollection new].
cache size < (self randmax highBit) ifTrue: [
Security.DSSRandom default next asByteArray do: [ :byte |
(1 to: 8) do: [ :i | cache add: (byte bitAt: i)]
]
].
r := 0.
bitSize := n highBit.
to := cache size.
from := to - bitSize + 1.
(from to: to) do: [ :i |
r := r bitAt: i - from + 1 put: (cache at: i)
].
cache removeFrom: from to: to.
r >= n ifTrue: [^self next: n].
^r
模减少是使随机整数生成器避免永远运行的最坏情况的常用方法。
然而,当可能的整数范围未知时,通常没有办法在不引入偏差的情况下“修复”这种永远运行的最坏情况。不仅模约归约(rand() % n
在接受的答案中讨论过)会以这种方式引入偏差,还有 Daniel Lemire 的“乘法和移位”归约,或者如果您在一定次数的迭代后停止拒绝结果。(要清楚,这并不意味着没有办法解决伪随机生成器中存在的偏差问题。例如,即使模数和其他归约通常是有偏差的,如果可能的范围内,它们不会有偏差问题整数是 2 的幂,如果随机生成器产生无偏随机位或它们的块。)
该答案的其余部分将显示随机生成器中运行时间和偏差之间的关系。从这里开始,我们将假设我们有一个“真正的”随机生成器,它可以产生无偏且独立的随机位。*
1976 年,DE Knuth 和 AC Yao 表明,任何仅使用随机位以给定概率生成随机整数的算法都可以表示为二叉树,其中随机位指示遍历树和每个叶子(端点)的方式对应一个结果。在这种情况下,我们正在处理在 [0, n) 中生成随机整数的算法,其中每个整数的选择概率为 1/n。如果对于所有结果,树中出现相同数量的叶子,则该算法是无偏的。但是如果 1/n 有一个非终止的二进制展开式(如果 n 不是 2 的幂,就会出现这种情况),那么该算法只有在以下情况下才是无偏的——</p>
- 二叉树具有“无限”深度,或
- 二叉树最后包括“拒绝”叶子,
在任何一种情况下,算法都不会在恒定时间内运行,并且在最坏的情况下会永远运行。(另一方面,当n
是 2 的幂时,最优二叉树将具有有限深度且没有拒绝节点。)
二叉树的概念还表明,任何“修复”这种最坏情况时间复杂度的方法通常都会导致偏差。(同样,这并不意味着没有办法解决伪随机生成器中存在的偏差问题。)例如,模约简相当于一棵二叉树,其中拒绝叶被标记的结果替换——但因为有更多可能结果比拒绝叶子,只有一些结果可以代替拒绝叶子,从而引入偏见。如果您在一定次数的迭代后停止拒绝,则会产生相同类型的二叉树 - 以及相同类型的偏差。(但是,根据应用程序,这种偏差可能可以忽略不计。随机整数生成也有安全方面的问题,在这个答案中讨论太复杂了。)
To illustrate, the following JavaScript code implements a random integer algorithm called the Fast Dice Roller by J. Lumbroso (2013). Note that it includes a rejection event and a loop which are necessary to make the algorithm unbiased in the general case.
function randomInt(minInclusive, maxExclusive) {
var maxInclusive = (maxExclusive - minInclusive) - 1
var x = 1
var y = 0
while(true) {
x = x * 2
var randomBit = (Math.random() < 0.5 ? 0 : 1)
y = y * 2 + randomBit
if(x > maxInclusive) {
if (y <= maxInclusive) { return y + minInclusive }
// Rejection
x = x - maxInclusive - 1
y = y - maxInclusive - 1
}
}
}
Note
* This answer won't involve the rand()
function in C because it has many issues. Perhaps the most serious here is the fact that the C standard does not explicitly specify a particular distribution for the numbers returned by rand()
, not even a uniform distribution.
正如公认的答案所表明的那样,“模偏差”的根源在于RAND_MAX
. 他使用极小的值RAND_MAX
(10) 来表明,如果 RAND_MAX 为 10,那么您尝试使用 % 生成介于 0 和 2 之间的数字,将产生以下结果:
rand() % 3 // if RAND_MAX were only 10, gives
output of rand() | rand()%3
0 | 0
1 | 1
2 | 2
3 | 0
4 | 1
5 | 2
6 | 0
7 | 1
8 | 2
9 | 0
所以有 4 个 0 的输出(4/10 的机会),只有 3 个 1 和 2 的输出(每个 3/10 的机会)。
所以是有偏见的。较低的数字有更好的机会出来。
RAND_MAX
但这只是在很小的时候才表现得如此明显。或者更具体地说,当您修改的数量与RAND_MAX
.
比循环(效率极低,甚至不应该被建议)更好的解决方案是使用具有更大输出范围的 PRNG。Mersenne Twister算法的最大输出为 4,294,967,295。因此,出于所有意图和目的这样做MersenneTwister::genrand_int32() % 10
,将平均分配,并且模偏差效应将几乎消失。
我刚刚为冯诺依曼的无偏硬币翻转方法编写了一个代码,理论上应该可以消除随机数生成过程中的任何偏差。更多信息可以在(http://en.wikipedia.org/wiki/Fair_coin)找到
int unbiased_random_bit() {
int x1, x2, prev;
prev = 2;
x1 = rand() % 2;
x2 = rand() % 2;
for (;; x1 = rand() % 2, x2 = rand() % 2)
{
if (x1 ^ x2) // 01 -> 1, or 10 -> 0.
{
return x2;
}
else if (x1 & x2)
{
if (!prev) // 0011
return 1;
else
prev = 1; // 1111 -> continue, bias unresolved
}
else
{
if (prev == 1)// 1100
return 0;
else // 0000 -> continue, bias unresolved
prev = 0;
}
}
}