Генерация (псевдо) случайных ограниченных значений (U) Int64 и Decimal

Примечание.. Для краткости не следует различать случайность и псевдослучайность. Кроме того, в этом контексте ограниченные средства между заданными значениями min и max)

Класс System.Random обеспечивает случайную генерацию целых чисел, парных и байтовых массивов. Используя Random.Next, можно легко генерировать случайные ограниченные значения типа Boolean, Char, (S) Byte, (U) Int16, (U) Int32. Используя Random.NextDouble(), можно аналогичным образом генерировать ограниченные значения типов Double и Single (насколько я понимаю этот тип). Генерация случайной строки (заданной длины и алфавита) имеет также .

Рассмотрим оставшиеся примитивные типы данных (исключая Object): Decimal и (U) Int64. Их случайное поколение также было решено (Decimal, (U) Int64 с помощью Random.NextBytes())., но не при ограничении. Теоретически можно использовать выборку отклонений (т.е. Цикл до тех пор, пока генерируемое значение не станет желательным диапазоном), но это, очевидно, не является практическим решением. Нормализация NextDouble() не будет работать, поскольку она не имеет достаточных значащих цифр.

Короче говоря, я прошу о правильной реализации следующих функций:

long NextLong(long min, long max)
long NextDecimal(decimal min, decimal max)

Обратите внимание, что поскольку System.DateTime основано на ulong, первая функция также допускает случайную ограниченную генерацию таких структур (аналогично здесь, только в тиках вместо минут).

Ответ 1

Предположим, вы знаете, как сгенерировать N случайных бит. Это довольно легко сделать либо с помощью NextBytes, либо с повторением вызовов Random.Next с соответствующими ограничениями.

Чтобы создать длинный/улунг в правильном диапазоне, выясните, насколько велик диапазон, и сколько бит требуется для его представления. Затем вы можете использовать выборку отбраковки, которая в худшем случае отклонит половину сгенерированных значений (например, если вы хотите получить значение в диапазоне [0, 128], что означает, что вы будете генерировать [0, 255] несколько раз). Если вам нужен диапазон, отличный от нуля, просто определите размер диапазона, создайте случайное значение в [0, размер), а затем добавьте базу.

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

Ответ 2

Это должно сделать это. Для десятичного числа я использовал первоначальный подход Джона Скита к генерации случайных decimal (без ограничений). Для long я предоставил метод создания случайного неотрицательного long, который затем используется для создания значения в случайном диапазоне.

Заметим, что при decimal результирующее распределение не равномерно распределено на [minValue, maxValue]. Он просто равномерен во всех представлениях бит десятичных знаков, которые попадают в диапазон [minValue, maxValue]. Я не вижу в этом простого пути без использования отбраковки.

При long результирующее распределение равномерно на [minValue, maxValue).

static class RandomExtensions {
    static int NextInt32(this Random rg) {
        unchecked {
            int firstBits = rg.Next(0, 1 << 4) << 28;
            int lastBits = rg.Next(0, 1 << 28);
            return firstBits | lastBits;
        }
    }

    public static decimal NextDecimal(this Random rg) {
        bool sign = rg.Next(2) == 1;
        return rg.NextDecimal(sign);
    }

    static decimal NextDecimal(this Random rg, bool sign) {
        byte scale = (byte)rg.Next(29);
        return new decimal(rg.NextInt32(),
                           rg.NextInt32(),
                           rg.NextInt32(),
                           sign,
                           scale);
    }

    static decimal NextNonNegativeDecimal(this Random rg) {
        return rg.NextDecimal(false);
    }

    public static decimal NextDecimal(this Random rg, decimal maxValue) {
        return (rg.NextNonNegativeDecimal() / Decimal.MaxValue) * maxValue; ;
    }

    public static decimal NextDecimal(this Random rg, decimal minValue, decimal maxValue) {
        if (minValue >= maxValue) {
            throw new InvalidOperationException();
        }
        decimal range = maxValue - minValue;
        return rg.NextDecimal(range) + minValue;
    }

    static long NextNonNegativeLong(this Random rg) {
        byte[] bytes = new byte[sizeof(long)];
        rg.NextBytes(bytes);
        // strip out the sign bit
        bytes[7] = (byte)(bytes[7] & 0x7f);
        return BitConverter.ToInt64(bytes, 0);
    }

    public static long NextLong(this Random rg, long maxValue) {
        return (long)((rg.NextNonNegativeLong() / (double)Int64.MaxValue) * maxValue);
    }

    public static long NextLong(this Random rg, long minValue, long maxValue) {
        if (minValue >= maxValue) {
            throw new InvalidOperationException();
        }
        long range = maxValue - minValue;
        return rg.NextLong(range) + minValue;
    }
}

Ответ 3

Основываясь на методе Джона Скита, здесь мой удар по нему:

public static long NextLong(this Random rnd, long min, long max) 
{
    if (max <= min) 
    {
        throw new Exception("Min must be less than max.");
    }

    long dif = max - min;

    var bytes = new byte[8];
    rnd.NextBytes(bytes);
    bytes[7] &= 0x7f; //strip sign bit

    long posNum = BitConverter.ToInt64(bytes, 0);
    while (posNum > dif)
    {
        posNum >>= 1;
    }

    return min + posNum;
}

Сообщите мне, видите ли вы какие-либо ошибки.

Ответ 4

Я пришел сюда, чтобы найти способ генерировать 64-битные значения в произвольном диапазоне. Другие ответы не дали случайного числа, если заданы определенные диапазоны (например, long.MinValue для long.MaxValue). Вот моя версия, которая, кажется, решает проблему:

public static long NextInt64(this Random random, long minValue, long maxValue)
{
    Contract.Requires(random != null);
    Contract.Requires(minValue <= maxValue);
    Contract.Ensures(Contract.Result<long>() >= minValue &&
                     Contract.Result<long>() < maxValue);

    return (long)(minValue + (random.NextUInt64() % ((decimal)maxValue - minValue)));
}

Он использует следующие методы расширения:

public static ulong NextUInt64(this Random random)
{
    Contract.Requires(random != null);

    return BitConverter.ToUInt64(random.NextBytes(8), 0);
}

public static byte[] NextBytes(this Random random, int byteCount)
{
    Contract.Requires(random != null);
    Contract.Requires(byteCount > 0);
    Contract.Ensures(Contract.Result<byte[]>() != null &&
                     Contract.Result<byte[]>().Length == byteCount);

    var buffer = new byte[byteCount];
    random.NextBytes(buffer);
    return buffer;
}

Распределение не является идеальным даже тогда, когда размер запрашиваемого диапазона не является чистым делителем 2 ^ 64, но по меньшей мере предоставляет случайное число в диапазоне запросов для любого заданного диапазона.

Ответ 5

long posNum = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0); 


 use this instead of NextBytes