Everything’s a learning opportunity, as they say.
I know that @SkyTheCoder’s original post was just trolling, but if you’re going to troll pedantically, you should at least be right! There are a few subtle but important errors in what everyone (including me) has said so far and I think it’s worth taking a moment to go through them in detail.
-
(@SkyTheCoder) “Since math.random(1, 2) only has 1 or 2, there is no middle, it’s an exact 50-50 chance.”
Well, no. Let’s play a game. We’ll roll a die; if the result is a perfect number[1], I’ll pay you $10, if not then you pay me $10. That’s a two state system so according to your logic it’s an exact 50-50 chance and the game is fair.
math.random(1,2)
is only better than if math.random() < .5 then return 0 else return 1 done
if math.random(1,2)
uses a better method of figuring out which number to return. It doesn’t (but we’ll come to that in a minute).
-
Then there’s all the stuff about the difference between [0,1)
and [0,1]
which I think has now been adequately covered in the above. Though I’d just add that theoretically there is no difference between the two as if we can take any value in [0,1]
then the probability of getting exactly 1
is exactly 0
. However, as we can’t take any value but only those in the precision of the device, we get a positive probability of getting 1
if the interval is [0,1]
. But it isn’t, according to PIL.
-
Or is it. Here’s where it starts getting interesting. I looked up the code for math.random
. This is what I found.
static int math_random (lua_State L) {
/ the %' avoids the (rare) case of r==1, and is needed also because on some systems (SunOS!)
rand()’ may return a value larger than RAND_MAX /
lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX;
switch (lua_gettop(L)) { / check number of arguments /
case 0: { / no arguments /
lua_pushnumber(L, r); / Number between 0 and 1 /
break;
}
case 1: { / only upper limit /
int u = luaL_checkint(L, 1);
luaL_argcheck(L, 1<=u, 1, “interval is empty”);
lua_pushnumber(L, floor(ru)+1); /* int between 1 and u' */ break; } case 2: { /* lower and upper limits */ int l = luaL_checkint(L, 1); int u = luaL_checkint(L, 2); luaL_argcheck(L, l<=u, 2, "interval is empty"); lua_pushnumber(L, floor(r*(u-l+1))+l); /* int between
l’ and `u’ */
break;
}
default: return luaL_error(L, “wrong number of arguments”);
}
return 1;
}
Let's take this in reverse. The situation `math.random(1,2)` is handled by the final case. Then we get our random number (in the interval `[0,1)`) and apply the function `floor(r*(u-l+1))+l` which generates an integer between (and including) `l` and `u`. The key point here is that if the `math.random()` is biased then so is `math.random(1,2)`.
But is it? Well, it's defined as `(rand()%RAND_MAX) / RAND_MAX`. The modulus function is there to ensure that we never get `1` as an answer. But there is a serious problem here: it doesn't throw away `1` if it gets it, it reassigns it to `0`. So in fact, `math.random()` returns `0` **twice as often as it should**. If we had infinite precision, this would be twice zero so no difference (really, honestly, absolutely no difference). However, as we don't have infinite precision then that really is skewed. Moreover, the comment above this line indicates that on some systems `rand()` can return a value above `RAND_MAX` so we're not dealing with the case that `rand()` returns just `RAND_MAX` (which is approximately 1/precision) but that `rand()` returns values in some unspecified interval above `RAND_MAX` (and without knowing the interval, it's impossible to know how bad this is).
To illustrate the point, imagine we're back rolling the die. But we want to decide between 5 options, not 6. So we want to simulate a 1-in-5 chance with a fair die. Here's two possibilities, which would you choose?
1. The values 1 through 5 mean what they say, if you get a 6 then roll again (and keep rolling until you don't get a 6).
2. The values 1 through 5 mean what they say, if you get a 6 then count it as 1.
To illustrate the issue with the `SunOS` implementation of `rand()` then imagine instead of 5 options, you have 4 and you replace "if you get a 6" by "if you get a 5 or 6".
Ultimately, it all depends on the behaviour of the inbuilt `rand()` function. I've not been able to find out how iOS implements that (not that I've looked very hard) but I did come across a [very interesting article](http://www.azillionmonkeys.com/qed/random.html) about how bad `rand()` is in general and what can be done to fix it (without going all the way to Mersenne Twisters and the like).
And just to forestall one objection to this, the point is *not* the distinction between random and pseudo-random. It's all about the *distribution* thereof, whether the sample is truly random or not.
[1] Did I say that *everything* was a learning opportunity? I meant it. Look it up.