Random numbers – windows cmd –
The Windows CMD shell contains a built-in variable called %RANDOM% that can be used to generate random numbers.
%RANDOM% generates a random integer from 0 to 32,767 (inclusive)
0 ≤ %RANDOM% ≤ 32767
The range of numbers can be made smaller than 32767 with a little arithmetic,
for example to generate a range between 1 and 500:
@ECHO OFF
SET /a _rand=(%RANDOM%*500/32768) 1
ECHO Random number %_rand%
(The Set /a will always round down)
If you try, it may look as though a larger range than 32767 will work, but doing this will produce gaps, for example changing 500 in the above to 65536 will result in a sequence of “random” numbers which only consists of odd numbers.
The distribution of numbers returned will be a determined by both the range and the quantity of numbers drawn.
For example if you are drawing random integer numbers where each number is between 0 and 100 then on average:
- If you draw 10 numbers then you should expect around 6% to be duplicates.
- If you draw 100 numbers then just over 63% will be duplicates i.e. matching one or more of the other 99 numbers.
- If you draw 1,000 numbers then almost all will be duplicates as there are only 100 possible values.
A pseudorandom sequence is not truly random but is determined by a small set of initial values (state), the initial seed is often based on the clock time. In the case of the CMD %RANDOM% the seed is based on the clock time when the CMD session started. This can be problematic when running a batch file, if the script always takes about the same time to run before calling %RANDOM% then the number returned will always lie within a small predictable range.
As an example create a file numbers.cmd:
@Echo off
Echo %RANDOM%
Then call the above with
CMD /c numbers.cmd
CMD /c numbers.cmd
CMD /c numbers.cmd
Raymond Chen [MSFT] has a detailed description of Why cmd.exe’s %RANDOM% isn’t so random
Johannes Baagøe has published a comparison of better random numbers for javascript. The fastest of these is Alea(), which you can find a copy of below. This has a number of advantages, you can create much larger numbers, it will create a lot of numbers quickly and if you call it passing a seed number then the results become repeatable – you can create exactly the same sequence of random numbers again at a later date.
// random.js // call this from the command line with: // C:> cscript /nologo random.js // or from PowerShell // PS C:> $myrandom = & cscript /nologo "c:batchrandom.js" // will create an array of 10 random numbers which you can then treat like any array variable: // PS C:> $myrandom[4] // Calling without a seed, the current time will be used as a seed var srandom=Alea(); // Calling with a seed will return the same value for the same seed //var seed=1234 //var srandom=Alea(seed); var i=0 // Return 10 random numbers while ( i < 10 ) { // Return a number between 1 and 500 million WScript.echo(Math.floor((srandom()*500000000) 1) ); i ; } function Mash() { var n = 0xefc8249d; var mash = function(data) { data = data.toString(); for (var i = 0; i < data.length; i ) { n = data.charCodeAt(i); var h = 0.02519603282416938 * n; n = h >>> 0; h -= n; h *= n; n = h >>> 0; h -= n; n = h * 0x100000000; // 2^32 } return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 }; mash.version = 'Mash 0.9'; return mash; } function Alea() { return (function(args) { // Johannes Baagoe <baagoe@baagoe.com>, 2021 var s0 = 0; var s1 = 0; var s2 = 0; var c = 1; if (args.length == 0) { args = [ new Date]; } var mash = Mash(); s0 = mash(' '); s1 = mash(' '); s2 = mash(' '); for (var i = 0; i < args.length; i ) { s0 -= mash(args[i]); if (s0 < 0) { s0 = 1; } s1 -= mash(args[i]); if (s1 < 0) { s1 = 1; } s2 -= mash(args[i]); if (s2 < 0) { s2 = 1; } } mash = null; var random = function() { var t = 2091639 * s0 c * 2.3283064365386963e-10; // 2^-32 s0 = s1; s1 = s2; return s2 = t - (c = t | 0); }; random.uint32 = function() { return random() * 0x100000000; // 2^32 }; random.fract53 = function() { return random() (random() * 0x200000 | 0) * 1.1102230246251565e-16; // 2^-53 }; random.version = 'Alea 0.9'; random.args = args; return random; } (Array.prototype.slice.call(arguments))); }; /* licensed according to the MIT - Expat license: Copyright (C) 2021 by Johannes Baagoe <baagoe@baagoe.org> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
“Anyone who attempts to generate random numbers by deterministic means is, of course, living in a state of sin” ~ John von Neumann
Related:
PowerShell Equivalent: Get-Random
VBScript: Rnd – Return a pseudorandom number.
Random.org – Generate true random numbers online.
Безопасный провайдер
К счастью, новый обобщённый класс
, появившийся в .NET 4, очень сильно облегчает написание провайдеров, обеспечивающих по одному экземпляру на поток. Просто нужно в
ThreadLocal передать делегат, который будет ссылаться на получение значения собственно нашего экземпляра. В данном случае я решил использовать единственное начальное значение (seed), инициализируя его при помощи Environment.TickCount (именно так действует конструктор Random без параметров).
Нижепредставленный класс полностью статический и содержит лишь один публичный (открытый) метод GetThreadRandom. Этот метод сделан методом, а не свойством, в основном из-за удобства: благодаря этому все классы, которым нужен экземпляр Random, будут зависеть от Func<Random> (делегат, указывающий на метод, не принимающий параметров и возвращающий значение типа Random), а не от самого класса Random.
Если тип предназначен для работы в одном потоке, он может вызвать делегат для получения единого экземпляра Random и после чего использовать его повсюду; если же тип должен работать в многопоточных сценариях, он может вызывать делегат каждый раз, когда ему требуется генератор случайных чисел.
Нижеприведенный класс создаст столько экземпляров класса Random, сколько есть потоков, и каждый экземпляр будет стартовать с различного начального значения. Если нам нужно использовать провайдер случайных чисел как зависимость в других типах, мы можем сделать так: new TypeThatNeedsRandom(RandomProvider.GetThreadRandom). Ну а вот и сам код:
using System;
using System.Threading;
public static class RandomProvider
{
private static int seed = Environment.TickCount;
private static ThreadLocal<Random> randomWrapper = new ThreadLocal<Random>(() =>
new Random(Interlocked.Increment(ref seed))
);
public static Random GetThreadRandom()
{
return randomWrapper.Value;
}
}
Достаточно просто, не правда ли? Всё потому, что весь код направлен на выдачу правильного экземпляра Random. После того, как экземпляр создан и возвращён, совершенно неважно, что вы будете с ним делать дальше: все дальнейшие выдачи экземпляров совершенно не зависят от текущего.
Использование криптографического генератора случайных чисел
.NET содержит абстрактный класс
, от которого должны наследоваться все реализации криптографических генераторов случайных чисел (далее — криптоГСЧ). Одну из таких реализаций содержит и .NET — встречайте класс
. Идея криптоГСЧ в том, что даже если он всё так же является генератором псевдослучайных чисел, он обеспечивает достаточно сильную непредсказуемость результатов. RNGCryptoServiceProvider использует несколько источников энтропии, которые фактически являются «шумами» в вашем компьютере, и генерируемую им последовательность чисел очень тяжело предсказать.
Более того, «внутрикомпьютерный» шум может использоваться не только в качестве начального состояния, но и между вызовами следующих случайных чисел; таким образом, даже зная текущее состояние класса, этого не хватит для вычисления как следующих чисел, которые будут сгенерированы в будущем, так и тех, которые были сгенерированы ранее.
Вообще-то точное поведение зависит от реализации. Помимо этого, Windows может использовать специализированное аппаратное обеспечение, являющееся источником «истинных случайностей» (например, это может быть датчик распада радиоактивного изотопа) для генерации ещё более защищённых и надёжных случайных чисел.
Сравним это с ранее рассматриваемым классом Random. Предположим, вы вызвали Random.Next(100) десять раз и сохранили результаты. Если вы имеете достаточно вычислительной мощи, то можете сугубо на основании этих результатов вычислить начальное состояние (seed), с которым был создан экземпляр Random, предсказать следующие результаты вызова Random.Next(100) и даже вычислить результаты предыдущих вызовов метода.
Такое поведение катастрофически неприемлемо, если вы используете случайные числа для обеспечения безопасности, в финансовых целях и т.д. КриптоГСЧ работают существенно медленнее класса Random, но генерируют последовательность чисел, каждое из которых более независимо и непредсказуемо от значений остальных.
В большинстве случаев более низкая производительность не является препятствием — им является плохой API. RandomNumberGenerator создан для генерации последовательностей байтов — и всё. Сравните это с методами класса Random, где есть возможность получения случайного целого числа, дробного числа, а также набора байтов.
Ещё одно полезное свойство — возможность получения случайного числа в указанном диапазоне. Сравните эти возможности с массивом случайных байтов, который выдаёт RandomNumberGenerator. Исправить ситуацию можно, создав свою оболочку (враппер) вокруг RandomNumberGenerator, которая будет преобразовывать случайные байты в «удобный» результат, однако это решение нетривиально.
Тем не менее, в большинстве случаев «слабость» класса Random вполне подходит, если вы сможете решить проблему, описанную в начале статьи. Посмотрим, что здесь можно сделать.
Используйте один экземпляр класса random при множественных вызовах
Вот он, корень решения проблемы — использовать лишь один экземпляр Random при создании множества случайных чисел посредством Random.Next. И это очень просто — посмотрите, как можно изменить вышеприведённый код:
// Этот код будет получше
Random rng = new Random();
for (int i = 0; i < 100; i )
{
Console.WriteLine(GenerateDigit(rng));
}
...
static int GenerateDigit(Random rng)
{
// Предположим, что здесь много логики
return rng.Next(10);
}
Теперь в каждой итерации будут разные числа, … но это ещё не всё. Что будет, если мы вызовем этот блок кода два раза подряд? Правильно, мы создадим два экземпляра Random с одинаковым начальным значением и получим два одинаковых набора случайных чисел. В каждом наборе числа будут различаться, однако между собой эти наборы будут равны.
Есть два способа решения проблемы. Во-первых, мы можем использовать не экземплярное, а статическое поле, содержащее экземпляр Random, и тогда вышеприведённый кусок кода создаст лишь один экземпляр, и будет его использовать, вызываясь сколько угодно раз.
Во-вторых, мы можем вообще убрать оттуда создание экземпляра Random, переместив его «повыше», в идеале — на самый «верх» программы, где будет создан единичный экземпляр Random, после чего он будет передаваться во все места, где нужны случайные числа.
Как это исправить?
Есть немало решений проблемы, каждое со своими плюсами и минусами. Мы рассмотрим несколько из них.
Объяснение
Класс Random не является истинным генератором случайных чисел, он содержит генератор
псевдо
случайных чисел. Каждый экземпляр класса Random содержит некоторое внутреннее состояние, и при вызове метода
(или
, или
) метод использует это состояние для возврата числа, которое будет казаться случайным. После этого внутреннее состояние меняется таким образом, чтобы при следующем вызове метода Next он возвратил другое кажущееся-случайным число, отличное от возвращённого ранее.
Все «внутренности» работы класса Random полностью детерминистичны. Это значит, что если вы возьмёте несколько экземпляров класса Random с одинаковым начальным состоянием, которое задаётся через параметр конструктора seed, и для каждого экземпляра вызовите определённые методы в одинаковом порядке и с одинаковыми параметрами, то в конце вы получите одинаковые результаты.
Так что ж плохого в вышеприведённом коде? Плохо то, что мы используем новый экземпляр класса Random внутри каждой итерации цикла. Конструктор Random, не принимающий параметров, принимает значение текущей даты и времени как seed (начальное состояние).
Итерации в цикле «прокрутятся» настолько быстро, что системное время «не успеет измениться» по их окончании; таким образом, все экземпляры Random получат в качестве начального состояния одинаковое значение и поэтому возвратят одинаковое псевдослучайное число.
Постановка проблемы
На StackOverflow, в новостных группах и рассылках все вопросы по теме «random» звучат примерно так:
Я использую Random.Next для генерации нескольких случайных чисел, но метод возвращает одно и то же число при его множественных вызовах. Число меняется при каждом запуске приложения, однако в рамках одного выполнения программы оно постоянное.
В качестве примера кода приводится примерно следующее:
// Плохой код! Не использовать!
for (int i = 0; i < 100; i )
{
Console.WriteLine(GenerateDigit());
}
...
static int GenerateDigit()
{
Random rng = new Random();
// Предположим, что здесь много логики
return rng.Next(10);
}
Итак, что здесь неправильно?
Потокобезопасность
Класс Random не потокобезопасен. Учитывая то, как мы любим создавать один экземпляр и использовать его по всей программе на протяжении всего времени её выполнения (синглтон, привет!), отсутствие потокобезопасности становится реальной занозой. Ведь если мы используем один экземпляр одновременно в нескольких потоках, то есть вероятность обнуления его внутреннего состояния, и если это произойдёт, то с этого момента экземпляр станет бесполезным.
Снова-таки, здесь есть два пути решения проблемы. Первый путь по-прежнему предполагает использование одного экземпляра, однако на этот раз с использованием блокировки ресурса посредством монитора. Для этого необходимо создать оболочку вокруг Random, которая будет оборачивать вызов его методов в оператор lock, обеспечивающий эксклюзивный доступ к экземпляру для вызывающей стороны. Этот путь плох тем, что снижает производительность в многопоточно-интенсивных сценариях.
Другой путь, который я опишу ниже — использование по одному экземпляру на каждый поток. Единственное, нам нужно удостовериться, что при создании экземпляров мы используем разные начальные значения (seed), а потому мы не можем использовать конструкторы по умолчанию. Во всём остальном этот путь относительно прямолинеен.
Проблемы с дизайном интерфейса
Одна проблема всё равно остаётся: мы используем слабо защищённый генератор случайных чисел. Как упоминается ранее, существует намного более безопасная во всех отношениях версия ГСЧ в RandomNumberGenerator, реализация которого находится в классе RNGCryptoServiceProvider. Однако его API достаточно сложно использовать в стандартных сценариях.
Было бы очень приятно, если бы провайдеры ГСЧ в фреймворке имели отдельные «источники случайности». В таком случае мы могли бы иметь единый простой и удобный API, который бы поддерживался как небезопасной-но-быстрой реализацией, так и безопасной-но-медленной.
Что-ж, мечтать не вредно. Возможно, подобный функционал появится в следующих версиях .NET Framework. Возможно, кто-то не из Microsoft предложит свою реализацию адаптера. (К сожалению, я не буду этим кем-то… правильная реализация подобной задумки удивительно сложна.)
Вы также можете создать свой класс, отнаследовав его от Random и переопределив методы Sample и NextBytes, однако неясно, как именно они должны работать, и даже собственная реализация Sample может быть намного сложнее, нежели кажется. Может быть, в следующий раз…