Random seeds
Using a seed for a random()
function is literally that: ‘seeding’ the formula that generates a new (pseudo) random number.
The formula p5js uses is:
const m = 4294967296;
const a = 1664525;
const c = 1013904223;
Formula: (a * seed + c) % m
So when using randomSeed(0)
, a 0
is entered into the formula: (a * 0 + c) % m = 1013904223
. Because we only want normalized results (a result between 0 and 1), we divide the result by m
.
The value for m
needs to be very large (as it is the max period) and for its relationships to a
and c
. I won’t be diving into that part 😎.
The next time random()
is called (without first manually setting a randomSeed) it will use the result from the previous calculation, in our case 1013904223
. So the next random number we get will be: (a * 1013904223 + c) % m = 1196435762
.
You can feed randomSeed()
with integers, floats or even negative numbers. Because of the unsigned right shift (value >>> 0
) in the randomSeed function, they will be converted to an integer between 0 and 4294967295.
- a positive integer will be the same after converting
- a negative number will be counted from the max integer backwards. So
-1
will be4294967295
and-2
will be4294967294
etc. - a float will be converted to an integer by removing all decimals.
That large number 4294967296
for m
= 232 or 2 to the power of 32. Chrome and other browsers actually use 2128 for their Math.random()
period, using the xorshift128+ algorithm. That is a lot more, but P5js is still using the LCG algorithm because it’s easy to understand, easily implemented and fast.
Every consecutive call of random()
will use the resulting seed from its previous call. randomSeed(0)
will produce a new seed of 1013904223
. Therefor, calling random()
2 times when using randomSeed(0)
will produce the same result as calling random()
1 time using randomSeed(1013904223)
.
Consecutive seeds
Using different seeds in the draw function of p5js or processing, obviously generates different numbers. But having seeds that are close to each other, will produce numbers that start close as well. Because using randomSeed(frameCount)
is a popular way of ‘changing’ seeds, here’s the output for the first 5 frames:
frameCount random call 1 random call 2
1 1015568748 1586005467
2 1017233273 1975575172
3 1018897798 2365144877
4 1020562323 2754714582
5 1022226848 3144284287
For all random call 1’s, the difference between every new seed is exactly 1664525 every single time (as long as you increment the seed with 1
, like in our example).
That number 1664525
is exactly the const a in the random()
formula. So if you put in seeds incremented by 1, because of the multiplication with a
, the difference between every result will also be a
.
As you can see in the output table, this only applies for the first random() call! A second random call will not use a 2
for it’s seed, but the result from seed 1
, be it 1015568748
.
It doesn’t matter if you use 1,2,3 or 10001, 10002, 10003. If the seeds are incremented by one, the resulting seed is incremented with 1664525
. The random()
function always returns a number between 0 and 1 (because of the modulo % m
and division by m
). So if every new seed is 1664525
apart, the returned number from the random()
function will be 1664525/4294967296 ≈ 0.00038
apart (const a / const m).
That’s a very tiny increase for every first random call as visualized here:
Watch what happens if we add an extra random()
call before our let s = random(200);
. The visual drastically changes (as only the first random()
call behaves like previously described).
Patterns using randomSeed
Check out the next example with a grid of 9 circles that randomly move at a grid spot with the random(-15,15)
function. The first random call in this example is for the x-position of the upper left circle (it’s literally the first time random()
is called after setting the seed). As we’ve seen, the first random call after consecutive seeds, isn’t thát random. That’s why it seems it’s only moving vertically, but actually it moves a (very) tiny bit to the right each frame. The framerate is set to 10, to slow things down:
Keep in mind: if you use any max number in your random call like random(-15,15)
, you’ve essentially expanded the resulting range 30 times now, from 0-1 to 0-30 (subtracting 15). The formula multiplies the result with this expanded number (30 in our case), so the difference for each first random result will then be 30 fold (≈ 0.0116
).
So in this example, the upper left circle will move 11.6 pixels to the right every 1000 frames. At a default frame rate of 60 fps, that would take 16 seconds.
But wait, there are more patterns to be seen in this grid, right? Exactly! You can run this sketch to get a nice overview of different amount of preceding random()
calls per consecutive seed. If we set amountOfPrecedingRandomCalls = 3
and amountOfResults = 10
, we will get the next table, showing 10 consecutive seeds for 0, 1 ánd 2 extra random calls before actually calculating the random()
we want. Be aware these are the normalized (0-1) results, so without the multiplication result of using -15,15
.
seed preceding random calls result difference
1 0 0.23645552527159452 0
2 0 0.23684307769872248 0.00038755242712795734
3 0 0.23723063012585044 0.00038755242712795734
4 0 0.2376181825529784 0.00038755242712795734
5 0 0.23800573498010635 0.00038755242712795734
6 0 0.2383932874072343 0.00038755242712795734
7 0 0.23878083983436227 0.00038755242712795734
8 0 0.23916839226149023 0.00038755242712795734
9 0 0.23955594468861818 0.00038755242712795734
10 0 0.23994349711574614 0.00038755242712795734
1 1 0.3692706737201661 0
2 1 0.4599744388833642 0.09070376516319811
3 1 0.5506782040465623 0.09070376516319811
4 1 0.6413819692097604 0.09070376516319811
5 1 0.7320857343729585 0.09070376516319811
6 1 0.8227894995361567 0.09070376516319811
7 1 0.9134932646993548 0.09070376516319811
8 1 0.004197029862552881 -0.9092962348368019
9 1 0.094900795025751 0.09070376516319811
10 1 0.1856045601889491 0.09070376516319811
1 2 0.5042420323006809 0
2 2 0.18895030464045703 -0.31529172766022384
3 2 0.8736585769802332 0.6847082723397762
4 2 0.5583668493200094 -0.31529172766022384
5 2 0.2430751216597855 -0.31529172766022384
6 2 0.9277833939995617 0.6847082723397762
7 2 0.6124916663393378 -0.31529172766022384
8 2 0.297199938679114 -0.31529172766022384
9 2 0.9819082110188901 0.6847082723397762
10 2 0.6666164833586663 -0.31529172766022384
So what can we predict with this output? Well, knowing we are using a random call for x position and then a random call for y position, we can see that the first random call has differences of ≈ 0.00038, while the second random call has differences of ≈ 0.09. So the y-coördinate of circle 1 moves approx. 234 times faster than the x-coördinate, resulting in what seams a vertical movement only.
If you would use amountOfPrecedingRandomCalls = 4
then you could predict the movement of circle number 2. The x-coördinate would jump left and right, and the y-coördinate moves ‘slowly’ (at a rate of 1.11 frames per second (0.037016365909948945 * 30
).
Do play with these 2 variables to find interesting patterns when using consecutive seeds! 😎
Incrementing seeds by 100
So we’ve seen what results we get if we increment the seed by 1 every time. What results do we get if we increment with another amount, lets say 100?
Well, every seed is 1664525*100
apart, so the returned number from the random()
function will be (1664525*100)/4294967296 ≈ 0.038
apart ( (a * 100) / m
). So, a 100 fold version of the increment of 1. But still, a small amount: it takes 100 frames (about 2 seconds) to move 3.8 pixels.
If you’d use randomSeed(frameCount*100)
and then random(-15,15)
, then every frame the upper left circle moves 100 * 0.0116 = 1.16
pixels. So each second it would ‘travel’ 60*1.16 ≈ 70
pixels (if it wasn’t limited through a max random value).