This is a partial C# implementation of UnityEngine.Random
with (almost) 1-to-1 parity.
Unity uses Xorshift for psuedorandom number generation. In particular Xorshift128, which uses a state consisting of four unsigned 32-bit integer values. The state is initialized in UnityEngine.Random.InitState
using a signed 32-bit integer seed, which is shuffled around with a technique similar to the way a Mersenne Twister is initialized.
This has been tested as far back as Unity 4.7.0f1, and as recent as Unity 2020.1.17f1.
- Huge thanks to MoatShrimp for figuring out how Unity initializes the Xorshift state parameters in InitState, and floating point generation.
- C# - As below. Values may differ in .NET 5.0, but thankfully Unity doesn't yet support that.
- JavaScript - Check out MoatShrimp's JS implemention (and neat Undermine loot lookup tool) here:
- Lua - I've translated this into Lua for use on MediaWiki wikis with the Scribunto extension installed. Note that this only works if Lua was compiled with
LUA_NUMBER = double
, which should be the case for most wikis. In my case the initial goal was to evaluate random loot lists in Pillars of Eternity, for use on the wiki.- Module:Lootlist (might have to scroll down a bit)
- If you translate this into other languages, or have any improvements/insights, feel free to comment below!
- If you do translate this, be mindful of:
- How your language handles numbers. Most programming languages that only define one type for numbers will use a double-precision floating point value to represent all numbers.
- Integer overflow, and casting behaviours (particularly from uint to int)
- Differences in the
%
operator (modulo vs remainder)
- How Unity initializes
UnityEngine.Random
when a seed is not provided. Probably seeded based on time, perhaps hourly? Need to do further testiong. - Floating point RPG is slightly inaccurate. Unity's implementation is likely done differently, though their exact method is unknown.
The following tests were performed using the C# implementation in Unity, initialized with: UnityEngine.Random.InitState(1234)
and XORShift128.InitSeed(1234)
, generated sequentially (the state isn't reset between runs).
UnityEngine.Random.State.s3
and XORShift128.w
are the last state parameter in Xorshift, the actual number that was generated in Xorshift. This value is a uint
and is used as a basis for all other Random functions.
Uses UnityEngine.Random.Range(min, max)
and XORShift128.NextIntRange(min, max)
0 to int.MaxValue
s3 / w |
Unity | XORShift |
---|---|---|
3463400838 | 1315917191 | 1315917191 |
3496203776 | 1348720129 | 1348720129 |
3452947669 | 1305464022 | 1305464022 |
1278673611 | 1278673611 | 1278673611 |
4169168310 | 2021684663 | 2021684663 |
0 to int.MinValue
(-2,147,483,648)
s3 / w |
Unity | XORShift |
---|---|---|
916287344 | -916287344 | -916287344 |
2240259090 | -92775442 | -92775442 |
1901252403 | -1901252403 | -1901252403 |
2323917162 | -176433514 | -176433514 |
1472147877 | -1472147877 | -1472147877 |
int.MinValue
to int.MaxValue
s3 / w |
Unity | XORShift |
---|---|---|
4020283508 | 1872799860 | 1872799860 |
141347300 | -2006136348 | -2006136348 |
2735243002 | 587759354 | 587759354 |
227819815 | -1919663833 | -1919663833 |
3885870057 | 1738386409 | 1738386409 |
int.MaxValue
to int.MinValue
s3 / w |
Unity | XORShift |
---|---|---|
2312142103 | -164658456 | -164658456 |
1775189369 | 372294278 | 372294278 |
3338523678 | -1191040031 | -1191040031 |
3426086347 | -1278602700 | -1278602700 |
3322349983 | -1174866336 | -1174866336 |
int.MinValue
to int.MinValue
(error is caught and a suitable value is returned before a value is even generated, hence the non-changing state value)
s3 / w |
Unity | XORShift |
---|---|---|
3322349983 | -2147483648 | -2147483648 |
3322349983 | -2147483648 | -2147483648 |
3322349983 | -2147483648 | -2147483648 |
3322349983 | -2147483648 | -2147483648 |
3322349983 | -2147483648 | -2147483648 |
Uses UnityEngine.Random.value
and XORShift128.NextFloat()
s3 / w |
Unity | XORShift |
---|---|---|
3593715923 | 0.4043221 | 0.404322 |
4266042159 | 0.551855 | 0.551855 |
2642301593 | 0.9868958 | 0.9868957 |
1674312536 | 0.593608 | 0.5936079 |
733387434 | 0.426595 | 0.426595 |
Uses UnityEngine.Random.Range(min, max)
and XORShift128.NextFloatRange(min, max)
0.0 to 100.0
s3 / w |
Unity | XORShift |
---|---|---|
3760331560 | 73.35463 | 73.35463 |
2410116911 | 69.16753 | 69.16753 |
3012185272 | 91.95337 | 91.95337 |
745993129 | 7.068896 | 7.068908 |
3699201620 | 2.080286 | 2.080297 |
0.0 to 100000.0
s3 / w |
Unity | XORShift |
---|---|---|
1758944507 | 31747.49 | 31747.5 |
2312712917 | 30313.62 | 30313.62 |
313095164 | 67614.8 | 67614.8 |
609241099 | 37280.14 | 37280.14 |
4138924286 | 60177.63 | 60177.64 |
0.0 to float.MaxValue
(3.402823E+38)
s3 / w |
Unity | XORShift |
---|---|---|
3065852775 | 1.775827E+38 | 1.775827E+38 |
4023550175 | 1.209507E+38 | 1.209507E+38 |
1231330622 | 7.280383E+37 | 7.280387E+37 |
678488548 | 4.010639E+37 | 4.010643E+37 |
1997128070 | 3.143466E+38 | 3.143466E+38 |
0.0 to float.MinValue
(-3.402823E+38)
s3 / w |
Unity | XORShift |
---|---|---|
212231104 | -2.382252E+38 | -2.382252E+38 |
1632010759 | -1.528401E+38 | -1.528402E+38 |
3470161922 | -1.104089E+38 | -1.104089E+38 |
4159346095 | -5.693158E+37 | -5.693162E+37 |
3362607345 | -4.967007E+37 | -4.967012E+37 |
float.MinValue
to float.MaxValue
s3 / w |
Unity | XORShift |
---|---|---|
2641274177 | -2.480102E+38 | -2.480101E+38 |
3758475398 | 3.095331E+38 | 3.095331E+38 |
1138851524 | -1.78091E+38 | -1.780909E+38 |
3785966737 | 1.208649E+38 | 1.208649E+38 |
151368008 | 3.100158E+38 | 3.100158E+38 |
float.MaxValue
to float.MinValue
s3 / w |
Unity | XORShift |
---|---|---|
3347712278 | -2.869245E+38 | -2.869245E+38 |
2413123709 | 1.13493E+38 | 1.13493E+38 |
619907290 | 2.713464E+38 | 2.713463E+38 |
5796605 | 1.299941E+38 | 1.299941E+38 |
2534213977 | -2.709683E+38 | -2.709683E+38 |
float.MinValue
to float.MinValue
s3 / w |
Unity | XORShift |
---|---|---|
2990456181 | -3.402823E+38 | -3.402823E+38 |
238536240 | -3.402823E+38 | -3.402823E+38 |
3443233425 | -3.402823E+38 | -3.402823E+38 |
847449518 | -3.402823E+38 | -3.402823E+38 |
1964100510 | -3.402823E+38 | -3.402823E+38 |
JFYI
NextInt()
andNextIntMax()
are bugged.This line:
return (int)(XORShift() % int.MaxValue);
coerces signedness in such a way that you will never get a negative number. if you cast XORShift() to int, alone, then mod, you can get negative numbers. but as is, NextInt() never produces negative numbers (nor NextIntMax, since it depends on NextInt). NextIntRange does not use NextInt(), and tests only cover NextIntRange so that's probably how this was missed.