Try some things to reduce rng biases

This with no PRNG classes: 492c068cdd
This commit is contained in:
NepDisk 2025-08-24 11:35:33 -04:00
parent e052cca8b7
commit 2ccaefd9b0
4 changed files with 112 additions and 42 deletions

View file

@ -435,10 +435,7 @@ static int lib_pRandomByte(lua_State *L)
static int lib_pRandomKey(lua_State *L)
{
INT32 a = (INT32)luaL_checkinteger(L, 1);
NOHUD
if (a > 65536)
LUA_UsageWarning(L, "P_RandomKey: range > 65536 is undefined behavior");
lua_pushinteger(L, P_RandomKey(a));
demo_writerng = 2;
return 1;
@ -450,13 +447,12 @@ static int lib_pRandomRange(lua_State *L)
INT32 b = (INT32)luaL_checkinteger(L, 2);
NOHUD
if (b < a) {
if (b < a)
{
INT32 c = a;
a = b;
b = c;
}
if ((b-a+1) > 65536)
LUA_UsageWarning(L, "P_RandomRange: range > 65536 is undefined behavior");
lua_pushinteger(L, P_RandomRange(a, b));
demo_writerng = 2;
return 1;

View file

@ -25,6 +25,49 @@
// RNG functions (not synched)
// ---------------------------
ATTRINLINE static UINT32 FUNCINLINE __external_prng__(void)
{
UINT32 rnd = rand();
#if RAND_MAX < 65535
// Compensate for especially bad randomness.
UINT32 rndv = (rand() & 1) << 15;
rnd ^= rndv;
#endif
// Shuffle like we do for our own PRNG, since RAND_MAX
// tends to be [0, INT32_MAX] instead of [0, UINT32_MAX].
rnd ^= rnd >> 13;
rnd ^= rnd >> 11;
rnd ^= rnd << 21;
return (rnd * 36548569);
}
ATTRINLINE static UINT32 FUNCINLINE __external_prng_bound__(UINT32 bound)
{
// Do rejection sampling to remove the modulo bias.
UINT32 threshold = -bound % bound;
for (;;)
{
UINT32 r = __external_prng__();
if (r >= threshold)
{
return r % bound;
}
}
}
/** Provides a random 32-bit number. Distribution is uniform.
* As with all M_Random functions, not synched in netgames.
*
* \return A random 32-bit number.
*/
UINT32 M_Random(void)
{
return __external_prng__();
}
/** Provides a random fixed point number. Distribution is uniform.
* As with all M_Random functions, not synched in netgames.
*
@ -32,13 +75,7 @@
*/
fixed_t M_RandomFixed(void)
{
#if RAND_MAX < 65535
// Compensate for insufficient randomness.
fixed_t rndv = (rand()&1)<<15;
return rand()^rndv;
#else
return (rand() & 0xFFFF);
#endif
return (fixed_t)(__external_prng_bound__(FRACUNIT));
}
/** Provides a random byte. Distribution is uniform.
@ -48,7 +85,7 @@ fixed_t M_RandomFixed(void)
*/
UINT8 M_RandomByte(void)
{
return (rand() & 0xFF);
return (UINT8)(__external_prng_bound__(UINT8_MAX));
}
/** Provides a random integer for picking random elements from an array.
@ -58,9 +95,9 @@ UINT8 M_RandomByte(void)
* \param a Number of items in array.
* \return A random integer from [0,a).
*/
INT32 M_RandomKey(INT32 a)
UINT32 M_RandomKey(UINT32 a)
{
return (INT32)((rand()/((unsigned)RAND_MAX+1.0f))*a);
return __external_prng_bound__(a);
}
/** Provides a random integer in a given range.
@ -73,7 +110,7 @@ INT32 M_RandomKey(INT32 a)
*/
INT32 M_RandomRange(INT32 a, INT32 b)
{
return (INT32)((rand()/((unsigned)RAND_MAX+1.0f))*(b-a+1))+a;
return (INT32)(__external_prng_bound__((b - a) + 1)) + a;
}
@ -88,24 +125,59 @@ static UINT32 randomseed = 0xBADE4404;
// Holds the INITIAL seed value. Used for demos, possibly other debugging.
static UINT32 initialseed = 0xBADE4404;
/** Provides a random fixed point number.
/** Provides a random 32 bit integer.
* This is a variant of an xorshift PRNG; state fits in a 32 bit integer structure.
*
* \return A random fixed point number from [0,1).
* \return A random, uniformly distributed number from [0,UINT32_MAX].
*/
ATTRINLINE static fixed_t FUNCINLINE __internal_prng__(void)
ATTRINLINE static UINT32 FUNCINLINE __internal_prng__(void)
{
randomseed ^= randomseed >> 13;
randomseed ^= randomseed >> 11;
randomseed ^= randomseed << 21;
return ( (randomseed*36548569) >> 4) & (FRACUNIT-1);
return (randomseed*36548569);
}
/** Provides a random number within a specified range.
*
* \return A random, uniformly distributed number from [0,bound].
*/
ATTRINLINE static UINT32 FUNCINLINE __internal_prng_bound__(UINT32 bound)
{
// Do rejection sampling to remove the modulo bias.
UINT32 threshold = -bound % bound;
for (;;)
{
UINT32 r = __internal_prng__();
if (r >= threshold)
{
return r % bound;
}
}
}
/** Provides a random fixed point number. Distribution is uniform.
* Literally a wrapper for the internal PRNG function.
*
* \return A random fixed point number from [0,1).
* \return A random fixed point number from [0,UINT32_MAX].
*/
#ifndef DEBUGRANDOM
UINT32 P_Random(void)
{
#else
UINT32 P_RandomD(const char *rfile, INT32 rline, pr_class_t pr_class)
{
CONS_Printf("P_Random(%u) at: %sp %d\n", pr_class, rfile, rline);
#endif
return __internal_prng__();
}
/** Provides a random fixed point number. Distribution is uniform.
*
* \return A random fixed point number from [0,1].
*/
#ifndef DEBUGRANDOM
fixed_t P_RandomFixed(void)
{
@ -114,14 +186,14 @@ fixed_t P_RandomFixedD(const char *rfile, INT32 rline)
{
CONS_Printf("P_RandomFixed() at: %sp %d\n", rfile, rline);
#endif
return __internal_prng__();
return (fixed_t)(__internal_prng_bound__(FRACUNIT));
}
/** Provides a random byte. Distribution is uniform.
* If you're curious, (&0xFF00) >> 8 gives the same result
* as a fixed point multiplication by 256.
*
* \return Random integer from [0, 255].
* \return Random integer from [0,255].
* \sa __internal_prng__
*/
#ifndef DEBUGRANDOM
@ -132,7 +204,7 @@ UINT8 P_RandomByteD(const char *rfile, INT32 rline)
{
CONS_Printf("P_RandomByte() at: %sp %d\n", rfile, rline);
#endif
return (UINT8)((__internal_prng__()&0xFF00)>>8);
return (UINT8)(__internal_prng_bound__(UINT8_MAX));
}
/** Provides a random integer for picking random elements from an array.
@ -140,18 +212,18 @@ UINT8 P_RandomByteD(const char *rfile, INT32 rline)
* NOTE: Maximum range is 65536.
*
* \param a Number of items in array.
* \return A random integer from [0,a).
* \return A random integer from [0,a].
* \sa __internal_prng__
*/
#ifndef DEBUGRANDOM
INT32 P_RandomKey(INT32 a)
UINT32 P_RandomKey(UINT32 a)
{
#else
INT32 P_RandomKeyD(const char *rfile, INT32 rline, INT32 a)
UINT32 P_RandomKeyD(const char *rfile, INT32 rline, UINT32 a)
{
CONS_Printf("P_RandomKey() at: %sp %d\n", rfile, rline);
#endif
return (INT32)(((INT64)__internal_prng__() * a) >> FRACBITS);
return __internal_prng_bound__(a);
}
/** Provides a random integer in a given range.
@ -171,7 +243,7 @@ INT32 P_RandomRangeD(const char *rfile, INT32 rline, INT32 a, INT32 b)
{
CONS_Printf("P_RandomRange() at: %sp %d\n", rfile, rline);
#endif
return (INT32)(((INT64)__internal_prng__() * (b-a+1)) >> FRACBITS) + a;
return (INT32)(__internal_prng_bound__((b - a) + 1)) + a;
}
@ -183,13 +255,13 @@ INT32 P_RandomRangeD(const char *rfile, INT32 rline, INT32 a, INT32 b)
/** Peeks to see what the next result from the PRNG will be.
* Used for debugging.
*
* \return A 'random' fixed point number from [0,1).
* \return A 'random' number from [0,UINT32_MAX]
* \sa __internal_prng__
*/
fixed_t P_RandomPeek(void)
UINT32 P_RandomPeek(void)
{
UINT32 r = randomseed;
fixed_t ret = __internal_prng__();
UINT32 ret = __internal_prng__();
randomseed = r;
return ret;
}

View file

@ -28,37 +28,41 @@ extern "C" {
// P_Random functions pulls random bytes from a PRNG that is network synced.
// RNG functions
UINT32 M_Random(void);
fixed_t M_RandomFixed(void);
UINT8 M_RandomByte(void);
INT32 M_RandomKey(INT32 a);
UINT32 M_RandomKey(UINT32 a);
INT32 M_RandomRange(INT32 a, INT32 b);
// PRNG functions
#ifdef DEBUGRANDOM
#define P_Random() P_RandomD(__FILE__, __LINE__)
#define P_RandomFixed() P_RandomFixedD(__FILE__, __LINE__)
#define P_RandomByte() P_RandomByteD(__FILE__, __LINE__)
#define P_RandomKey(c) P_RandomKeyD(__FILE__, __LINE__, c)
#define P_RandomRange(c, d) P_RandomRangeD(__FILE__, __LINE__, c, d)
UINT32 P_RandomD(const char *rfile, INT32 rline);
fixed_t P_RandomFixedD(const char *rfile, INT32 rline);
UINT8 P_RandomByteD(const char *rfile, INT32 rline);
INT32 P_RandomKeyD(const char *rfile, INT32 rline, INT32 a);
UINT32 P_RandomKeyD(const char *rfile, INT32 rline, INT32 a);
INT32 P_RandomRangeD(const char *rfile, INT32 rline, INT32 a, INT32 b);
#else
UINT32 P_Random(void);
fixed_t P_RandomFixed(void);
UINT8 P_RandomByte(void);
INT32 P_RandomKey(INT32 a);
UINT32 P_RandomKey(UINT32 a);
INT32 P_RandomRange(INT32 a, INT32 b);
#endif
// Macros for other functions
#define M_SignedRandom() ((INT32)M_RandomByte() - 128) // [-128, 127] signed byte, originally a
#define P_SignedRandom() ((INT32)P_RandomByte() - 128) // function of its own, moved to a macro
#define M_SignedRandom() ((INT32)M_RandomByte() + INT8_MIN) // [-128, 127] signed byte, originally a
#define P_SignedRandom() ((INT32)P_RandomByte() + INT8_MIN) // function of its own, moved to a macro
#define M_RandomChance(p) (M_RandomFixed() < p) // given fixed point probability, p, between 0 (0%)
#define P_RandomChance(p) (P_RandomFixed() < p) // and FRACUNIT (100%), returns true p% of the time
// Debugging
fixed_t P_RandomPeek(void);
UINT32 P_RandomPeek(void);
// Working with the seed for PRNG
#ifdef DEBUGRANDOM

View file

@ -522,12 +522,10 @@ static void ST_drawDebugInfo(void)
if (cht_debug & DBG_RNG) // randomizer testing
{
fixed_t peekres = P_RandomPeek();
peekres *= 10000; // Change from fixed point
peekres >>= FRACBITS; // to displayable decimal
V_DrawRightAlignedString(320, height - 16, V_MONOSPACE, va("Init: %08x", P_GetInitSeed()));
V_DrawRightAlignedString(320, height - 8, V_MONOSPACE, va("Seed: %08x", P_GetRandSeed()));
V_DrawRightAlignedString(320, height, V_MONOSPACE, va("== : .%04d", peekres));
V_DrawRightAlignedString(320, height, V_MONOSPACE, va("== : %08x", peekres));
height -= 32;
}