mirror of
				https://github.com/KartKrewDev/RingRacers.git
				synced 2025-10-30 08:01:28 +00:00 
			
		
		
		
	Try some things to reduce rng biases
- Don't clamp all RNG calls to [0, FRACUNIT-1]. Only does this for P_RandomFixed now. - Use rejection sampling for any clamped RNG calls, to remove modulo bias. - Because of this, P_RandomRange ranges >= FRACUNIT are no longer undefined behavior. - Added P_Random/M_Random to grab RNG output directly. - Shuffle M_Random's RNG as well, since OS rand can be [0,INT32_MAX] instead of [0,UINT32_MAX].
This commit is contained in:
		
							parent
							
								
									df968e1b1a
								
							
						
					
					
						commit
						492c068cdd
					
				
					 4 changed files with 110 additions and 45 deletions
				
			
		| 
						 | 
				
			
			@ -402,10 +402,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(PR_UNDEFINED, a));
 | 
			
		||||
	demo_writerng = 2;
 | 
			
		||||
	return 1;
 | 
			
		||||
| 
						 | 
				
			
			@ -415,15 +412,13 @@ static int lib_pRandomRange(lua_State *L)
 | 
			
		|||
{
 | 
			
		||||
	INT32 a = (INT32)luaL_checkinteger(L, 1);
 | 
			
		||||
	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(PR_UNDEFINED, a, b));
 | 
			
		||||
	demo_writerng = 2;
 | 
			
		||||
	return 1;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										126
									
								
								src/m_random.c
									
										
									
									
									
								
							
							
						
						
									
										126
									
								
								src/m_random.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -25,20 +25,56 @@
 | 
			
		|||
// 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.
 | 
			
		||||
  *
 | 
			
		||||
  * \return A random fixed point number from [0,1).
 | 
			
		||||
  * \return A random fixed point number from [0,1].
 | 
			
		||||
  */
 | 
			
		||||
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 +84,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 +94,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 +109,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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -92,23 +128,56 @@ typedef struct
 | 
			
		|||
 | 
			
		||||
static rng_t rng; // The entire PRNG state
 | 
			
		||||
 | 
			
		||||
/** 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__(pr_class_t pr_class)
 | 
			
		||||
ATTRINLINE static UINT32 FUNCINLINE __internal_prng__(pr_class_t pr_class)
 | 
			
		||||
{
 | 
			
		||||
	rng.seed[pr_class] ^= rng.seed[pr_class] >> 13;
 | 
			
		||||
	rng.seed[pr_class] ^= rng.seed[pr_class] >> 11;
 | 
			
		||||
	rng.seed[pr_class] ^= rng.seed[pr_class] << 21;
 | 
			
		||||
	return ( (rng.seed[pr_class] * 36548569) >> 4) & (FRACUNIT-1);
 | 
			
		||||
	return (rng.seed[pr_class] * 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__(pr_class_t pr_class, UINT32 bound)
 | 
			
		||||
{
 | 
			
		||||
	// Do rejection sampling to remove the modulo bias.
 | 
			
		||||
	UINT32 threshold = -bound % bound;
 | 
			
		||||
	for (;;)
 | 
			
		||||
	{
 | 
			
		||||
		UINT32 r = __internal_prng__(pr_class);
 | 
			
		||||
		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(pr_class_t pr_class)
 | 
			
		||||
{
 | 
			
		||||
#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__(pr_class);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Provides a random fixed point number. Distribution is uniform.
 | 
			
		||||
  *
 | 
			
		||||
  * \return A random fixed point number from [0,1].
 | 
			
		||||
  */
 | 
			
		||||
#ifndef DEBUGRANDOM
 | 
			
		||||
fixed_t P_RandomFixed(pr_class_t pr_class)
 | 
			
		||||
| 
						 | 
				
			
			@ -118,14 +187,14 @@ fixed_t P_RandomFixedD(const char *rfile, INT32 rline, pr_class_t pr_class)
 | 
			
		|||
{
 | 
			
		||||
	CONS_Printf("P_RandomFixed(%u) at: %sp %d\n", pr_class, rfile, rline);
 | 
			
		||||
#endif
 | 
			
		||||
	return __internal_prng__(pr_class);
 | 
			
		||||
	return (fixed_t)(__internal_prng_bound__(pr_class, 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
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +205,7 @@ UINT8 P_RandomByteD(const char *rfile, INT32 rline, pr_class_t pr_class)
 | 
			
		|||
{
 | 
			
		||||
	CONS_Printf("P_RandomByte(%u) at: %sp %d\n", pr_class, rfile, rline);
 | 
			
		||||
#endif
 | 
			
		||||
	return (UINT8)((__internal_prng__(pr_class) & 0xFF00) >> 8);
 | 
			
		||||
	return (UINT8)(__internal_prng_bound__(pr_class, UINT8_MAX));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Provides a random integer for picking random elements from an array.
 | 
			
		||||
| 
						 | 
				
			
			@ -144,23 +213,22 @@ UINT8 P_RandomByteD(const char *rfile, INT32 rline, pr_class_t pr_class)
 | 
			
		|||
  * 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(pr_class_t pr_class, INT32 a)
 | 
			
		||||
UINT32 P_RandomKey(pr_class_t pr_class, UINT32 a)
 | 
			
		||||
{
 | 
			
		||||
#else
 | 
			
		||||
INT32 P_RandomKeyD(const char *rfile, INT32 rline, pr_class_t pr_class, INT32 a)
 | 
			
		||||
UINT32 P_RandomKeyD(const char *rfile, INT32 rline, pr_class_t pr_class, UINT32 a)
 | 
			
		||||
{
 | 
			
		||||
	CONS_Printf("P_RandomKey(%u) at: %sp %d\n", pr_class, rfile, rline);
 | 
			
		||||
#endif
 | 
			
		||||
	return (INT32)(((INT64)__internal_prng__(pr_class) * a) >> FRACBITS);
 | 
			
		||||
	return __internal_prng_bound__(pr_class, a);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Provides a random integer in a given range.
 | 
			
		||||
  * Distribution is uniform.
 | 
			
		||||
  * NOTE: Maximum range is 65536.
 | 
			
		||||
  *
 | 
			
		||||
  * \param a Lower bound.
 | 
			
		||||
  * \param b Upper bound.
 | 
			
		||||
| 
						 | 
				
			
			@ -175,7 +243,7 @@ INT32 P_RandomRangeD(const char *rfile, INT32 rline, pr_class_t pr_class, INT32
 | 
			
		|||
{
 | 
			
		||||
	CONS_Printf("P_RandomRange(%u) at: %sp %d\n", pr_class, rfile, rline);
 | 
			
		||||
#endif
 | 
			
		||||
	return (INT32)(((INT64)__internal_prng__(pr_class) * (b - a + 1)) >> FRACBITS) + a;
 | 
			
		||||
	return (INT32)(__internal_prng_bound__(pr_class, (b - a) + 1)) + a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -187,13 +255,13 @@ INT32 P_RandomRangeD(const char *rfile, INT32 rline, pr_class_t pr_class, INT32
 | 
			
		|||
/** 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(pr_class_t pr_class)
 | 
			
		||||
UINT32 P_RandomPeek(pr_class_t pr_class)
 | 
			
		||||
{
 | 
			
		||||
	UINT32 r = rng.seed[pr_class];
 | 
			
		||||
	fixed_t ret = __internal_prng__(pr_class);
 | 
			
		||||
	UINT32 ret = __internal_prng__(pr_class);
 | 
			
		||||
	rng.seed[pr_class] = r;
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -69,37 +69,41 @@ typedef enum
 | 
			
		|||
// 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(c)            P_RandomD(__FILE__, __LINE__, c)
 | 
			
		||||
#define P_RandomFixed(c)       P_RandomFixedD(__FILE__, __LINE__, c)
 | 
			
		||||
#define P_RandomByte(c)        P_RandomByteD(__FILE__, __LINE__, c)
 | 
			
		||||
#define P_RandomKey(c, d)      P_RandomKeyD(__FILE__, __LINE__, c, d)
 | 
			
		||||
#define P_RandomRange(c, d, e) P_RandomRangeD(__FILE__, __LINE__, c, d, e)
 | 
			
		||||
UINT32  P_RandomD(const char *rfile, INT32 rline, pr_class_t pr_class);
 | 
			
		||||
fixed_t P_RandomFixedD(const char *rfile, INT32 rline, pr_class_t pr_class);
 | 
			
		||||
UINT8   P_RandomByteD(const char *rfile, INT32 rline, pr_class_t pr_class);
 | 
			
		||||
INT32   P_RandomKeyD(const char *rfile, INT32 rline, pr_class_t pr_class, INT32 a);
 | 
			
		||||
UINT32  P_RandomKeyD(const char *rfile, INT32 rline, pr_class_t pr_class, UINT32 a);
 | 
			
		||||
INT32   P_RandomRangeD(const char *rfile, INT32 rline, pr_class_t pr_class, INT32 a, INT32 b);
 | 
			
		||||
#else
 | 
			
		||||
UINT32  P_Random(pr_class_t pr_class);
 | 
			
		||||
fixed_t P_RandomFixed(pr_class_t pr_class);
 | 
			
		||||
UINT8   P_RandomByte(pr_class_t pr_class);
 | 
			
		||||
INT32   P_RandomKey(pr_class_t pr_class, INT32 a);
 | 
			
		||||
UINT32  P_RandomKey(pr_class_t pr_class, UINT32 a);
 | 
			
		||||
INT32   P_RandomRange(pr_class_t pr_class, 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(pr) ((INT32)P_RandomByte(pr) - 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(pr) ((INT32)P_RandomByte(pr) + 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(pr, p) (P_RandomFixed(pr) < p) // and FRACUNIT (100%), returns true p% of the time
 | 
			
		||||
 | 
			
		||||
// Debugging
 | 
			
		||||
fixed_t P_RandomPeek(pr_class_t pr_class);
 | 
			
		||||
UINT32 P_RandomPeek(pr_class_t pr_class);
 | 
			
		||||
 | 
			
		||||
// Working with the seed for PRNG
 | 
			
		||||
#ifdef DEBUGRANDOM
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -411,12 +411,10 @@ static void ST_drawDebugInfo(void)
 | 
			
		|||
		// Figure out some other way to display all of the RNG classes.
 | 
			
		||||
 | 
			
		||||
		fixed_t peekres = P_RandomPeek(PR_UNDEFINED);
 | 
			
		||||
		peekres *= 10000;     // Change from fixed point
 | 
			
		||||
		peekres >>= FRACBITS; // to displayable decimal
 | 
			
		||||
 | 
			
		||||
		V_DrawRightAlignedString(320, height - 16, V_MONOSPACE, va("Init: %08x", P_GetInitSeed(PR_UNDEFINED)));
 | 
			
		||||
		V_DrawRightAlignedString(320, height - 8,  V_MONOSPACE, va("Seed: %08x", P_GetRandSeed(PR_UNDEFINED)));
 | 
			
		||||
		V_DrawRightAlignedString(320, height,      V_MONOSPACE, va("==  :    .%04d", peekres));
 | 
			
		||||
		V_DrawRightAlignedString(320, height,      V_MONOSPACE, va("==  : %08x", peekres));
 | 
			
		||||
 | 
			
		||||
		height -= 32;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue