Library: investigate usage of rand() and consider replacement

As it turns out, by far margin we mostly use rand() to generate
test values within a limited interval, using the ''modulo trick''
and thus excluding the upper bound.

Looking into the implementation of the distributions in the
libStdC++ shows that ''constructing'' a distribution on-the-fly
is cheap and boils down to checking and then storing the bounds;
so basically there is no need to keep ''cached distribution objects''
around, because for all practical purposes these behave like free functions

What is required occasionally is a non-zero HashValue, and sometimes
an interval of floating-point number or a normal distribution seem useful.

Providing these as free-standing convenience functions,
implicitly accessing the default PRNG.
This commit is contained in:
Fischlurch 2024-11-12 21:10:14 +01:00
parent ce2116fccd
commit 2883a8619f
3 changed files with 209 additions and 32 deletions

View file

@ -38,6 +38,7 @@
#include "lib/integral.hpp"
#include "lib/hash-value.h"
#include "lib/nocopy.hpp"
#include <random>
@ -79,6 +80,11 @@ namespace lib {
uint64_t u64() { return uniformU_(generator_); }
double uni() { return uniformD_(generator_); }
friend int rani(uint);
friend double ranRange(double,double);
friend double ranNormal(double,double);
friend HashVal ranHash();
/** inject controlled randomisation */
void reseed (SeedNucleus&);
@ -119,7 +125,42 @@ namespace lib {
/* ===== convenience accessors ===== */
inline int rani() { return defaultGen.i32(); } ///< using default params: min ≡ 0, max ≡ numeric_limits
/** @return a random integer ∈ [0 ... bound[ */
inline int
rani (uint bound =1u<<31)
{
if (!bound) ++bound;
--bound;
uint upper{std::numeric_limits<int>::max()};
upper = bound < upper? bound : upper;
std::uniform_int_distribution<int> dist(0, upper);
return dist (defaultGen.generator_);
}
/** @return a random double ∈ [start ... bound[ */
inline double
ranRange (double start, double bound)
{
std::uniform_real_distribution<double> dist{start,bound};
return dist (defaultGen.generator_);
}
inline double
ranNormal (double mean =0.0, double stdev =1.0)
{
std::normal_distribution<double> dist{mean,stdev};
return dist (defaultGen.generator_);
}
/** @return a random *non-zero* HashVal */
inline lib::HashVal
ranHash()
{
static std::uniform_int_distribution<lib::HashVal> dist{lib::HashVal(1)};
return dist (defaultGen.generator_);
}
/// @deprecated
inline uint64_t ranu() { return defaultGen.u64(); }
inline double runi() { return defaultGen.uni(); }

View file

@ -28,7 +28,10 @@
#include "lib/test/run.hpp"
#include "lib/random.hpp"
#include "lib/util.hpp"
#include "lib/test/diagnostic-output.hpp"
using util::isLimited;
namespace lib {
namespace test {
@ -45,6 +48,7 @@ namespace test {
run (Arg)
{
simpleUsage();
verify_distributionVariants();
verify_reproducibleSequence();
}
@ -62,10 +66,44 @@ namespace test {
int r2 = rani();
CHECK (0 <= r2 and r2 < RAND_MAX);
CHECK (r1 != r2);
CHECK (r1 != r2); // may fail with very low probability
}
/** @test properties of predefined distributions provided for convenience
* - the upper bound for `rani(bound)` is exclusive
* - uniform distributions are sufficiently uniform
* - spread of normal distribution is within expected scale
*/
void
verify_distributionVariants()
{
double avg{0.0};
const uint N = 1e6;
for (uint i=0; i < N; ++i)
avg += 1.0/N * rani (1000);
auto expect = 500;
auto error = fabs(avg/expect - 1);
CHECK (error < 0.005);
for (uint i=0; i < N; ++i)
CHECK (isLimited(0, rani(5), 4));
for (uint i=0; i < N; ++i)
CHECK (0 != ranHash());
auto sqr = [](double v){ return v*v; };
double spread{0.0};
for (uint i=0; i < N; ++i)
spread += sqr (ranNormal() - 0.5);
spread = sqrt (spread/N);
CHECK (spread < 1.12);
}
/** @test demonstrate that random number sequences can be reproduced
* - use a rigged SeedNucleus, always returning a fixed sees
* - build two distinct random sequence generators, yet seeded

View file

@ -15226,9 +15226,7 @@
<icon BUILTIN="idea"/>
<node CREATED="1518575730894" ID="ID_24825251" MODIFIED="1518575750656">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
nur <i>einen Satz</i>&#160;Klauseln
@ -16653,9 +16651,7 @@
<node CREATED="1488672665626" ID="ID_590886664" MODIFIED="1518487921076" TEXT="Grundlagen f&#xfc;r Command-handling"/>
<node CREATED="1487313769425" ID="ID_728232011" MODIFIED="1518487921076">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
Grundlagen f&#252;r <b>InteractionControl</b>
@ -17123,9 +17119,7 @@
</node>
<node CREATED="1504308024282" ID="ID_1154461573" MODIFIED="1518487921077">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
wie <i>bestimmt?</i>
@ -17738,9 +17732,7 @@
<icon BUILTIN="hourglass"/>
<node CREATED="1625070136274" ID="ID_104505251" MODIFIED="1625070160572">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
das hei&#223;t: weitgehend <i>custom drawing</i>
@ -47254,9 +47246,7 @@
</node>
<node CREATED="1455669004941" ID="ID_853385575" MODIFIED="1575133324401">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
dies setzt volle Implementierung
@ -47669,9 +47659,7 @@
<node CREATED="1455928216420" ID="ID_662720483" MODIFIED="1464117064570" TEXT="further src elements available"/>
<node CREATED="1470527053136" ID="ID_1383280265" MODIFIED="1470527075712">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
of questionable use
@ -47818,9 +47806,7 @@
<node CREATED="1461946965569" ID="ID_803034273" MODIFIED="1461946980872" TEXT="locate designated src element"/>
<node CREATED="1455928275316" ID="ID_1937317223" MODIFIED="1512926191930">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
<i>move</i>&#160;into target
@ -57260,7 +57246,8 @@
</html></richcontent>
</node>
</node>
<node CREATED="1731118832589" ID="ID_732098473" MODIFIED="1731119409643" TEXT="brauche eine reseed()-Operation um einen Seed wiederherzustellen">
<node COLOR="#435e98" CREATED="1731118832589" ID="ID_732098473" MODIFIED="1731424338038" TEXT="brauche eine reseed()-Operation um einen Seed wiederherzustellen">
<icon BUILTIN="yes"/>
<node CREATED="1731118937690" ID="ID_876325939" MODIFIED="1731118961825" TEXT="der C++ - Standard bietet de-facto diese Operation auf allen enthaltenen Engines"/>
<node CREATED="1731118962605" ID="ID_1749223447" MODIFIED="1731118982278" TEXT="notfalls k&#xf6;nnte man aber auch ein in-place Destroy / re-Construct machen"/>
<node COLOR="#338800" CREATED="1731119637225" ID="ID_955411605" MODIFIED="1731205571101" TEXT="reseed()-Operation auf unser RandomSequencer-Interface &#xfc;bernehmen">
@ -57291,13 +57278,14 @@
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1731119762690" ID="ID_1637478235" MODIFIED="1731205613988" TEXT="Entwurf: Zufalls-Generator in lib::test::Test">
<icon BUILTIN="idea"/>
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1731119762690" ID="ID_1637478235" MODIFIED="1731424344693" TEXT="Zufalls-Generator in lib::test::Test">
<icon BUILTIN="forward"/>
<node CREATED="1731119858173" ID="ID_1447736454" MODIFIED="1731119870919" TEXT="verwendet einen eingebetteten SeedNucleus"/>
<node COLOR="#5b280f" CREATED="1731120003113" ID="ID_1177767927" MODIFIED="1731176382320" TEXT="jeder Aufruf erzeugt ein neues RandomSequencer-Objekt">
<icon BUILTIN="button_cancel"/>
</node>
<node CREATED="1731119876579" ID="ID_1129580767" MODIFIED="1731119987245" TEXT="sorgt daf&#xfc;r, da&#xdf; der gezogene Seed dokumentiert wird">
<node COLOR="#338800" CREATED="1731119876579" ID="ID_1129580767" MODIFIED="1731421441201" TEXT="sorgt daf&#xfc;r, da&#xdf; der gezogene Seed dokumentiert wird">
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1731120117194" ID="ID_1685729849" MODIFIED="1731120697456" TEXT="Problem: wo?">
<icon BUILTIN="help"/>
<node CREATED="1731120134129" ID="ID_633936698" MODIFIED="1731120148682" TEXT="STDOUT und STDERR k&#xf6;nnen mit Test-Definitionen interferieren"/>
@ -57314,14 +57302,15 @@
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1731120702508" ID="ID_1798637658" MODIFIED="1731120716944" TEXT="es wird also bei jedem Ziehen eines Seed ein NOTICE-Log geschrieben">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1731120702508" ID="ID_1798637658" MODIFIED="1731421435241" TEXT="es wird also bei jedem Ziehen eines Seed ein NOTICE-Log geschrieben">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1731120731502" ID="ID_1120851239" MODIFIED="1731120769966" TEXT="neue Kommandozeilen-Parameter: --seed &lt;zahl&gt;">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1731120731502" ID="ID_1120851239" MODIFIED="1731421432983" TEXT="neue Kommandozeilen-Parameter: --seed &lt;zahl&gt;">
<icon BUILTIN="button_ok"/>
</node>
<node CREATED="1731176387690" ID="ID_94726091" MODIFIED="1731176394537" TEXT="&#xdc;berlegungen zum API">
<node COLOR="#435e98" CREATED="1731176387690" ID="ID_94726091" MODIFIED="1731421458019" TEXT="&#xdc;berlegungen zum API">
<icon BUILTIN="yes"/>
<node CREATED="1731176395547" ID="ID_37424726" MODIFIED="1731176788372" TEXT="in den meisten F&#xe4;llen will man grade nicht ein separates Generator-Objekt">
<richcontent TYPE="NOTE"><html>
<head/>
@ -57427,6 +57416,115 @@
</node>
</node>
</node>
<node CREATED="1731424663557" ID="ID_1548490877" MODIFIED="1731424673834" TEXT="std::rand() ersetzen">
<icon BUILTIN="yes"/>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1731424676911" ID="ID_432733421" MODIFIED="1731424687348" TEXT="was wird ben&#xf6;tigt?">
<font NAME="SansSerif" SIZE="12"/>
<icon BUILTIN="help"/>
<node CREATED="1731424690509" ID="ID_1222355432" MODIFIED="1731424715317" TEXT="test::randStr()"/>
<node CREATED="1731424716055" ID="ID_1693891146" MODIFIED="1731431008272" TEXT="test::randTime()"/>
<node CREATED="1731424784952" ID="ID_1485935359" MODIFIED="1731424795595" TEXT="woot! luid.c verwendet rand()"/>
<node CREATED="1731424872438" ID="ID_512461088" MODIFIED="1731424875680" TEXT="Bereiche">
<node CREATED="1731424876772" ID="ID_1889708776" MODIFIED="1731424890390" TEXT="[0 .. 1000 [">
<node CREATED="1731425385582" ID="ID_1130852617" MODIFIED="1731425389674" TEXT="auch 10"/>
<node CREATED="1731425390222" ID="ID_385805413" MODIFIED="1731425394394" TEXT="auch 10k"/>
<node CREATED="1731425395102" ID="ID_1806459182" MODIFIED="1731425397729" TEXT="auch 100k"/>
</node>
<node CREATED="1731425040295" ID="ID_373464808" MODIFIED="1731425053800" TEXT="[0 .. &lt;range&gt; ["/>
<node CREATED="1731424922446" ID="ID_583117295" MODIFIED="1731425038355" TEXT="[1 .. &lt;range&gt; ]"/>
<node CREATED="1731425698987" ID="ID_509650285" MODIFIED="1731425705174" TEXT="beliebiger int-range">
<node CREATED="1731426808023" ID="ID_69355296" MODIFIED="1731426817194" TEXT="nur wenige F&#xe4;lle"/>
</node>
<node CREATED="1731425981949" ID="ID_658238858" MODIFIED="1731430895929" TEXT="reproducible HashVal &#x2260; 0"/>
</node>
<node CREATED="1731426958427" ID="ID_1269151803" MODIFIED="1731427004802" TEXT="Feststellungen">
<icon BUILTIN="yes"/>
<node CREATED="1731425150703" ID="ID_1821489280" MODIFIED="1731425164672" TEXT="Bereichsgrenze ist fast immer compile-time">
<icon BUILTIN="idea"/>
</node>
<node CREATED="1731426971529" ID="ID_1219491527" MODIFIED="1731426983764" TEXT="nur ganz wenige negative Zufallszahlen"/>
<node CREATED="1731427756457" ID="ID_1288279705" MODIFIED="1731427887939" TEXT="uniform_int_distribution speichert nur die Bereichsgrenzen (sonst stateless)">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
Die klasse enth&#228;lt diverse Checks, die aber (weitgehend?) constexpr sind. Der eigentliche operative Aufruf ist eine reine Funktion, die den Generator hereingereicht bekommt. Diese Funktion gibt es in zwei Auspr&#228;gungen, einmal f&#252;r einen einzigen Wert, und einmal (wohl optimiert) f&#252;r Ausgabe in einen Iterator-Range. Letztere hei&#223;t __generate und ist wohl nur f&#252;r die STL intern gedacht
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1731428047688" ID="ID_35787864" MODIFIED="1731428068259" TEXT="per default ist der Bereich [0 ... numeric_limits::max]"/>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1731428069970" ID="ID_282614053" MODIFIED="1731428077830" TEXT="Achtung: closed-Interval">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1731428080621" ID="ID_168543776" MODIFIED="1731428095640" TEXT="im Gegensatz dazu liefert der Modulo-Trick ein offenes Interval">
<icon BUILTIN="clanbomber"/>
</node>
<node CREATED="1731428117560" ID="ID_1814881667" MODIFIED="1731428178152" TEXT="nahezu die gesamte codebasis verwendet den Modulo-Trick &#x27f9; Grenze exclusiv">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
an einigen wenigen Stellen wird eigens daf&#252;r gesorgt, da&#223; die grenz inclusiv ist
</p>
</body>
</html>
</richcontent>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1731430747958" ID="ID_909943227" MODIFIED="1731430752006" TEXT="Zielvorgabe">
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
<icon BUILTIN="yes"/>
<node CREATED="1731430755615" ID="ID_1824063380" MODIFIED="1731430926339" TEXT="drop-in-Replacement f&#xfc;r Modulo">
<node CREATED="1731433686599" ID="ID_97051869" MODIFIED="1731433762484">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
<font face="Monospaced" color="#1312bf"><b>rani</b></font><font face="Monospaced" color="#50507b">(</font><font face="Monospaced" color="#69507b">max</font><font face="Monospaced" color="#50507b">)</font>
</p>
</body>
</html>
</richcontent>
</node>
<node CREATED="1731433623983" ID="ID_1594131908" MODIFIED="1731433628226" TEXT="es ist ein int"/>
<node CREATED="1731433629215" ID="ID_1630650197" MODIFIED="1731433643792" TEXT="Obergrenze exclusiv"/>
<node CREATED="1731433617520" ID="ID_423604881" MODIFIED="1731433655617" TEXT="principle of least surprise">
<icon BUILTIN="ksmiletris"/>
</node>
</node>
<node CREATED="1731431617197" ID="ID_960451563" MODIFIED="1731433675731" TEXT="der i32 soll den gesamten int32-Bereich abdecken (auch negativ!)"/>
<node COLOR="#5b280f" CREATED="1731434364363" ID="ID_202795829" MODIFIED="1731434407298" TEXT="ranu und runi werden entfernt (zu sonderbar....)">
<icon BUILTIN="stop-sign"/>
</node>
<node CREATED="1731434034464" ID="ID_676955985" MODIFIED="1731434037208" TEXT="convenience">
<node CREATED="1731434572056" ID="ID_1921841184" MODIFIED="1731434593712" TEXT="ranHash() &#x2260; 0"/>
<node CREATED="1731434043756" ID="ID_1935715464" MODIFIED="1731434328260" TEXT="ranRange(u,o) : double &#x2208; [u ... o[ "/>
<node CREATED="1731434161079" ID="ID_1433291868" MODIFIED="1731434333379" TEXT="ranNormal(&#x3bc; &#x2254;0.0, &#x3c3; &#x2254; 1.0) : Gau&#xdf;"/>
<node CREATED="1731434257216" ID="ID_1951992549" MODIFIED="1731434270484" TEXT="root-Import f&#xfc;r Tests">
<node CREATED="1731434271416" ID="ID_224185748" MODIFIED="1731434275587" TEXT="defaultGen"/>
<node CREATED="1731434276112" ID="ID_1394389531" MODIFIED="1731434278979" TEXT="entropyGen"/>
<node CREATED="1731434279632" ID="ID_1693749548" MODIFIED="1731434342786" TEXT="rani(), ranRange, ranNormal"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1731434498664" ID="ID_33884738" MODIFIED="1731434636107" TEXT="Umstellung">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1731434503085" ID="ID_779294881" MODIFIED="1731441344340" TEXT="API-Umstellungen">
<icon BUILTIN="pencil"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1731434516153" ID="ID_1079673401" MODIFIED="1731434633528" TEXT="bestehende Verwendungen anpassen">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1731434530901" ID="ID_81651492" MODIFIED="1731434633527" TEXT="rand() abl&#xf6;sen in Core">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1731434548714" ID="ID_1401652131" MODIFIED="1731434633527" TEXT="rand() % MAX abl&#xf6;sen in Tests">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
</node>
</node>
</node>