math.random always giving same results even when math.randomseed has unique seed

I’ve made a simple function which selects 32 random numbers. On the sim it works fine, but on iOS and Android it always returns the same 32 numbers in the same order, even when a time is passed in to math.randomseed().

I’ve simplified my function, and tested this on a device to confirm there was nothing else affecting the random numbers:

--get time in seconds local now = os.time() --and milliseconds since app was opened local millis = system.getTimer() --concatenate to make a longer unique number local newSeed = tostring(now)..tostring(millis) --convert tonumber just to make sure randomseed accepts it --this seems to round to an int which is fine as number should still be unique newSeed = tonumber(newSeed) --set randomseed math.randomseed(tonumber(newSeed)) --select 32 random numbers from 1 to 100 local maxRandom = 100 for i = 1, 32 do local ranNum = math.random(1, maxRandom) print(ranNum) end

On iOS I get the same result every time I restart the app:

25 2 39 42 7 100 49 77 4 4 94 89 60 48 84 19 74 12 70 36 64 91 30 27 38 55 1 100 2 85 86 47

Android gets different results to iOS, but again will always generate the same pattern each time the app is restarted.

I’m using Enterprise build 2886. Is there a known issue with math.randomseed, or have I made a mistake somewhere that I’m overlooking?

Did you try not converting the seed to a string?  I believe randomseed is expecting a number, not a string.

Looking at your code again, I see you are convering newSeed to a string, and then converting back to a number.  I would just do as a test:

math.randomseed( os.time() )

I already convert it back to a number before passing it to randomseed:

--convert tonumber just to make sure randomseed accepts it --this seems to round to an int which is fine as number should still be unique newSeed = tonumber(newSeed) 

I’ve printed out the type after this point to confirm that it is “number”.

In actual fact I’m excessively converting it to a number, because not only am I converting it before hand, but I’ve only just noticed that I’m also converting it to number in the randomseed call itself:

--set randomseed math.randomseed(tonumber(newSeed))

I just ran your code in the simulator and I get the same results as you every time.  When I change the seed to:

math.randomseed(os.time())

… the results are always different.

Using your code and printing newSeed, I get this:

14678963002060 14678963132059

I would assume that randomseed is truncating newSeed and using the first 8 digits, which are the same, therefore the seed is the same every run.

Ah ok, that would sort of make sense if it’s doing that, however os.time() returns a 10 digit number.  If randomseed can accept os.time() I would expect it to use the first 10 digits of my number, rather than the first 8. If it was using the first 10, then it would be a slightly different number each time (1467896300 and 1467896313).

Can someone from Corona confirm what the randomseed function does in this case, as the docs don’t mention it at all?

lua’s implementation only recognizes 2^31 seeds, so:

  1. clever tricks to produce bigger numbers are unnecessary in the first place

  2. bigger numbers will overflow silently, actually resulting in WORSE seeding

fwiw… if you truly need LOTS of seeds (say shuffling deck of cards, where 52! > 2^31) you might try the Mersenne Twister, it supports up to a ridiculous (2^32)^624 seeds, each with a ridiculous period of 2^19937.  here is a pure lua implementation you could easily try out (use case: when “quality” is more important than “performance”) - it also has the benefit of passing the original author’s validation test (there are other lua implementation of MT out there too, but not all will pass validation – cuz the only thing worse than a PRNG with known flaws is a PRNG with unknown flaws! :D)  hth

I’ve decided to just use os.time for the seed, perform half of the checks and then use os.time() to see again and do the other half.  

That should be good enough in the event that 2 people start their app for the first time at exactly the same second.  

In all honesty it doesn’t matter too much even if 2 users get the same numbers, I was just trying to make things as random as I could without impacting performance. Thanks for the help though everyone.

First, we are using Lua’s math library. I don’t believe we have made any changes to it. If I understand it correctly, we are just passing the value to the c library srand() ) function as a 32 bit unsigned integer. As Dave pointed out overflows result in bad seeds.

While I don’t know how much variance system.getTimer() is going to provide (the time from app start to your randomseed() call) will pretty much be the same on processors of the same speed) and the closer to the start of your app you put the randomseed() call the smaller that value and variance will be…) what you can do is use the bit plugin and shift the value of os.time() say four bits to the left and OR in the bottom 4 bits of system.getTimer(), as long as you’re getting a 32 bit unsigned int back from the bit plugin. Or replace the top four bits with the bottom 4 bits of system.getTimer().

Lua numbers are double floats, but it can represent whole numbers up to 53 bits (2^53) (please correct me if I’m wrong on this) which is way bigger than an unsigned 32 bit int (4,294,967,295). While that’s a 10 digit number, anything larger overflows. That is 4,294,967,296 is a 10 digit number, but it won’t fit in an unsigned 32 bit int.

Rob

Warning: I am answering this quickly and didn’t give a thorough read through, so if I’m repeating someone or off the target, my apologies.

When you call math.random() rapidly, you are likely to get a number of repeats, especially if you have a limited range to choose from:

print(math.random(1,32)) print(math.random(1,32)) print(math.random(1,32)) print(math.random(1,32)) print(math.random(1,32)) ... print(math.random(1,32))

You can solve this by using a shuffle bag instead.  

I’ll let you look up what a shuffle bag is if you want more details, but …

think of it like a bag, that you fill with numbers, etc. which ensures you get a random selection and don’t repeat till you’ve pulled all the items out of the ‘bag’.

Here is an example and a module that should help:

http://github.com/roaminggamer/RG_FreeStuff/raw/master/AskEd/2016/07/shufflebag.zip

Run the example and click the red/green/cyan ‘buttons’ to select cards from the sample bags (see code).

To Ed’s repeating issue, its a core problem with using the C library’s rand() function. It’s notorious for generating repeating numbers when the range is small. Coin flips and 1-4’s are particularly problematic. Most Unix implementations now offer a random()/srandom() pair that eliminates the low range repeating. But random()/srandom() are not part of the standard C library, so Lua can’t depend on it’s availability.

There are plenty of alternate generators out there. In addition to Ed’s option, there is a community plugin: MWC Random Number Generator (https://store.coronalabs.com/plugin/mwc-random-number-generator) available as a plugin. And of course if you have network connectivity you could go to random.com and use network.request() to generate random numbers that are truly random. You could fetch one number from them to seed math.randomseed()!

Rob

Alan, the number is too big and it exceeds a 32-bit integer’s limit which is what math.seed() accepts as an argument.  That’s the problem.  And this is not really a 64-bit versus 32-bit environment problem either.
 
So, what’s happening here is that you’re concatenating two 32-bit numbers (os.time() and system.getTimer()) to a single 64-bit number in string form.  When you call Lua’s tonumber() function, you’re turning that 64-bit integer string into a double precision float (Lua 5.1 does not support integers).  A 64-bit number has 20 digits, but a double precision float has a precision of 15 digits (worst case), meaning that you’ll lose the 5 least significant digits in the number (these are the right-most digits) if all 20 digits are used.  When you feed that double precision number to math.seed(), it gets converted to a 32-bit integer in the C language in both a 32-bit and 64-bit environment.  A 32-bit integer won’t accept a value greater than 2,147,483,647 (ie: ((2^32)/2) - 1).  Odds are extremely high that your double precision floating point value is larger than this limit.  The C language standard states that casting a float to an integer that’s beyond its limits are undefined (see link below, section 6.3.1.4, bullet 2, last sentence).
   http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf
 
And for your quick reference, here is a copy-and-paste of what is relevant in the above linked document.

If the value being converted is outside the range of values that can be represented, the behavior is undefined.

This means that the behavior will likely be different between different C compilers and environments.  It may overflow (this might be happening on Mac).  Or it may be set to the max int value (this may be happening on Android).  In any case, the point is you can’t depend on this behavior.  It’s undefined.  Meaning that you should avoid it.

Bottom Line:

Don’t pass a number to math.seed() with a value greater than a 32-bit signed integer.

How’s that for an explanation?  :slight_smile:

Thank you Joshua. As soon I actually thought about it, of course the number couldn’t be infinitely long!  

Just one of those days…

Did you try not converting the seed to a string?  I believe randomseed is expecting a number, not a string.

Looking at your code again, I see you are convering newSeed to a string, and then converting back to a number.  I would just do as a test:

math.randomseed( os.time() )

I already convert it back to a number before passing it to randomseed:

--convert tonumber just to make sure randomseed accepts it --this seems to round to an int which is fine as number should still be unique newSeed = tonumber(newSeed) 

I’ve printed out the type after this point to confirm that it is “number”.

In actual fact I’m excessively converting it to a number, because not only am I converting it before hand, but I’ve only just noticed that I’m also converting it to number in the randomseed call itself:

--set randomseed math.randomseed(tonumber(newSeed))

I just ran your code in the simulator and I get the same results as you every time.  When I change the seed to:

math.randomseed(os.time())

… the results are always different.

Using your code and printing newSeed, I get this:

14678963002060 14678963132059

I would assume that randomseed is truncating newSeed and using the first 8 digits, which are the same, therefore the seed is the same every run.

Ah ok, that would sort of make sense if it’s doing that, however os.time() returns a 10 digit number.  If randomseed can accept os.time() I would expect it to use the first 10 digits of my number, rather than the first 8. If it was using the first 10, then it would be a slightly different number each time (1467896300 and 1467896313).

Can someone from Corona confirm what the randomseed function does in this case, as the docs don’t mention it at all?

lua’s implementation only recognizes 2^31 seeds, so:

  1. clever tricks to produce bigger numbers are unnecessary in the first place

  2. bigger numbers will overflow silently, actually resulting in WORSE seeding