mirror of
https://github.com/minetest/minetest_docs.git
synced 2024-07-04 15:15:15 +02:00
Document random options (#55)
This commit is contained in:
parent
9a0a50506c
commit
cf4192e8ac
193
doc/random.adoc
Normal file
193
doc/random.adoc
Normal file
@ -0,0 +1,193 @@
|
||||
= Random
|
||||
include::../include/config.adoc[]
|
||||
include::../include/types.adoc[]
|
||||
:description: Comparison of available random number generators ("sources")
|
||||
:keywords: random, source, secure
|
||||
|
||||
Minetest provides four different random sources, each with its own merits.
|
||||
Modders must choose wisely unless they can let the engine do the random for them
|
||||
(e.g. randomly picking a sound or a texture for particles).
|
||||
|
||||
== Lua builtins
|
||||
|
||||
Not restricted by mod security, these functions are available to both SSMs and CSMs:
|
||||
|
||||
=== https://www.lua.org/manual/5.1/manual.html#pdf-math.randomseed[`math.randomseed`]
|
||||
|
||||
Seed the random. Minetest already does this for you using the system time.
|
||||
|
||||
[IMPORTANT]
|
||||
.Do not seed the random
|
||||
====
|
||||
Do not seed the random to turn it into a deterministic random source
|
||||
as other mods may expect it to be "non-deterministic".
|
||||
|
||||
Conversely, do not rely on the random to have any particular seed either; other mods & the engine
|
||||
may have seeded it (using the system time) to be "non-deterministic".
|
||||
|
||||
The problem with `math.randomseed` is that there is only one global, hidden seed.
|
||||
There is no way to get the current seed out; mods can't restore their random sequence.
|
||||
Mods seeding the random thus necessarily conflict - unless they all expect
|
||||
it to be "non-deterministic" and only seed it accordingly
|
||||
(ideally not at all, since the engine-side seeding should suffice).
|
||||
|
||||
If you need `math.random` for its performance but want it to be deterministic,
|
||||
you may *reseed* the random after you're done with it to ensure that it is "non-deterministic" again.
|
||||
|
||||
[source,lua]
|
||||
---
|
||||
-- Use the random to generate a seed for the random; preferable over using system time,
|
||||
-- as the latter may be deterministic
|
||||
local seed = ... -- some fixed seed
|
||||
local reseed = math.random(2^31-1)
|
||||
math.randomseed(seed) -- temporarily make the random "deterministic"
|
||||
-- ... do something using `math.random` ...
|
||||
math.randomseed(reseed)
|
||||
---
|
||||
====
|
||||
|
||||
=== https://www.lua.org/manual/5.1/manual.html#pdf-math.random[`math.random`]
|
||||
|
||||
Get a random number. Very versatile; allows getting floats between `0` and `1` or integers in a range.
|
||||
|
||||
NOTE: The random numbers between `0` and `1` do not provide a full 52-bit mantissa
|
||||
full of entropy; they usually have around 32 bits of entropy.
|
||||
|
||||
WARNING: When using this to obtain integers,
|
||||
make sure that both the upper & lower bound
|
||||
as well as their difference are within the C `int` range -
|
||||
otherwise you may get overflows & errors.
|
||||
|
||||
TIP: Use `math.random` as your go-to versatile "non-deterministic" random source.
|
||||
|
||||
== Random Number Generators
|
||||
|
||||
=== `PcgRandom`
|
||||
|
||||
A seedable 32-bit signed integer pseudo-random number generator.
|
||||
|
||||
==== `PcgRandom(seed)`
|
||||
|
||||
Constructs a `PcgRandom` instance with the given seed,
|
||||
which should be an integer within 32-bit bounds.
|
||||
|
||||
==== Methods
|
||||
|
||||
===== `:next([min, max])`
|
||||
|
||||
If `min` and `max` are both omitted,
|
||||
they default to `-2^31` (`-2147483648`)
|
||||
and `2^31 - 1` (`2147483647`) respectively.
|
||||
|
||||
===== `:rand_normal_dist(min, max, [num_trials])`
|
||||
|
||||
WARNING: No successful use of this function is documented. Consider implementing your own normal distribution instead.
|
||||
|
||||
`min` and `max` are required; they need to be integers.
|
||||
|
||||
Rough approximation of a normal distribution with a mean of `(max - min) / 2`
|
||||
and a variance of `(((max - min + 1) ^ 2) - 1) / (12 * num_trials)`.
|
||||
|
||||
`num_trials` defaults to `6`. The more trials, the better the approximation.
|
||||
|
||||
The return value is a float.
|
||||
|
||||
=== `PseudoRandom`
|
||||
|
||||
A seedable 16-bit unsigned integer pseudo-random number generator.
|
||||
|
||||
"Uses a well-known LCG algorithm introduced by K&R."
|
||||
|
||||
Perhaps the lowest-quality random generator of all.
|
||||
|
||||
==== `PseudoRandom(seed)`
|
||||
|
||||
Constructor: Takes a `seed` and returns a `PseudoRandom` object.
|
||||
|
||||
===== `:next([min, max])`
|
||||
|
||||
If `min` and `max` are both omitted, they default to `0` and `2^16-1` (`32767`) respectively.
|
||||
|
||||
WARNING: Requires `((max - min) == 32767) or ((max-min) <= 6553))` for a proper distribution.
|
||||
|
||||
=== `SecureRandom`
|
||||
|
||||
System-provided cryptographically secure random:
|
||||
An attacker should not be able to predict the generated sequence of random numbers.
|
||||
Use this when generating cryptographic keys or tokens.
|
||||
|
||||
Not necessarily available in all builds and on all platforms.
|
||||
|
||||
==== `SecureRandom()`
|
||||
|
||||
Constructor: Returns a SecureRandom object or `nil` if no secure random source is available.
|
||||
|
||||
TIP: Use `assert(SecureRandom(), "no secure random available")` to error if no secure random source is available.
|
||||
|
||||
==== Methods
|
||||
|
||||
===== `:next_bytes([count])`
|
||||
|
||||
Only argument is `count`, an optional integer defaulting to `1`
|
||||
and limited to `2048` specifying how many bytes are to be returned.
|
||||
Returned as a Lua bytestring of length `count`
|
||||
|
||||
== Benchmarking
|
||||
|
||||
[source,lua]
|
||||
---
|
||||
collectgarbage"stop" -- we don't want GC heuristics to interfere
|
||||
|
||||
local n = 1e8 -- number of runs
|
||||
local function bench(name, constructor, invokation)
|
||||
local func = assert(loadstring(([[
|
||||
local r = %s
|
||||
for _ = 1, %d do %s end
|
||||
]]):format(constructor, n, invokation)))
|
||||
local t = minetest.get_us_time()
|
||||
func()
|
||||
print(name, (minetest.get_us_time() - t) / n, "µs/call")
|
||||
end
|
||||
|
||||
bench("Lua", "nil", "math.random()")
|
||||
bench("PCG", "PcgRandom(42)", "r:next()")
|
||||
bench("K&R", "PseudoRandom(42)", "r:next()")
|
||||
bench("Secure", "assert(SecureRandom())", "r:next_bytes()")
|
||||
---
|
||||
|
||||
Example output:
|
||||
|
||||
[source]
|
||||
---
|
||||
Lua 0.00385002 µs/call
|
||||
PCG 0.05579729 µs/call
|
||||
K&R 0.05859349 µs/call
|
||||
Secure 0.11211887 µs/call
|
||||
---
|
||||
|
||||
== Comparison
|
||||
|
||||
|===
|
||||
| Random Source | Performance | Bytes of entropy | Seedability | Versatility | Distribution | Security | Portability
|
||||
| `math.random` | very good (1x) | up to 4 | global seed; seeded by default | very good | no guarantees, but usually decent enough | not cryptographically secure | varies by platform
|
||||
| `PcgRandom` | okay (~14x) | up to 4 | per-instance seed | very good | good, decent guarantees | not cryptographically secure | always the same
|
||||
| `PseudoRandom` | okay (~15x) | 1 to 2 | per-instance seed | outright sucks | okay-ish | not cryptographically secure | always the same
|
||||
| `SecureRandom` | still okay (30x) | 1 to 2048 | not seedable | cryptographically secure | varies by platform; may be missing
|
||||
|===
|
||||
|
||||
Note: The performance comparison is a bit of an apples-to-oranges comparison for multiple reasons:
|
||||
|
||||
. The different generators make different guarantees regarding the randomness;
|
||||
. The different generators generate different numbers of bytes per invocation - the default was arbitrarily chosen;
|
||||
Secure random in particular is able to generate plenty of bytes (up to 2048) with one call.
|
||||
|
||||
The benchmark still suffices to draw basic conclusions though,
|
||||
especially for the common case where a random source is simply used once
|
||||
(e.g. `math.random() < 0.5`).
|
||||
|
||||
=== Conclusion
|
||||
|
||||
. *Never use `PseudoRandom`. It is strictly inferior to `PcgRandom`.*
|
||||
. Use `math.random` if you want a fast "non-deterministic" random.
|
||||
. Use `PcgRandom` if you need per-instance seedability and can take the performance hit.
|
||||
. Use `SecureRandom` if and only if you need a cryptographically secure random.
|
Loading…
Reference in New Issue
Block a user