Last active
February 9, 2023 07:59
-
-
Save cs-altshift/fd3daecbace809ec0dfa566dcea13639 to your computer and use it in GitHub Desktop.
C# Allocation-free Seeded Random Number Generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
namespace AltShift.Maths.Randomization | |
{ | |
public struct SeededRandomizer | |
{ | |
public const string DEFAULT_CONSTRUCTOR_ERROR_STR = "SeededRandomizer created with the default constructor are not valid randomizer"; | |
public readonly bool isValid; | |
private SeededRandomNumberGenerator random; | |
public int Seed => random.seed; | |
public int GenerationCount => random.GenerationCount; | |
public double DoubleValue { | |
get { | |
if (!isValid) { | |
throw new InvalidOperationException(DEFAULT_CONSTRUCTOR_ERROR_STR); | |
} | |
return random.NextDouble(); | |
} | |
} | |
/// <summary> | |
/// Valeur dans l'interval [0; 1[ | |
/// </summary> | |
public float Value { | |
get { | |
if (!isValid) { | |
throw new InvalidOperationException(DEFAULT_CONSTRUCTOR_ERROR_STR); | |
} | |
return random.NextFloat(); | |
} | |
} | |
public int IntValue { | |
get { | |
if (!isValid) { | |
throw new InvalidOperationException(DEFAULT_CONSTRUCTOR_ERROR_STR); | |
} | |
return random.NextInt(); | |
} | |
} | |
public bool BoolValue { | |
get { | |
if (!isValid) { | |
throw new InvalidOperationException(DEFAULT_CONSTRUCTOR_ERROR_STR); | |
} | |
return (random.NextDouble() > 0.5); | |
} | |
} | |
public static SeededRandomizer Create() { | |
return new SeededRandomizer(-1); | |
} | |
public SeededRandomizer(SeededRandomizer _original) : this(_original.Seed, _original.GenerationCount) { } | |
public SeededRandomizer(int _seed, int _generationCount = 0) { | |
isValid = true; | |
random = new SeededRandomNumberGenerator(_seed); | |
FastForward(_generationCount); | |
} | |
public int Range(int _includedMin, int _excludedMax) { | |
if (!isValid) { | |
throw new InvalidOperationException(DEFAULT_CONSTRUCTOR_ERROR_STR); | |
} | |
return random.Range(_includedMin, _excludedMax); | |
} | |
public float Range(float _includedMin, float _excludedMax) { | |
if (!isValid) { | |
throw new InvalidOperationException(DEFAULT_CONSTRUCTOR_ERROR_STR); | |
} | |
return random.Range(_includedMin, _excludedMax); | |
} | |
/// <summary> | |
/// Return true if random is lower than _normalizedPercent | |
/// </summary> | |
public bool TestPercent(float _normalizedPercent) { | |
return Value < _normalizedPercent; | |
} | |
public T PickFrom<T>(IEnumerable<T> _enumerable) { | |
int count = 0; | |
foreach (T t in _enumerable) { | |
count++; | |
} | |
if (count == 0) { | |
throw new ArgumentException("Given enumerable should not be empty", nameof(_enumerable)); | |
} | |
int randomIndex = random.Range(0, count); | |
foreach (T t in _enumerable) { | |
if (randomIndex <= 0) { | |
return t; | |
} | |
randomIndex--; | |
} | |
return default(T); | |
} | |
public T PickFrom<T>(List<T> _list) { | |
int randomIndex = random.Range(0, _list.Count); | |
return _list[randomIndex]; | |
} | |
public void Reset(int _count = 0) { | |
if (!isValid) { | |
throw new InvalidOperationException(DEFAULT_CONSTRUCTOR_ERROR_STR); | |
} | |
random = new SeededRandomNumberGenerator(Seed); | |
FastForward(_count); | |
} | |
public void FastForward(int _count = 1) { | |
if (!isValid) { | |
throw new InvalidOperationException(DEFAULT_CONSTRUCTOR_ERROR_STR); | |
} | |
for (int i = 0; i < _count; i++) { | |
random.NextInt(); | |
} | |
} | |
/// <summary> | |
/// Create another randomizer with a seed generated from this randomizer. | |
/// </summary> | |
/// <param name="_generationCount">when != -1, the SeededRandomizer must have _generationCount generations or the game will assert</param> | |
/// <returns>A SeededRandomizer with next generated int of current SeededRandomizer as its seed</returns> | |
public SeededRandomizer CreateChild(int _generationCount = -1) { | |
bool shouldCheckGenerationCount = (_generationCount >= 0); | |
if (shouldCheckGenerationCount) { | |
if (GenerationCount != _generationCount) { | |
throw new ArgumentException(string.Format("SeededRandomizer Child must be the {0}th but it was the {1}th", _generationCount, GenerationCount)); | |
} | |
} | |
int childSeed = random.NextInt(); | |
return new SeededRandomizer(childSeed); | |
} | |
public override string ToString() { | |
return string.Format("{0}({1})", Seed, GenerationCount); | |
} | |
public void Shuffle<T>(IList<T> list) { | |
int n = list.Count; | |
for (int i = 0; i < n; i++) { | |
int r = i + random.Range(0, n - i); | |
T t = list[r]; | |
list[r] = list[i]; | |
list[i] = t; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace AltShift.Maths.Randomization | |
{ | |
// No-alloc version coded from Random source code | |
// https://referencesource.microsoft.com/#mscorlib/system/random.cs | |
public unsafe struct SeededRandomNumberGenerator | |
{ | |
private const int MSEED = 161803398; | |
private const int MZ = 0; | |
public readonly int seed; | |
public int GenerationCount { get; private set; } | |
// Generation variables | |
private int inext; | |
private int inextp; | |
private int cell00; | |
private int cell01; | |
private int cell02; | |
private int cell03; | |
private int cell04; | |
private int cell05; | |
private int cell06; | |
private int cell07; | |
private int cell08; | |
private int cell09; | |
private int cell10; | |
private int cell11; | |
private int cell12; | |
private int cell13; | |
private int cell14; | |
private int cell15; | |
private int cell16; | |
private int cell17; | |
private int cell18; | |
private int cell19; | |
private int cell20; | |
private int cell21; | |
private int cell22; | |
private int cell23; | |
private int cell24; | |
private int cell25; | |
private int cell26; | |
private int cell27; | |
private int cell28; | |
private int cell29; | |
private int cell30; | |
private int cell31; | |
private int cell32; | |
private int cell33; | |
private int cell34; | |
private int cell35; | |
private int cell36; | |
private int cell37; | |
private int cell38; | |
private int cell39; | |
private int cell40; | |
private int cell41; | |
private int cell42; | |
private int cell43; | |
private int cell44; | |
private int cell45; | |
private int cell46; | |
private int cell47; | |
private int cell48; | |
private int cell49; | |
private int cell50; | |
private int cell51; | |
private int cell52; | |
private int cell53; | |
private int cell54; | |
private int cell55; | |
public SeededRandomNumberGenerator(int _seed) { | |
cell00 = 0; | |
cell01 = 0; | |
cell02 = 0; | |
cell03 = 0; | |
cell04 = 0; | |
cell05 = 0; | |
cell06 = 0; | |
cell07 = 0; | |
cell08 = 0; | |
cell09 = 0; | |
cell10 = 0; | |
cell11 = 0; | |
cell12 = 0; | |
cell13 = 0; | |
cell14 = 0; | |
cell15 = 0; | |
cell16 = 0; | |
cell17 = 0; | |
cell18 = 0; | |
cell19 = 0; | |
cell20 = 0; | |
cell21 = 0; | |
cell22 = 0; | |
cell23 = 0; | |
cell24 = 0; | |
cell25 = 0; | |
cell26 = 0; | |
cell27 = 0; | |
cell28 = 0; | |
cell29 = 0; | |
cell30 = 0; | |
cell31 = 0; | |
cell32 = 0; | |
cell33 = 0; | |
cell34 = 0; | |
cell35 = 0; | |
cell36 = 0; | |
cell37 = 0; | |
cell38 = 0; | |
cell39 = 0; | |
cell40 = 0; | |
cell41 = 0; | |
cell42 = 0; | |
cell43 = 0; | |
cell44 = 0; | |
cell45 = 0; | |
cell46 = 0; | |
cell47 = 0; | |
cell48 = 0; | |
cell49 = 0; | |
cell50 = 0; | |
cell51 = 0; | |
cell52 = 0; | |
cell53 = 0; | |
cell54 = 0; | |
cell55 = 0; | |
seed = (_seed == -1) ? Environment.TickCount : _seed; | |
GenerationCount = 0; | |
// Initializing generation properties | |
int subtraction = (seed == int.MinValue) ? int.MaxValue : Math.Abs(seed); | |
int mj = MSEED - subtraction; | |
int mk = 1; | |
fixed (int* seedArray = &cell00) { | |
seedArray[55] = mj; | |
for (int i = 1; i < 55; i++) { | |
int ii = (21 * i) % 55; | |
seedArray[ii] = mk; | |
mk = mj - mk; | |
if (mk < 0) { | |
mk += int.MaxValue; | |
} | |
mj = seedArray[ii]; | |
} | |
for (int k = 1; k < 5; k++) { | |
for (int i = 1; i < 56; i++) { | |
seedArray[i] -= seedArray[1 + (i + 30) % 55]; | |
if (seedArray[i] < 0) { | |
seedArray[i] += int.MaxValue; | |
} | |
} | |
} | |
} | |
inext = 0; | |
inextp = 21; | |
} | |
public float NextFloat() { | |
return (float) NextDouble(); | |
} | |
public double NextDouble() { | |
return (NextInt() * (1.0 / int.MaxValue)); | |
} | |
public double NextDoubleForLargeRange() { | |
int result = NextInt(); | |
bool negative = (NextInt(_increaseGenerationCount: false) % 2 == 0); | |
if (negative) { | |
result = -result; | |
} | |
double d = result; | |
d += (int.MaxValue - 1); | |
d /= 2 * (uint) int.MaxValue - 1; | |
return d; | |
} | |
public int NextInt() { | |
return NextInt(_increaseGenerationCount: true); | |
} | |
private int NextInt(bool _increaseGenerationCount) { | |
if (_increaseGenerationCount) { | |
GenerationCount++; | |
} | |
int locINext = inext + 1; | |
if (locINext >= 56) { | |
locINext = 1; | |
} | |
int locINextp = inextp + 1; | |
if (locINextp >= 56) { | |
locINextp = 1; | |
} | |
fixed (int* seedArray = &cell00) { | |
int retVal = seedArray[locINext] - seedArray[locINextp]; | |
if (retVal == int.MaxValue) { | |
retVal--; | |
} | |
else if (retVal < 0) { | |
retVal += int.MaxValue; | |
} | |
seedArray[locINext] = retVal; | |
inext = locINext; | |
inextp = locINextp; | |
return retVal; | |
} | |
} | |
public int Range(int _includedMin, int _excludedMax) { | |
if (_excludedMax < _includedMin) { | |
throw new ArgumentException("Maximum value must be greater or equal to minimum value"); | |
} | |
long range = (long) _excludedMax - _includedMin; | |
if (range <= (long) int.MaxValue) { | |
return ((int) (NextDouble() * range) + _includedMin); | |
} | |
else { | |
return (int) ((long) (NextDoubleForLargeRange() * range) + _includedMin); | |
} | |
} | |
public float Range(float _includedMin, float _excludedMax) { | |
return _includedMin + NextFloat() * (_excludedMax - _includedMin); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment