Как я могу выбрать случайное значение между 0 и bigint?

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


Неадекватность моего текущего подхода

Теперь для обычных целых чисел я обычно пишу что-то вроде int rand 500; и делаю с ним.

Но для больших целых чисел это выглядит как rand для этого не предназначено.

Используя следующий код, я провел симуляцию 2 миллионов вызовов rand $bigint:

$ perl -Mbigint -E 'say int rand 1230138339199329632554990773929330319360000000 for 1 .. 2e6' > rand.txt

Распределение результирующего набора далеко не желательно:

  • 0 (56 counts)
  • величина 1e + 040 (112 кол-во)
  • величина 1e + 041 (1411 единиц)
  • величина 1e + 042 (14496 графов)
  • величина 1e + 043 (146324 графа)
  • величина 1e + 044 (1463824 counts)
  • величина 1e + 045 (373777 единиц)

Таким образом, процесс никогда не мог выбрать число, подобное 999 или 5e+020, что делает этот подход непригодным для того, что я хочу сделать.

Похоже, что это имеет какое-то отношение к произвольной точности rand, которая никогда не превышает 15 цифр в ходе моего тестирования:

$ perl -E 'printf "%.66g", rand'
0.307037353515625

Как я могу преодолеть это ограничение?

Моя первоначальная мысль заключается в том, что, возможно, есть способ влиять на точность rand, но он похож на бандаж на гораздо более серьезную проблему (т.е. неспособность rand обрабатывать большие целые числа).

В любом случае, я надеюсь, что кто-то пошел по этому пути раньше и знает, как исправить ситуацию.

Ответы

Ответ 1

(Преобразован из моего комментария)

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

псевдокод

  • Вычислить биты, необходимые для представления вашего номера: n_needed_bits
  • Проверьте размер битов, возвращаемых вашим PRNG: n_bits_prng
  • Рассчитайте количество необходимых образцов: needed_prng_samples = ceil(n_needed_bits / n_bits_prng)
  • Пока верно:
    • Пример needed_prng_samples (вызовы PRNG) и объединение всех полученных бит
    • Убедитесь, что результирующее число находится в пределах вашего диапазона.
    • Да?: возвратный номер (готовый)
    • Нет: ничего не делать (цикл продолжается, снова будет повторно отображать все компоненты!)

Примечания

  • Это форма прием-выборка/отбраковка-выборка
  • Подход алгоритм лас-вегаса: время выполнения не ограничено в теории
    • Количество необходимых циклов в среднем: n_possible-sample-numbers-of-full-concatenation / n_possible-sample-numbers-within-range
  • Полная передискретизация (если результат не находится в пределах диапазона) в соответствии с методом отклонения дает доступ к более формальному анализу без смещения/единообразия и является очень важным аспектом для этого подхода.
  • Конечно, для выполнения этой работы необходимы классические предположения в отношении вывода PRNG.
    • Если PRNG, например, имеет некоторую неравномерность в отношении младших бит/высоких бит (как это часто упоминается), это будет иметь эффект вывода выше

Ответ 2

Я искал эту проблему из-за неправильного угла

Буферы не одинакового размера. Каждый бит в 10 раз больше предыдущего. Чтобы представить это в перспективе, существует 10 000 возможных целых чисел по величине 1e+44 для каждого целого с величиной 1e+40.

Вероятность нахождения любого числа с величиной 1e+20 для bigint в 1e+45 меньше 0.00000 00000 00000 00000 001 %.

Забудьте об иглах в стоге сена, это больше похоже на поиск иглы в квазаре!

Ответ 3

Подход может состоять в том, чтобы вырезать строковое представление числа на куски, логическое ($ low) инициализировано false, а первые случайные ничьи равны верхней границе.

EDIT: добавлены некоторые пояснения после комментария

# first argument (in) upper bound
# second argument (in/out) is lower (false while random returns upper bound, after it remains true)
sub randhlp {
    my($upp)[email protected]_;
    my $l=length $upp;
    # random number less than
    # - upper bound if islower is false
    # - 9..99 otherwise
    my $x=int rand ($_[1] ? 10**$l : $upp+1);
    if ($x<$upp) {
        $_[1]=1;
    }
    # left padding with 0
    return sprintf("%0*d",$l,$x);
}

# returns a random number less than argument (numeric string)
sub randistr {
    my($n)[email protected]_;
    $n=~/^\d+$/ or die "invalid input not numeric";
    $n ne "0" or die "invalid input 0";
    my($low,$x);
    do {
        undef $x;
        # split string by chunks of 6 characters
        # except last chunk which has 1 to 6 characters
        while ($n=~/.{1,6}/g) {
            # concatenate random results
            $x.=randhlp($&,$low)
        }
    } while ($x eq $n);
    $x=~s/^0+//;
    return $x;
}

Тест

for ($i=0;$i<2e6;++$i) {
    $H{length(randistr("1230138339199329632554990773929330319360000000"))}+=1;
}

print "$_ $H{$_}\n" for sort keys %H;

Возвращает

39 4
40 61
41 153
42 1376
43 14592
44 146109
45 1463301
46 374404