Lately, I have seen a brief discussion on the discord server about which is the best random number generator available on roblox. One person advised someone to use
math.randomseed(tick()) and then do
math.random(). However, another user said that
Random.new() should be used instead of
math.randomseed(). So, after I did a quick search on the wiki, I couldn't really discern the difference between math.randomseed and Random.new().
So after researching Random.new, Random:NextInteger, and Random:NextNumber for a bit I am still confused about why, if both methods provide psuedo-random numbers, why have two ways to perform the same task. For example, the following two bits of code should generate psuedo-random integers between 0 and 200
local random = Random.new(tick()) while true do print(random:NextInteger(0,200)) wait(2) end
math.randomseed(tick()) while true do print(math.random(0,200)) wait(2) end
These two pieces of code should, essentially do the same thing, that being generating a psuedo-random number between 0 and 200. But that still raises the question of why, why have two different ways to accomplish the same thing?
So, if anyone has any idea of what the rationale behind having two ways of essentially doing the same thing, and if there is a different, which of the two methods is better, or "Random-er" than the other, that would be appreciated.
The TLDR is that Random.new() is better, and there is no reason to use math.random() ever, as it has no benefits and a significant drawback if you're trying to do something with a repeatable, deterministic random sequence.
Internally, math.random() used to use good old C rand(). Which was not actually good, in any way, and the generator was shared by everything running in the Roblox client or Studio: your code, Roblox's Lua code, Studio C++ code... so seeding the thing was completely and utterly pointless.
Now, math.random() just internally calls the PRNG implemented by the new Random class. This was done to retroactively improve the statistical randomness of math.random() for the benefit of games that are already using it. This does not mean you should use it for new work. This re-implementation of math.random() using Random does not address the other major shortcoming which is the single shared generator. If you use math.random(), all your code that calls it is still pulling from the same stream, so it's very difficult to get repeatable results even if you seed it with the same number. Any variation in the order with which scripts or coroutines call math.random() can make the behavior change from run to run. It only takes one extra or one out of order call to get a completely divergent behavior from that point on.
The other thing that Random.new() has over math.random() is that math.random() now has conditional logic overhead to redirect to Random's functions. You can call math.random() with no arguments, one argument, or two, and the behavior is different. On the C++ side, it's counting the arguments and doing if-then-else branching to decide which function of Random to call. This overhead is very tiny, almost negligible, but it's there, and when you have two options to use that get you the same values, why would you knowingly call the less efficient one?