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 |
Thank you very much for that information! As i made this using your information, i thought i might share this version of it i made in Rust:
It's missing most of the calls, but since this is all i needed, i didn't spend more time. As Rust does not allow integer overflow, i had to use the
std::num::Wrapping
.