mirror of
https://github.com/minetest/minetest_docs.git
synced 2024-07-07 08:35:16 +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