Pertaining to rand() % n
being less than ideal
Doing rand() % n
has a non-uniform distribution. You will get a disproportionate number of certain values because the number of values isn't a multiple of 20
Next, rand()
is typically a linear congruential generator (there are many others, just this is the most likely one implemented - and with less than ideal parameters (there are many ways to select the parameters)). The biggest problem with this is that often the low bits in it (the ones you get with a % 20
type expression) are not that random. I recall one rand()
from years ago where the lowest bit alternated from 1
to 0
with each call to rand()
-- it wasn't very random.
From rand(3) man page:
The versions of rand() and srand() in the Linux C Library use the same
random number generator as random() and srandom(), so the lower-order
bits should be as random as the higher-order bits. However, on older
rand() implementations, and on current implementations on different
systems, the lower-order bits are much less random than the higher-
order bits. Do not use this function in applications intended to be
portable when good randomness is needed.
This may be now relegated to history, but it is quite possible that you've still got a poor rand() implementation hiding down somewhere in the stack. In which case, its still quite applicable.
The thing to do is to actually use a good random number library (that gives good random numbers) and then ask for random numbers within the range you want.
An example of a good random number bit of code (from 13:00 in the linked video)
#include <iostream>
#include <random>
int main() {
std::mt19937 mt(1729); // yes, this is a fixed seed
std::uniform_int_distribution<int> dist(0, 99);
for (int i = 0; i < 10000; i++) {
std::cout << dist(mt) << " ";
}
std::cout << std::endl;
}
Compare this to:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand(time(NULL));
for (int i = 0; i < 10000; i++) {
printf("%d ", rand() % 100);
}
printf("\n");
}
Run both of these programs and compare how often certain numbers come up (or don't come up) in that output.
Related video: rand() considered harmful
Some historical aspects of rand() causing bugs in Nethack that one should watch and consider in one's own implementations:
Nethack RNG Problem
Rand() is a very fundamental function for Nethack's random number generation. The way Nethack uses it is buggy or it may be argued that lrand48() produces crappy pseudo-random numbers. (However, lrand48() is a library function using a defined PRNG method and any program that uses it should take into account the weaknesses of that method.)
The bug is that Nethack relies (sometimes exclusively as is the case in rn(2)) on the lower bits of the results from lrand48(). Because of this, RNG in the whole game works bad. This is especially noticable before user actions introduce further randomness, i.e. in character generation and first level creation.
While the above was from 2003, it should still be kept in mind as it may not be the case that all of the systems running your intended game will be an up to date Linux system with a good rand() function.
If you are just doing this for yourself, you can test how good your random number generator is by writing some code and testing the output with ent.
On the properties of random numbers
There are other interpretations of 'random' that aren't exactly random. In a random stream of data, it is quite possible to get the same number twice. If you flip a coin (random), it is quite possible to get two heads in a row. Or throw a dice twice and get the same number twice in a row. Or spinning a roulette wheel and getting the same number twice there.
The distribution of numbers
When playing a song list, people expect 'random' to mean that the same song or artist won't be played a second time in a row. Having a playlist play The Beatles twice in a row is considered 'not random' (though it is random). The perception that for a play list of four songs played a total of eight times:
1 3 2 4 1 2 4 3
is more 'random' than:
1 3 3 2 1 4 4 2
More on this for the 'shuffling' of songs: How to shuffle songs?
On repeated values
If you don't want to repeat values, there is a different approach that should be considered. Generate all the possible values, and shuffle them.
If you are calling rand()
(or any other random number generator), you are calling it with replacement. You can always get the same number twice. One option is to toss out the values again and again until you select one that meets your requirements. I will point out that this has a non-determistic runtime and it is possible that you could find yourself in a situation where there is an infinite loop unless you start doing a more complex back tracing.
List and Pick
Another option is to generate a list of all possible valid states and then select a random element from that list. Find all the empty spots (that meet some rules) in the room and then pick a random one from that list. And then do it again and again until you're done.
Shuffle
The other approach is to shuffle as if it was a deck of cards. Start out with all the empty spots in the room and then start assigning them by dealing out the empty spots, one at a time, to each rule/process asking for an empty spot. You are done when you run out of cards or things stop asking for them.
rand()
is a pityfull RNG, and anyway with such a small range, you don't just have to expect collisions, they are nearly guaranteed.rand()
is a lousy RNG, it is probably appropriate for a single player game, and RNG quality isn't the issue here.rand()
seems to be irrelevant here. There is no cryptography involved, and any RNG will very probably give collisions in such a small map.