Skip to content

Commit

Permalink
Avoid modulo bias when generating random numbers.
Browse files Browse the repository at this point in the history
Add Q_rand_uniform() function that generates random numbers modulo N,
using the trick from OpenBSD implementation of arc4random_uniform().
  • Loading branch information
skullernet authored and Paril committed Dec 10, 2021
1 parent a0777ee commit 8eea732
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 3 deletions.
1 change: 1 addition & 0 deletions inc/shared/shared.h
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ static inline int Q_gcd(int a, int b)

void Q_srand(uint32_t seed);
uint32_t Q_rand(void);
uint32_t Q_rand_uniform(uint32_t n);

#define clamp(a,b,c) ((a)<(b)?(a)=(b):(a)>(c)?(a)=(c):(a))
#define cclamp(a,b,c) ((b)>(c)?clamp(a,c,b):clamp(a,b,c))
Expand Down
2 changes: 1 addition & 1 deletion src/baseq2/g_items.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ void DoRespawn(edict_t *ent)
for (count = 0, ent = master; ent; ent = ent->chain, count++)
;

choice = Q_rand() % count;
choice = Q_rand_uniform(count);

for (count = 0, ent = master; count < choice; ent = ent->chain, count++)
;
Expand Down
2 changes: 1 addition & 1 deletion src/baseq2/g_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ edict_t *G_PickTarget(char *targetname)
return NULL;
}

return choice[Q_rand() % num_choices];
return choice[Q_rand_uniform(num_choices)];
}


Expand Down
2 changes: 1 addition & 1 deletion src/baseq2/p_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,7 @@ edict_t *SelectRandomDeathmatchSpawnPoint(void)
} else
count -= 2;

selection = Q_rand() % count;
selection = Q_rand_uniform(count);

spot = NULL;
do {
Expand Down
36 changes: 36 additions & 0 deletions src/shared/shared.c
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,13 @@ void Q_setenv(const char *name, const char *value)
static uint32_t mt_state[N];
static uint32_t mt_index;

/*
==================
Q_srand
Seed PRNG with initial value
==================
*/
void Q_srand(uint32_t seed)
{
mt_index = N;
Expand All @@ -881,6 +888,13 @@ void Q_srand(uint32_t seed)
mt_state[i] = seed = 1812433253U * (seed ^ seed >> 30) + i;
}

/*
==================
Q_rand
Generate random integer in range [0, 2^32)
==================
*/
uint32_t Q_rand(void)
{
uint32_t x, y;
Expand Down Expand Up @@ -913,6 +927,28 @@ uint32_t Q_rand(void)
return y;
}

/*
==================
Q_rand_uniform
Generate random integer in range [0, n) avoiding modulo bias
==================
*/
uint32_t Q_rand_uniform(uint32_t n)
{
uint32_t r, m;

if (n < 2)
return 0;

m = -n % n; // 2^32 mod n
do {
r = Q_rand();
} while (r < m);

return r % n;
}

/*
=====================================================================
Expand Down

0 comments on commit 8eea732

Please sign in to comment.