Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@jcdickinson
Created May 31, 2012 15:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcdickinson/2844077 to your computer and use it in GitHub Desktop.
Save jcdickinson/2844077 to your computer and use it in GitHub Desktop.
Proc Number Generator
/// <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;
}
}
@jcdickinson
Copy link
Author

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment