среда, 25 марта 2015 г.

Как в .Net получить действительно случайные числа

Наверняка, всем известно о существовании в .Net класса System.Random, позволяющего получать якобы случайные числа практически без особых как умственных, так и временных затрат. Представьте что у нас есть такой вот метод, который чисто ради эксперимента мы вызовем в цикле несколько раз:

1
2
3
4
5
6
7
8
9
10
static string GetRandomNum(int minValue, int maxValue)
{
    Random rnd = new Random();
    return rnd.Next(minValue, maxValue).ToString();
}
 
for (int i = 0; i < 10; i++)
{
    Console.WriteLine(GetRandomNum(1,11));
}


В данном случае вы получите совершенно одинаковые все "случайные" цифры.

Происходит так потому, что при инициализации объекта Random с использованием конструктора по умолчанию в качестве числа, использующегося для вычисления случайных чисел используется Environment.TickCount (количество миллисекунд, прошедших со времени старта системы), который обновляется раз в 15.6 миллисекунд.
Соответственно, на коротких интервалах и получаются одинаковые "случайные" числа. И, кстати, если каждый день включать компьютер в одно и то же время, то выполняя этот код в одно и то же время с начала старта операционной системы в течении нескольких дней, вы тоже будете получать одни и те же потрясающе случайные значения. И если в рамках одного периода включения-выключения компьютера с этим можно бороться путем создания и инициализации объекта класса Random один раз, то с тем, что значения будут совсем довольно предсказуемы в рамках нескольких дней в Random сделать ничего нельзя.

Для получения гарантированно значительно более случайных и предсказуемых чисел в .Net существует класс RNGCryptoServiceProvider, находящийся в пространстве имен System.Security.Cryptography, в котором нас интересует метод GetBytes(byte[] data), заполняющий массив байтом криптостойкой случайной последовательностью чисел. Но как же с помощью него получить, то, что мы получали от Random, а именно случайное число, находящееся между двумя заданными значениями?

Сделать это довольно просто, если представить полученный случайный байт как число из диапазона от 0 до 1, и произвести с ним несложные математические действия:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static string GetRandomNum(int minValue, int maxValue)
{
    
    System.Security.Cryptography.RNGCryptoServiceProvider rnd = new System.Security.Cryptography.RNGCryptoServiceProvider();
 
    //Получаем наш случайный байт
    byte[] randombyte = new byte[1];
    rnd.GetBytes(randombyte);
    //превращаем его в число от 0 до 1
    double random_multiplyer = (randombyte[0] / 255d);
    //получаем разницу между минимальным и максимальным значением
    int difference = maxValue-minValue+1;
    //прибавляем к минимальному значение число от 0 до difference
    int result = (int)(minValue + Math.Floor(random_multiplyer * difference));
    return result.ToString();
}

В результате выполнения этого кода последовательность чисел будет уже, во-первых, более разнообразной, а, во-вторых, она будет действительно случайной.

1 комментарий:

  1. Автор, данный метод выдает на 1 значение больше максимального. Прошу это проверить.

    ОтветитьУдалить