Как я могу выбрать случайное значение между 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