public
Created

Proc Number Generator

  • Download Gist
ProcNumberGenerator.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
/// <summary>
/// Represents a way to manage random events that should
/// occur a certain percentage of the time.
/// </summary>
/// <remarks>
/// This generator will correct unfair samples; for example, if it is set up
/// with a 50% chance to proc and 5 nonprocs are sampled; there is a good
/// chance that the next 5 samples will proc.
/// </remarks>
public class ProcNumberGenerator
{
private Random _random = new Random(Environment.TickCount ^ Thread.CurrentThread.ManagedThreadId);
private int _samples;
private float _actual;
private float _desired;
 
/// <summary>
/// Gets or sets the desired percentages of procs.
/// </summary>
/// <value>
/// The value should be between 0 and 1 (a percentage divided by 100).
/// </value>
public float Desired
{
get
{
return _desired;
}
set
{
if (_samples == 0 || _desired == 0)
{
_desired = value;
}
else
{
// Deviation is adjusted so that it is though
// the previous desired percentage was 100%.
// It is then adjusted back to a percentage
// of the new desired percentage.
 
//var deviation = (_actual - _desired) * (1 / _desired);
//_actual = deviation * value + value;
 
_actual = (_actual - _desired) * (value / _desired) + value;
_desired = value;
 
// Allow it to recover slightly faster.
_samples = (int)(_samples * 0.8f);
}
}
}
 
/// <summary>
/// Gets the actual percentage achieved.
/// </summary>
public float Actual
{
get
{
return _actual;
}
}
 
/// <summary>
/// Creates a new instance of the <see cref="ProcNumberGenerator"/>, with
/// a 50% chance of proccing.
/// </summary>
public ProcNumberGenerator()
: this(0.5f)
{
 
}
 
/// <summary>
/// Creates a new instance of the <see cref="ProcNumberGenerator"/>.
/// </summary>
/// <param name="desired">The desired percentage of procs as a value between 0 and 1.</param>
public ProcNumberGenerator(float desired)
{
_desired = desired;
}
 
/// <summary>
/// Gathers the next random boolean value from the generator; that has been
/// statistically adjusted.
/// </summary>
/// <returns>The resulting random boolean value.</returns>
public bool Next()
{
if (_desired == 0)
{
Notify(false);
return false;
}
else if (_desired == 1)
{
Notify(true);
return true;
}
 
var total = _desired + _actual;
var rand = _random.NextDouble() * total;
var result = rand <= _desired;
 
Notify(result);
 
return result;
}
 
/// <summary>
/// Notifies the generator that a non-random event
/// has occurred and that it should take it into account.
/// </summary>
/// <param name="procced">A value indicating the outcome of the event.</param>
public void Notify(bool procced)
{
var num = procced ? 1f : 0f;
 
if (_samples == 0)
{
_actual = num;
_samples = 1;
return;
}
 
var newSamples = _samples + 1;
 
var oldContrib = _samples / (float)newSamples;
var newContrib = num / (float)newSamples;
 
_actual = (_actual * oldContrib) + newContrib;
_samples = newSamples;
}
}

One improvement would be to limit _samples to a value passed in by the constructor, e.g. _samples = Math.Min(newSamples, _maxSamples). This would prevent unlucky streaks in the long term (e.g. at 1000 samples the system would recover pretty slowly).

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.