Как сгенерировать несмещенное случайное число в произвольном диапазоне, используя наименьшее количество битов

Из-за принципа голубиной дыры вы не можете просто использовать

output = min + (rand() % (int)(max - min + 1))

для получения беспристрастного, однородного результата. Этот ответ на аналогичный вопрос дает одно решение, но оно очень расточительно с точки зрения потребляемых случайных битов.

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

Хотя я уверен, что оптимальный размер исходного диапазона может быть получен, это не решает вопроса о том, что может быть лучший алгоритм, чем оптимизация этого.

[EDIT] Моя идея была показана в ответах для получения предвзятых результатов.

Мне приходит в голову подход

  1. Потребляйте минимальное количество битов, необходимое для охвата желаемого диапазона.
  2. Если это значение выходит за пределы желаемого диапазона, отбрасывайте только один бит и потребляйте еще один.
  3. Повторяйте по мере необходимости.

person Eoin    schedule 27.12.2013    source источник
comment
Страница руководства для rand() предлагает что-то вроде этого: min + (int) (((double) rand() / (RAND_MAX + 1.0)) * (max - min + 1)). Идея состоит в том, чтобы масштабировать вывод из rand() в дробь в диапазоне [0, 1), затем умножить на ваш диапазон и добавить минимум, чтобы получить число в диапазоне [min, max). Вероятно, есть способы сделать это, не прибегая к более медленным операциям с плавающей запятой, но хотите ли вы их использовать, будет зависеть от ваших ожиданий производительности и от того, насколько сложным вы хотите получить...   -  person twalberg    schedule 27.12.2013
comment
Ваш подход приведет к неравномерному распределению результатов.   -  person Henry    schedule 27.12.2013
comment
rand() возвращает фиксированное количество случайных битов, вы предлагаете использовать только некоторые из них, а остальные сохранить на потом? Это кажется гораздо более громоздким, чем необходимо, поскольку биты свободны. Если вы беспокоитесь об исчерпании потока битов (что на самом деле означает, что вы начали повторять случайную последовательность), вам следует рассмотреть лучший (настоящий?) случайный источник.   -  person Dwayne Towell    schedule 27.12.2013
comment
@twalberg, если бы вы прочитали вопрос и ссылки в нем, вы бы поняли недостаток этого подхода. Это работает, когда допустима небольшая погрешность ваших случайных чисел, что охватывает множество практических приложений. Это не универсально.   -  person Mark Ransom    schedule 27.12.2013
comment
@DwayneTowell В моем случае производство случайных битов относительно дорого. Думайте в том же духе, что и источник вроде random.org.   -  person Eoin    schedule 28.12.2013
comment
comment
@LiorKogan, а не дубликат. Смотрите мой ответ, чем этот вопрос отличается от других.   -  person Mark Ransom    schedule 29.12.2013
comment
stackoverflow.com/a/17749722/2417578 содержит пример алгоритма сохранения битов и ссылку на документ, анализирующий различные алгоритмы сохранения. против различной стоимости источника ГСЧ. Должно быть все необходимое.   -  person sh1    schedule 09.08.2019


Ответы (2)


Обычный подход к устранению предвзятости состоит в том, чтобы отбрасывать числа, выходящие за пределы желаемого диапазона. Как уже отмечалось, это расточительно. Можно свести к минимуму потери, начав с большего количества битов и одновременно сгенерировав несколько случайных чисел; вы можете добиться лучшего соответствия между диапазоном входов и выходов.

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

def pigeon_die(total_bit_count):
    for i in xrange(total_bit_count // 3):
        bits = random.getrandbits(3)
        yield 1 + bits * 6 // 8

1 : 832855
2 : 417835
3 : 416012
4 : 833888
5 : 416189
6 : 416554
total 3333333
max/min 2.00448063998

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

def wasteful_die(total_bit_count):
    for i in xrange(total_bit_count // 3):
        bits = random.getrandbits(3)
        if bits < 6:
            yield 1 + bits

1 : 417043
2 : 415812
3 : 417835
4 : 416012
5 : 416645
6 : 417243
total 2500590
max/min 1.00486517946

Последний пример принимает 13 бит за раз и генерирует из них 5 случайных чисел. Это генерирует даже больше чисел, чем наивный подход!

def optimized_die(total_bit_count):
    for i in xrange(total_bit_count // 13):
        bits = random.getrandbits(13)
        if bits < 6**5:
            for j in range(5):
                yield 1 + bits % 6
                bits //= 6

1 : 608776
2 : 608849
3 : 608387
4 : 608119
5 : 607855
6 : 608559
total 3650545
max/min 1.00163525841

Выбор 13 бит был сделан путем логарифмирования по основанию 6 степени 2 и выбора того, который ближе всего к целому числу.

def waste_list(n):
    for bit in range(1, 31):
        potential = math.log(2**bit, n)
        count = int(potential)
        if count > 0:
            waste = potential - count
            yield waste, bit, count

for waste, bit, count in sorted(waste_list(6)):
    print bit, count, waste
    if bit == 3:
        break

13 5 0.029086494049
26 10 0.0581729880981
8 3 0.0948224578763
21 8 0.123908951925
3 1 0.160558421704

Как видите, есть 4 варианта лучше, чем простые 3 бита.

person Mark Ransom    schedule 28.12.2013
comment
Извините за поздний ответ, но это кажется отличным подходом, так как мне нужно сгенерировать несколько значений. - person Eoin; 16.01.2014

Боюсь, что предлагаемый вами подход является предвзятым.

Предположим, генератор случайных чисел выдал числа от 0 до 255, а вы хотели получить случайное число в диапазоне от 0 до 254.

Если генератор случайных чисел выдает 255 (1111_1111 в двоичном формате), ваш подход будет отбрасывать один бит и добавлять еще один, пока в конечном итоге вы не получите число 254 (1111_1110 в двоичном формате). (Если я правильно понял ваш подход.)

Следовательно, ваши сгенерированные числа будут иметь вероятность 1/256 для каждого числа, кроме 254, вероятность которого будет равна 1/128.

person Peter de Rivaz    schedule 27.12.2013
comment
Вы, кажется, совершенно правы. Мне все еще интересно, существует ли альтернативный алгоритм. - person Eoin; 28.12.2013
comment
@Eoin, я считаю, что золотым стандартом является метод доктор Жак. Я потратил время на то, чтобы расширить свою теорию чисел (которая плоха), чтобы попытаться оптимизировать ее, и в конце концов связался с автором, чтобы обсудить мои выводы, но вместе мы не смогли доказать каких-либо ощутимых преимуществ, связанных с тем, что я придумал. . Просто максимизируйте N, чтобы максимизировать эффективность. - person sh1; 09.08.2019