Обычно от генератора случайных чисел можно ожидать, что он не только генерирует случайные числа, но и что числа генерируются равномерно случайным образом.
Существует два определения равномерно случайных: дискретно равномерно случайных и непрерывно равномерно случайных .
Дискретно равномерно случайный имеет смысл для генератора случайных чисел, который имеет конечное число различных возможных результатов. Например, генерация целого числа от 1 до 10. Затем можно ожидать, что вероятность получения 4 равна вероятности получения 7.
Постоянно равномерно случайный имеет смысл, когда генератор случайных чисел генерирует числа в диапазоне. Например, генератор, который генерирует действительное число от 0 до 1. Затем можно ожидать, что вероятность получения числа от 0 до 0,5 равна вероятности получения числа от 0,5 до 1.
Когда генератор случайных чисел генерирует числа с плавающей запятой (что в сущности и является System.Decimal - это просто число с плавающей запятой, основание 10), можно утверждать, что правильное определение равномерно случайного числа:
С одной стороны, поскольку число с плавающей запятой представляется в компьютере фиксированным числом битов, очевидно, что существует конечное число возможных результатов. Таким образом, можно утверждать, что правильное распределение - это дискретное непрерывное распределение, в котором каждое представимое число имеет одинаковую вероятность. Это в основном то, что делает реализация Джона Скита и Джона Лейдегрена .
С другой стороны, можно утверждать, что, поскольку число с плавающей запятой, как предполагается, является приближением к действительному числу, было бы лучше попытаться приблизить поведение непрерывного генератора случайных чисел, даже если Фактический ГСЧ фактически дискретен. Это поведение, которое вы получаете от Random.NextDouble (), где - даже если в диапазоне 0,00001-0,00002 представлено примерно столько представимых чисел, сколько в диапазоне 0,8-0,9, у вас в тысячу раз больше шансов получить число во втором диапазоне - как и следовало ожидать.
Поэтому правильная реализация Random.NextDecimal (), вероятно, должна быть равномерно распределена непрерывно.
Вот простой вариант ответа Джона Скита, который равномерно распределен между 0 и 1 (я повторно использую его метод расширения NextInt32 ()):
public static decimal NextDecimal(this Random rng)
{
return new decimal(rng.NextInt32(),
rng.NextInt32(),
rng.Next(0x204FCE5E),
false,
0);
}
Вы также можете обсудить, как получить равномерное распределение по всему диапазону десятичных дробей. Возможно, есть более простой способ сделать это, но это небольшое изменение ответа Джона Лейдгрена должно привести к относительно равномерному распределению:
private static int GetDecimalScale(Random r)
{
for(int i=0;i<=28;i++){
if(r.NextDouble() >= 0.1)
return i;
}
return 0;
}
public static decimal NextDecimal(this Random r)
{
var s = GetDecimalScale(r);
var a = (int)(uint.MaxValue * r.NextDouble());
var b = (int)(uint.MaxValue * r.NextDouble());
var c = (int)(uint.MaxValue * r.NextDouble());
var n = r.NextDouble() >= 0.5;
return new Decimal(a, b, c, n, s);
}
По сути, мы следим за тем, чтобы значения масштаба выбирались пропорционально размеру соответствующего диапазона.
Это означает, что мы должны получить шкалу 0 90% времени - поскольку этот диапазон содержит 90% возможного диапазона - шкалу 1 9% времени и т. Д.
По-прежнему существуют некоторые проблемы с реализацией, поскольку она учитывает, что некоторые числа имеют несколько представлений, но она должна быть намного ближе к равномерному распределению, чем другие реализации.