Разработка датчика случайных чисел, распределенных по требуемому закону

Нередко в своих программах нам приходится использовать генератор псевдослучайных чисел, или, как его еще называют, Random. С его помощью мы можем получать псевдослучайные числа, распределенные по равномерному закону. Иными словами, вероятности появления всех чисел одинаковы. Однако не всегда это удобно, и порой датчик, который мог бы генерировать числа как-то иначе, просто необходим, так что сейчас мы будем разбираться, как реализовать подобную штуку.

План действий

Первым делом нам нужно выбрать функцию плотности вероятности. Эта функция должна удовлетворять двум условиям:

  1. На заданном нами отрезке [a; b] функция должна быть положительной.
  2. Площадь под функцией на этом отрезке должна равняться единице.

В общем, все просто. Берем любую функцию, строим ее график и выбираем отрезок, на котором функция принимает положительные значения. Но тут может возникнуть проблема: площадь под выбранной функцией не равна единице. Чтобы это исправить, воспользуемся тем, что интересующая нас площадь ? есть площадь криволинейной трапеции, а она, в свою очередь, находится по формуле: Разработка датчика случайных чисел, распределенных по требуемому закону (Выч. мат, С#)Таким образом, найдя значение интеграла и разделив на него нашу функцию, мы получим новую функцию fk, которая будет удовлетворять всем нашим условиям. Теперь для получения одной случайной величины, нужно всего-навсего найти корень следующего уравнения, который и будет являться искомой величиной:

#image.jpgЗдесь V — случайная величина, равномерно распределенная на отрезке [0; 1], то есть величина, получаемая с помощью стандартного генератора псевдослучайных чисел.

Программная реализация

Создаем класс MyRandom, который будет наследоваться от стандартного класса Random.

/*
* MyRandom -- представляет генератор псевдослучайных чисел, распределенных по
* закону с плотностью вероятности, равной f(x) = xsin(x) + 10
* на отрезке [2; 10]
*/
public class MyRandom : Random
{
// Интервал функции плотности вероятности [a; b]
private double a = 2;
private double b = 10;
private Func<double, double, double> func = (x, v) => (10 * x + Math.Sin(x) - x * Math.Cos(x) - 21.7415910999) - 86.1051 * v;
}

Здесь func — функция, возвращающее значение левой части уже упомянутого выше уравнения:#image.jpg

// Функция, реализующая метод половинного деления
// Возвращает значение функции func в точке x с точностью eps
private double calcByBisectionMethod(double v, double eps)
{
double a = this.a;
double b = this.b;
// Сравнение с нулем
if (Math.Abs(func(a, v)) < 0.000000001)
{
return a;
}
if (Math.Abs(func(b, v)) < 0.000000001)
{
return b;
}
double differ = b - a;
double middle = (a + b) / 2;
while (Math.Abs(func(middle, v)) > eps)
{
differ /= 2;
middle = a + differ;
if (func(a, v) * func(middle, v) < 0)
{
b = middle;
}
else
{
a = middle;
}
}
return middle;
}

Теперь осталось за малым, а именно переопределить все методы родительского класса.

// Возвращает случайную величину от 0 до 1,
// распределенную по нашему закону
protected override double Sample()
{
// Случайная величина от 2 до 10
double tempValue = calcByBisectionMethod(base.Sample(), 0.01);
// Смещаем функцию на отрезок [0; 1]
return (tempValue - a) / (b - a);
}
// Возвращает случайную величину от 0 до 1,
// распределенную по нашему закону
public override double NextDouble()
{
return this.Sample();
}
public override int Next(int minValue, int maxValue)
{
return (int)(this.Sample() * (maxValue - minValue) + minValue);
}
public override int Next(int maxValue)
{
return this.Next(0, maxValue);
}
public override int Next()
{
return this.Next(int.MaxValue);
}
public override void NextBytes(byte[] buffer)
{
int count = buffer.Length;
for (int i = 0; i < count; i++)
{
buffer[i] = Convert.ToByte(this.Next(Byte.MaxValue));
}
}

На этом все. Единственное, на что следует обратить внимание, так это на следующее примечание с сайта msdn.microsoft.com:

Примечания для наследующих объектов

Начиная с платформы .NET Framework версии 2.0, при создании класса, производного от класса Random, и переопределении метода Sample распределение, предоставленное реализацией метода Sample в производном классе, не используется для вызова реализации следующих методов в базовом классе:

  • Метод Random.NextBytes(Byte[]).
  • Метод Random.Next().
  • Метод Random.Next(Int32, Int32), если (maxValue. - minValue) больше Int32.MaxValue.

Вместо них используется равномерное распределение, предоставляемое базовым классом Random. Такой принцип действия повышает общую производительность класса Random. Чтобы изменить данный принцип для вызова реализации метода Sample в производном классе, необходимо также переопределить принцип действия этих трех членов.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *