The bell has tolled for rand() (2014)
cpp.indi.frih.net
cpp.indi.frih.net
It's not a random number facility, it's a pseudorandom number facility. And, unless you've explicitly seeded it from an unpredictable source, the results are consistent across runs with otherwise similar contexts, which is a desirable feature in many of the domains PRNGs are used for (it's undesirable when you need an RNG instead of a PRNG, but then so is using a PRNG like C-standard rand() in that case.)
The PRNG/RNG distinction you make is slightly false. Even if you're doing cryptography usually don't want to use a true source of entropy directly, you're better off feeding several sources of entropy into a PRNG. That's what both /dev/random and /dev/urandom give you (despite some common misconceptions).
What most developers really want is a non-deterministic (randomly seeded) PRNG. When you actually want the PRNG to be deterministic, you usually also need to seed it explicitly anyway (e.g. having multiple runs in the same app or allowing the user to specify a seed to generate a map in a game). The implicit is useful for very few developers (if any), and confusing for the vast majority of junior developers out there.
PS: that someone was me
Obviously, it is not cryptographically secure but these RNGs are a class of their own and are typically much slower than needed for other applications.
The problem with rand() is that it may be unseeded (same result each time you run the program), have a very short period, very short range, and fail even basic randomness tests. Fill an image with rand() results and it may not look random at all.
It may not be good enough for monte-carlo simulations, fuzz testing, probabilistic algorithms and structures, etc... Some rand() implementations are too poor even for casual games.
A good example of a terrible RNG causing trouble is in the Monster Hunter 3 Ultimate game. There are some randomly generated items in it that cannot be accessed because of the RNG. Players call it "charm tables" but it is just a result of a really poor RNG.
If you don't really want random numbers, but just "some numbers, I don't care if they're random or not", rand() is fine, but using "int myNumber = 42" is faster and safer.
A weak random number generator that usually powers rand() is also usually faster and takes less CPU to run.
Both of these things were thought to be more important in more innocent times when C was standardized.
OpenBSD's decision makes sense on balance, it gives some security to otherwise insecure programs by default, the trade-off is breaking programs written against the C standard which make assumptions about the documented rand() behavior.
One thing to note though, different platforms don't necessarily implement the rand() function the same way, the standard does not define an algorithm for it, so unless you want your tests to run differently on every different C library it might not be the best idea to begin with.
The standard should be defining something that is good for the general case, without a lot of levers. KISS! That means giving people 4 different PRNG algorithms, plus a hardware interface, etc, is not the right direction. Your average programmer shouldn't have to make a decision more complex than "secure" or "fast" and the API should clearly make the distinction.
In this case, the linux kernel finds about the right match with /dev/random and /dev/urandom. If you want something more complex then you can pick up a 3rd party library and spend 6 months learning about random numbers, how to test their randomness, seed a CSPRNG, etc. Otherwise leave the implementation choices to the experts and make it easy for the common programmer.
That's because it's supposed to be a random-number-generator, not a pseudo-random-number-generator. It needs real entropy.
There was a brief HN discussion on the topic when FreeBSD made their change: https://news.ycombinator.com/item?id=8550457
I mean, maybe you say that my next 4,096 bits of entropy need to be completely independent from the last 4,096, but ib the usual case, I doubt it.
#include <unsafe-deprecated-c-library-functions>
if they really want them.I think this is a nice article, but this part stood out to me. I've heard many times here on HN that it's one of the worst non-cryptographic pseudo-random number generators in common use.
I couldn't find the specific article, but this Q/A had some good information and links: https://cs.stackexchange.com/questions/50059/why-is-the-mers...
> For reasons I can’t fathom, the C standard mandates that rand() must always start as if it were seeded with 1. I can’t imagine why anyone thought that was a good idea. What is the sense of making a random number facility that – by default – isn’t random?
This seems like a good decision to me. There are plenty of use cases for arbitrary values that are consistent across runs (for instance, repeatable unit tests). If the application calls for non-determinism, let the application author do it themselves from an appropriate seed value.
(I maybe somewhat biased from my experiences threading random number generator state in Haskell programs.)
Furthermore, even for those cases where you do want consistency, I doubt you'd want a hardcoded seed value. Most of such cases allow the seed to be parametrized between runs without recompiling (via command line arguments, configs etc), and so they'll still need to invoke srand to set the seed.
On the other hand, saying that without srand, the default is random, would actually allow modern implementations to e.g. initialize from /dev/random. Right now there's basically no cross-platform way to set a truly random seed in ISO C.
It would be helpful if the c standard included a source for an appropriate seed value if that's the policy (which I think is wrong, given the naming it should be on those wanting reproducible behavior to flip a switch or set a fixed seed. Slow default is better than insecure default)
I recently needed some pseudo-randoms, and discovered that I now have to stick a bunch of boilerplate in the code to obtain them. Is there really a difference between me sticking the boilerplate in vs having a function called rand() that does the boilerplate for me?
Edit: The author does state "Responsible teachers and tutors should stop teaching it to new C++ programmers"; I misinterpreted his point.
Here are some of the pain points I'm aware of:
There's no way to use the random device to uniformly seed the internal state of an arbitrary pseudorandom generator. In fact, the usual thing to write will seed with only 32 bits of randomness. (this has been noted by others in this thread)
Some of the floating-point functionality such as std::uniform_real_distribution and generate_canonical have trouble where the text specifies half-open intervals like [0,1) but the (pseudo)code in practice gives closed intervals like [0,1]. Usually this can only be observed when generating values of type 'float'. This makes me generally suspect that the real number distributions in the standard library are probably poorly specified in general. (in fact, the original report that led to issue 2524 was in about the exponential distribution, which per the standard must be defined in terms of generate_canonical) http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#...
Finally, there is support for some frankly weird things in the basic random engines: you can specify a random engine that only returns the numbers 37 and 38, for instance. It turns out that e.g., std::linear_congruential_engine, including std::minstd_rand, can have a minimum possible output value of 1 rather than 0, which in turn makes the random engine's min() method necessary. But why not require engines to generate integral values on the closed interval [0..n], and make it the responsibility of linear_congruential_engine to subtract 1 before returning? A random number generator with (2^31)-2 distinct possible values is going to make for much weirder statistical anomalies than one with period 2^x when someone uses them carelessly (and what other way is there to use random numbers?).
---
Your code
thread_local static std::random_device rd{};
thread_local static std::mt19937 engine{rd()};
does not properly seed a mt19937.It is unfortunate how people keep trying to use `std::mt19937` and, unsurprisingly given how hard it is to use, how they (almost)[0] inevitably fail. Do yourself a favour and use a randomness library that doesn't hate you[1], is simple to seed correctly, produces decent quality randomness, isn't horribly bloated and has more features to boot. It's 2017[2]. Let the Mersenne Twister die.
[0]: https://codereview.stackexchange.com/q/114066/40768
[1]: http://www.pcg-random.org/ or http://xoroshiro.di.unimi.it/
[2]: Well, late 2014 in the article, but close enough.
The issue is that rd() returns at most a 64-bit int, whilst mt19937 has 19937 bits of state. This means you are very restricted.
Note that, for example linux's /dev/random uses 4069 bits of entropy.
Please wiggle your mouse for 1 minute so we can gather entropy for you AES key.
REALLY?One problem with /dev/urandom is you always have to open it, create a filedescriptor just to get to some random number!?