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

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

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

        
        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, и произвести с ним несложные математические действия:

        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 значение больше максимального. Прошу это проверить.

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