Make Rand use a provided Mersenne Twister.

This implementation is loosely inspired by the original paper on the
Mersenne Twister, and borrows code from a public-domain implementation of
it, adapting it to fit the style of Telodendria's code, and fixing a few
bugs regarding the size of the data type used.

Neither C nor POSIX provide a good, thread-safe pseudorandom number
generator. The OpenBSD linker started complaining about the use of
rand_r(), and no standard alternative presented itself as worthy of
consideration, so I finally decided it was time to roll my own PRNG.
This commit is contained in:
Jordan Bancino 2023-04-25 21:28:55 +00:00
parent 098eed44a0
commit d933d12e1b
5 changed files with 198 additions and 48 deletions

View file

@ -23,12 +23,80 @@
*/
#include <Rand.h>
#include <Int.h>
#include <Util.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define RAND_STATE_VECTOR_LENGTH 624
#define RAND_STATE_VECTOR_M 397
#define RAND_UPPER_MASK 0x80000000
#define RAND_LOWER_MASK 0x7FFFFFFF
#define RAND_TEMPER_B 0x9D2C5680
#define RAND_TEMPER_C 0xEFC60000
typedef struct RandState
{
UInt32 mt[RAND_STATE_VECTOR_LENGTH];
int index;
} RandState;
static void
RandSeed(RandState *state, UInt32 seed)
{
state->mt[0] = seed & 0xFFFFFFFF;
for (state->index = 1; state->index < RAND_STATE_VECTOR_LENGTH; state->index++)
{
state->mt[state->index] = (6069 * state->mt[state->index - 1]) & 0xFFFFFFFF;
}
}
static UInt32
RandGenerate(RandState *state)
{
static const UInt32 mag[2] = { 0x0, 0x9908B0DF };
UInt32 result;
if (state->index >= RAND_STATE_VECTOR_LENGTH || state->index < 0)
{
int kk;
if (state->index >= RAND_STATE_VECTOR_LENGTH + 1 || state->index < 0)
{
RandSeed(state, 4357);
}
for (kk = 0; kk < RAND_STATE_VECTOR_LENGTH - RAND_STATE_VECTOR_M; kk++)
{
result = (state->mt[kk] & RAND_UPPER_MASK) | (state->mt[kk + 1] & RAND_LOWER_MASK);
state->mt[kk] = state->mt[kk + RAND_STATE_VECTOR_M] ^ (result >> 1) ^ mag[result & 0x1];
}
for (; kk < RAND_STATE_VECTOR_LENGTH - 1; kk++)
{
result = (state->mt[kk] & RAND_UPPER_MASK) | (state->mt[kk + 1] & RAND_LOWER_MASK);
state->mt[kk] = state->mt[kk + (RAND_STATE_VECTOR_M - RAND_STATE_VECTOR_LENGTH)] ^ (result >> 1) ^ mag[result & 0x1];
}
result = (state->mt[RAND_STATE_VECTOR_LENGTH - 1] & RAND_UPPER_MASK) | (state->mt[0] & RAND_LOWER_MASK);
state->mt[RAND_STATE_VECTOR_LENGTH - 1] = state->mt[RAND_STATE_VECTOR_M - 1] ^ (result >> 1) ^ mag[result & 0x1];
state->index = 0;
}
result = state->mt[state->index++];
result ^= (result >> 11);
result ^= (result << 7) & RAND_TEMPER_B;
result ^= (result << 15) & RAND_TEMPER_C;
result ^= (result >> 18);
return result;
}
/* Generate random numbers using rejection sampling. The basic idea is
* to "reroll" if a number happens to be outside the range. However
* this could be extremely inefficient.
@ -44,20 +112,23 @@
void
RandIntN(int *buf, size_t size, unsigned int max)
{
static pthread_mutex_t seedLock = PTHREAD_MUTEX_INITIALIZER;
static unsigned int seed = 0;
int tmp;
static pthread_mutex_t stateLock = PTHREAD_MUTEX_INITIALIZER;
static UInt32 seed = 0;
static RandState state;
/* Limit the range to banish all previously biased results */
const int allowed = RAND_MAX - RAND_MAX % max;
int tmp;
size_t i;
pthread_mutex_lock(&seedLock);
pthread_mutex_lock(&stateLock);
if (!seed)
{
/* Generate a seed from the system time, PID, and TID */
seed = UtilServerTs() ^ getpid() ^ (unsigned long) pthread_self();
RandSeed(&state, seed);
}
/* Generate {size} random numbers. */
@ -66,13 +137,13 @@ RandIntN(int *buf, size_t size, unsigned int max)
/* Most of the time, this will take about 1 loop */
do
{
tmp = rand_r(&seed);
} while (tmp >= allowed);
/* Since a generated number here is never in the biased range,
* we can now safely use modulo. */
tmp = RandGenerate(&state);
} while (tmp > allowed);
buf[i] = tmp % max;
}
pthread_mutex_unlock(&seedLock);
pthread_mutex_unlock(&stateLock);
}
/* Generate just 1 random number */

View file

@ -23,41 +23,18 @@
*/
#include <Sha2.h>
#include <Memory.h>
#include <Int.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
/*
* POSIX says that LONG_BIT and WORD_BIT are defined, but some notable
* non-conforming systems and compilers don't don't define it, or only
* define it in circumstances I'm unwilling to comply with (such as
* defining _GNU_SOURCE.
*
* So, unfortunately, although LONG_BIT and WORD_BIT are the most
* elegant solutions, we're forced to do this so we can check LONG_MAX
* and INT_MAX.
*/
#define BIT64_MAX 9223372036854775807
#define BIT32_MAX 2147483647
#define BIT16_MAX 32767
#if (defined(LONG_BIT) && LONG_BIT == 32) || (defined(LONG_MAX) && LONG_MAX == BIT32_MAX)
typedef unsigned long uint32_t;
#elif (defined(WORD_BIT) && WORD_BIT == 32) || (defined(INT_MAX) && INT_MAX == BIT32_MAX)
typedef unsigned int uint32_t;
#else
#error Unable to find suitable integer type for uint32_t
#endif
#define GET_UINT32(x) \
(((uint32_t)(x)[0] << 24) | \
((uint32_t)(x)[1] << 16) | \
((uint32_t)(x)[2] << 8) | \
((uint32_t)(x)[3]))
(((UInt32)(x)[0] << 24) | \
((UInt32)(x)[1] << 16) | \
((UInt32)(x)[2] << 8) | \
((UInt32)(x)[3]))
#define PUT_UINT32(dst, x) { \
(dst)[0] = (x) >> 24; \
@ -79,8 +56,8 @@ typedef unsigned int uint32_t;
#define WW(i) (w[i] = w[i - 16] + S0(w[i - 15]) + w[i - 7] + S1(w[i - 2]))
#define ROUND(a, b, c, d, e, f, g, h, k, w) { \
uint32_t tmp0 = h + T0(e) + CH(e, f, g) + k + w; \
uint32_t tmp1 = T1(a) + MAJ(a, b, c); \
UInt32 tmp0 = h + T0(e) + CH(e, f, g) + k + w; \
UInt32 tmp1 = T1(a) + MAJ(a, b, c); \
h = tmp0 + tmp1; \
d += tmp0; \
}
@ -88,7 +65,7 @@ typedef unsigned int uint32_t;
typedef struct Sha256Context
{
size_t length;
uint32_t state[8];
UInt32 state[8];
size_t bufLen;
unsigned char buffer[64];
} Sha256Context;
@ -96,7 +73,7 @@ typedef struct Sha256Context
static void
Sha256Chunk(Sha256Context * context, unsigned char chunk[64])
{
const uint32_t rk[64] = {
const UInt32 rk[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
@ -110,8 +87,8 @@ Sha256Chunk(Sha256Context * context, unsigned char chunk[64])
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
uint32_t w[64];
uint32_t a, b, c, d, e, f, g, h;
UInt32 w[64];
UInt32 a, b, c, d, e, f, g, h;
int i;
@ -202,10 +179,10 @@ Sha256(char *str)
char *outStr;
unsigned char fill[64];
uint32_t fillLen;
UInt32 fillLen;
unsigned char buf[8];
uint32_t hiLen;
uint32_t loLen;
UInt32 hiLen;
UInt32 loLen;
if (!str)
{
@ -237,8 +214,8 @@ Sha256(char *str)
fill[0] = 0x80;
fillLen = (context.bufLen < 56) ? 56 - context.bufLen : 120 - context.bufLen;
hiLen = (uint32_t) (context.length >> 29);
loLen = (uint32_t) (context.length << 3);
hiLen = (UInt32) (context.length >> 29);
loLen = (UInt32) (context.length << 3);
PUT_UINT32(&buf[0], hiLen);
PUT_UINT32(&buf[4], loLen);

73
src/include/Int.h Normal file
View file

@ -0,0 +1,73 @@
#ifndef TELODENDRIA_INT_H
#define TELODENDRIA_INT_H
#include <limits.h>
#define BIT64_MAX 18446744073709551615
#define BIT32_MAX 4294967295
#define BIT16_MAX 65535
#define BIT8_MAX 255
#ifndef UCHAR_MAX
#error Size of char data type is unknown. Define UCHAR_MAX.
#endif
#ifndef USHRT_MAX
#error Size of short data type is unknown. Define USHRT_MAX.
#endif
#ifndef UINT_MAX
#error Size of int data type is unknown. Define UINT_MAX.
#endif
#ifndef ULONG_MAX
#error Size of long data type is unknown. Define ULONG_MAX.
#endif
#if UCHAR_MAX == BIT8_MAX
typedef signed char Int8;
typedef unsigned char UInt8;
#else
#error Unable to determine suitable data type for 8-bit integers.
#endif
#if UINT_MAX == BIT16_MAX
typedef signed int Int16;
typedef unsigned int UInt16;
#elif USHRT_MAX == BIT16_MAX
typedef signed short Int16;
typedef unsigned short UInt16;
#elif UCHAR_MAX == BIT16_MAX
typedef signed char Int16;
typedef unsigned char UInt16;
#else
#error Unable to determine suitable data type for 16-bit integers.
#endif
#if ULONG_MAX == BIT32_MAX
typedef signed long Int32;
typedef unsigned long UInt32;
#elif UINT_MAX == BIT32_MAX
typedef signed int Int32;
typedef unsigned int UInt32;
#elif USHRT_MAX == BIT32_MAX
typedef signed short Int32;
typedef unsigned short UInt32;
#elif UCHAR_MAX == BIT32_MAX
typedef signed char Int32;
typedef unsigned char UInt32;
#else
#error Unable to determine suitable data type for 32-bit integers.
#endif
/* The ANSI C standard only guarantees a data size of up to 32 bits. */
#endif

View file

@ -154,6 +154,10 @@ recipe_build() {
fi
fi
done
if ! intcheck; then
exit 1
fi
}
recipe_run() {

25
tools/src/intcheck.c Normal file
View file

@ -0,0 +1,25 @@
#include <Int.h>
#include <stdio.h>
#define ASSERT_SIZE(type, size) \
if ((sizeof(type) * 8) != size) \
{ \
fputs(#type " is not " #size " bits.\n", stderr); \
return 1; \
}
int
main(void)
{
ASSERT_SIZE(Int8, 8);
ASSERT_SIZE(UInt8, 8);
ASSERT_SIZE(Int16, 16);
ASSERT_SIZE(UInt16, 16);
ASSERT_SIZE(Int32, 32);
ASSERT_SIZE(UInt32, 32);
return 0;
}