From 7f64ace8f0d38b83c1e616b74994cc0ef043de3a Mon Sep 17 00:00:00 2001 From: Tharo <17233964+Thar0@users.noreply.github.com> Date: Tue, 27 Feb 2024 03:21:25 +0000 Subject: [PATCH] RNG doc (#1892) * RNG doc * Add some missing note qualifiers in comments * code_800FD970 -> rand in Makefile and disasm CSVs --- Makefile | 2 +- spec | 2 +- src/code/code_800FD970.c | 79 ------------- src/code/rand.c | 162 +++++++++++++++++++++++++++ tools/disasm/gc-eu-mq/files_code.csv | 6 +- 5 files changed, 167 insertions(+), 84 deletions(-) delete mode 100644 src/code/code_800FD970.c create mode 100644 src/code/rand.c diff --git a/Makefile b/Makefile index 34e198cb3b..ac9cb6e489 100644 --- a/Makefile +++ b/Makefile @@ -238,7 +238,7 @@ $(BUILD_DIR)/src/boot/stackcheck.o: OPTFLAGS := -O2 $(BUILD_DIR)/src/code/__osMalloc.o: OPTFLAGS := -O2 $(BUILD_DIR)/src/code/code_800FC620.o: OPTFLAGS := -O2 $(BUILD_DIR)/src/code/code_800FCE80.o: OPTFLAGS := -O2 -$(BUILD_DIR)/src/code/code_800FD970.o: OPTFLAGS := -O2 +$(BUILD_DIR)/src/code/rand.o: OPTFLAGS := -O2 $(BUILD_DIR)/src/code/gfxprint.o: OPTFLAGS := -O2 $(BUILD_DIR)/src/code/jpegutils.o: OPTFLAGS := -O2 $(BUILD_DIR)/src/code/jpegdecoder.o: OPTFLAGS := -O2 diff --git a/spec b/spec index df4a354ac3..ad24d44436 100644 --- a/spec +++ b/spec @@ -482,7 +482,7 @@ beginseg include "$(BUILD_DIR)/src/code/code_800FCE80.o" include "$(BUILD_DIR)/src/code/fp.o" include "$(BUILD_DIR)/src/code/system_malloc.o" - include "$(BUILD_DIR)/src/code/code_800FD970.o" + include "$(BUILD_DIR)/src/code/rand.o" include "$(BUILD_DIR)/src/code/__osMalloc.o" #if !OOT_DEBUG include "$(BUILD_DIR)/src/libultra/libc/sprintf.o" diff --git a/src/code/code_800FD970.c b/src/code/code_800FD970.c deleted file mode 100644 index cc9dc5e63c..0000000000 --- a/src/code/code_800FD970.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "global.h" - -// The latest generated random number, used to generate the next number in the sequence. -static u32 sRandInt = 1; - -// Space to store a value to be re-interpreted as a float. -static u32 sRandFloat; - -/** - * Gets the next integer in the sequence of pseudo-random numbers. - */ -u32 Rand_Next(void) { - return sRandInt = (sRandInt * 1664525) + 1013904223; -} - -/** - * Seeds the pseudo-random number generator by providing a starting value. - */ -void Rand_Seed(u32 seed) { - sRandInt = seed; -} - -/** - * Returns a pseudo-random floating-point number between 0.0f and 1.0f, by generating - * the next integer and masking it to an IEEE-754 compliant floating-point number - * between 1.0f and 2.0f, returning the result subtract 1.0f. - */ -f32 Rand_ZeroOne(void) { - sRandInt = (sRandInt * 1664525) + 1013904223; - sRandFloat = ((sRandInt >> 9) | 0x3F800000); - return *((f32*)&sRandFloat) - 1.0f; -} - -/** - * Returns a pseudo-random floating-point number between -0.5f and 0.5f by the same - * manner in which Rand_ZeroOne generates its result. - */ -f32 Rand_Centered(void) { - sRandInt = (sRandInt * 1664525) + 1013904223; - sRandFloat = ((sRandInt >> 9) | 0x3F800000); - return *((f32*)&sRandFloat) - 1.5f; -} - -/** - * Seeds a pseudo-random number at rndNum with a provided seed. - */ -void Rand_Seed_Variable(u32* rndNum, u32 seed) { - *rndNum = seed; -} - -/** - * Generates the next pseudo-random integer from the provided rndNum. - */ -u32 Rand_Next_Variable(u32* rndNum) { - return *rndNum = (*rndNum * 1664525) + 1013904223; -} - -/** - * Generates the next pseudo-random floating-point number between 0.0f and - * 1.0f from the provided rndNum. - */ -f32 Rand_ZeroOne_Variable(u32* rndNum) { - u32 next = (*rndNum * 1664525) + 1013904223; - - sRandFloat = ((*rndNum = next) >> 9) | 0x3F800000; - return *((f32*)&sRandFloat) - 1.0f; -} - -/** - * Generates the next pseudo-random floating-point number between -0.5f and - * 0.5f from the provided rndNum. - */ -f32 Rand_Centered_Variable(u32* rndNum) { - u32 next = (*rndNum * 1664525) + 1013904223; - - *rndNum = next; - sRandFloat = (next >> 9) | 0x3F800000; - return *((f32*)&sRandFloat) - 1.5f; -} diff --git a/src/code/rand.c b/src/code/rand.c new file mode 100644 index 0000000000..4a0c5dc4a3 --- /dev/null +++ b/src/code/rand.c @@ -0,0 +1,162 @@ +/** + * @file rand.c + * + * This file implements the primary random number generator the game relies on. The generator is pseudo-random and + * implemented as a Linear Congruential Generator (LCG). + * + * A LCG computes random numbers sequentially via the relation + * X(n+1) = (a * X(n) + c) mod m + * where m is the modulus, a is the multiplier and c is the increment. + * + * These three parameters (a,c,m) completely specify the LCG and should be chosen such that + * - m > 0 + * - 0 < a < m + * - 0 <= c < m + * + * The period of the LCG (a, c, m) is the smallest period p such that X(n + p) = X(n), past n=p the sequence will repeat + * itself in its outputs. + * A good LCG should have the maximum possible period, which will be equal to m as there are at most m possible values + * for X. This occurs when (Hull, T.E., & Dobell, A.R. (1962). Random Number Generators. Siam Review, 4, 230-254.): + * - m,c are relatively prime, that is the only integer that divides both m and c with no remainder is 1. + * - a - 1 is divisible by all prime factors of m. + * - a - 1 is divisible by 4 if m is divisible by 4. + * + * Ideally m is chosen to be a large power of 2 so that the modulo operation is inexpensive to compute. In this case the + * prime factors of m = 2^k are just k copies of 2. For k > 1 m is divisible by 4, so a - 1 must be divisible by 4. 2^k + * and c can easily be made relatively prime by making c an odd number. + * If we let k=32 to match the size of an integer, the modulo operation is made implicit by the width of the data type + * and becomes free to compute. + * + * The parameter a should be selected such that a-1 is divisible by 4 (and hence divisible by 2) and c should be any odd + * number. The precise values should fare well against the spectral test, a measure of "how random" a particular LCG is. + * A pair (a,c) that satisfies these requirements is (1664525, 1013904223), recommended by "Numerical Recipes in C: The + * Art of Scientific Computing" (p. 284). + * + * Therefore, the LCG with parameters (1664525, 1013904223, 2^32) that is implemented in this file has a maximal period + * of 2^32 and produces high-quality pseudo-random numbers. + * + * @note If sampling the LCG for a n-bit number it is important to use the upper n bits instead of the lower n bits of + * the LCG output. The lower n bits only have a period of 2^n which may significantly worsen the quality of the + * resulting random numbers compared to the quality of the full 32-bit result. + * + * @note Original name: qrand.c + */ +#include "ultra64.h" + +#define RAND_MULTIPLIER 1664525 +#define RAND_INCREMENT 1013904223 + +/** + * The latest generated random number, used to generate the next number in the sequence. + * + * @note Original name: __qrand_idum + */ +static u32 sRandInt = 1; + +/** + * Space to store a value to be re-interpreted as a float. + * + * @note Orignal name: __qrand_itemp + */ +static fu sRandFloat; + +/** + * Gets the next integer in the sequence of pseudo-random numbers. + * + * @note Original name: qrand + */ +u32 Rand_Next(void) { + return sRandInt = sRandInt * RAND_MULTIPLIER + RAND_INCREMENT; +} + +/** + * Seeds the pseudo-random number generator by providing a starting value. + * + * @note Original name: sqrand + */ +void Rand_Seed(u32 seed) { + sRandInt = seed; +} + +/** + * Returns a pseudo-random floating-point number between 0.0f and 1.0f, by generating the next integer and masking it + * to an IEEE-754 compliant floating-point number between 1.0f and 2.0f, returning the result subtract 1.0f. + * + * @note This technique for generating pseudo-random floats is recommended as a particularly fast but potentially + * non-portable generator in "Numerical Recipes in C: The Art of Scientic Computing", pp. 284-5. + * + * @note Original name: fqrand + */ +f32 Rand_ZeroOne(void) { + sRandInt = sRandInt * RAND_MULTIPLIER + RAND_INCREMENT; + // Samples the upper 23 bits to avoid effectively reducing the LCG period. + sRandFloat.i = (sRandInt >> 9) | 0x3F800000; + return sRandFloat.f - 1.0f; +} + +/** + * Returns a pseudo-random floating-point number between -0.5f and 0.5f by the same manner in which Rand_ZeroOne + * generates its result. + * + * @see Rand_ZeroOne + * + * @note Original name: fqrand2 + */ +f32 Rand_Centered(void) { + sRandInt = sRandInt * RAND_MULTIPLIER + RAND_INCREMENT; + sRandFloat.i = (sRandInt >> 9) | 0x3F800000; + return sRandFloat.f - 1.5f; +} + +//! All functions below are unused variants of the above four, that use a provided random number variable instead of the +//! internal `sRandInt` + +/** + * Seeds a pseudo-random number at rndNum with a provided starting value. + * + * @see Rand_Seed + * + * @note Original name: sqrand_r + */ +void Rand_Seed_Variable(u32* rndNum, u32 seed) { + *rndNum = seed; +} + +/** + * Generates the next pseudo-random integer from the provided rndNum. + * + * @see Rand_Next + * + * @note Original name: qrand_r + */ +u32 Rand_Next_Variable(u32* rndNum) { + return *rndNum = (*rndNum) * RAND_MULTIPLIER + RAND_INCREMENT; +} + +/** + * Generates the next pseudo-random floating-point number between 0.0f and 1.0f from the provided rndNum. + * + * @see Rand_ZeroOne + * + * @note Original name: fqrand_r + */ +f32 Rand_ZeroOne_Variable(u32* rndNum) { + u32 next = (*rndNum) * RAND_MULTIPLIER + RAND_INCREMENT; + + sRandFloat.i = ((*rndNum = next) >> 9) | 0x3F800000; + return sRandFloat.f - 1.0f; +} + +/** + * Generates the next pseudo-random floating-point number between -0.5f and 0.5f from the provided rndNum. + * + * @see Rand_ZeroOne, Rand_Centered + * + * @note Original name: fqrand2_r + */ +f32 Rand_Centered_Variable(u32* rndNum) { + u32 next = (*rndNum) * RAND_MULTIPLIER + RAND_INCREMENT; + + sRandFloat.i = ((*rndNum = next) >> 9) | 0x3F800000; + return sRandFloat.f - 1.5f; +} diff --git a/tools/disasm/gc-eu-mq/files_code.csv b/tools/disasm/gc-eu-mq/files_code.csv index 9de88579f9..d22e15fe08 100644 --- a/tools/disasm/gc-eu-mq/files_code.csv +++ b/tools/disasm/gc-eu-mq/files_code.csv @@ -132,7 +132,7 @@ BCBE0,800CDAE0,src/code/padsetup BCD20,800CDC20,src/code/code_800FCE80 BD230,800CE130,src/code/fp BD390,800CE290,src/code/system_malloc -BD560,800CE460,src/code/code_800FD970 +BD560,800CE460,src/code/rand BD720,800CE620,src/code/__osMalloc BE4D0,800CF3D0,src/libultra/libc/sprintf BE5A0,800CF4A0,src/code/printutils @@ -295,7 +295,7 @@ F0640,80101540,src/code/logseverity F0650,80101550,src/code/gfxprint F0F00,80101E00,src/code/code_800FC620 F0F20,80101E20,src/code/fp -F0F30,80101E30,src/code/code_800FD970 +F0F30,80101E30,src/code/rand F0F40,80101E40,src/code/__osMalloc F0F50,80101E50,src/libultra/gu/sins F1750,80102650,src/libultra/io/siacs @@ -451,7 +451,7 @@ offset,vram,.bss 119530,8012A430,src/code/gfxprint 119540,8012A440,src/code/code_800FCE80 119550,8012A450,src/code/system_malloc -119580,8012A480,src/code/code_800FD970 +119580,8012A480,src/code/rand 119590,8012A490,src/code/__osMalloc 1195A0,8012A4A0,src/code/jpegdecoder 1195B0,8012A4B0,src/libultra/io/sptask