Skip to content

Instantly share code, notes, and snippets.

@cs-altshift
Last active February 9, 2023 07:59
Show Gist options
  • Save cs-altshift/fd3daecbace809ec0dfa566dcea13639 to your computer and use it in GitHub Desktop.
Save cs-altshift/fd3daecbace809ec0dfa566dcea13639 to your computer and use it in GitHub Desktop.
C# Allocation-free Seeded Random Number Generator
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;
}
}
}
}
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