Как эффективно генерировать большие, равномерно распределенные, случайные целые числа в bash?

30

Мне было интересно, что было бы лучшим способом получить хорошую случайность в bash, то есть, что было бы процедурой для получения случайного положительного целого числа между MINи MAXтаким, что

  1. Диапазон может быть сколь угодно большим (или, по крайней мере, скажем, до 2 32 с -1);
  2. Значения распределены равномерно (т. Е. Нет смещения);
  3. Это эффективно.

Эффективный способ получить случайность в bash - использовать $RANDOMпеременную. Однако это только выборка значения между 0 и 2 15 -1, которое может быть недостаточно большим для всех целей. Люди обычно используют по модулю, чтобы получить его в диапазоне, который они хотят, например,

MIN=0
MAX=12345
rnd=$(( $RANDOM % ($MAX + 1 - $MIN) + $MIN ))

Это, кроме того, создает смещение, если только не $MAXпроизойдет деление 2 15 -1 = 32767. Например, если $MIN0 и $MAX9, то значения от 0 до 7 немного более вероятны, чем значения 8 и 9, поскольку $RANDOMникогда не будут 32768 или 32769. Это смещение ухудшается с увеличением диапазона, например, если $MIN0 и $MAXравно 9999, а затем цифры от 0 до 2767 имеют вероятность 4 / 32767 , в то время как число 2768 до 9999 есть только вероятность 3 / 32767 .

Таким образом, хотя вышеуказанный метод удовлетворяет условию 3, он не удовлетворяет условиям 1 и 2.

Наилучший метод, который я до сих пор придумывал, пытаясь удовлетворить условия 1 и 2, заключался /dev/urandomв следующем:

MIN=0
MAX=1234567890
while
  rnd=$(cat /dev/urandom | tr -dc 0-9 | fold -w${#MAX} | head -1 | sed 's/^0*//;')
  [ -z $rnd ] && rnd=0
  (( $rnd < $MIN || $rnd > $MAX ))
do :
done

В основном, просто собирать случайность из /dev/urandom(можно рассмотреть использование /dev/randomвместо этого, если требуется криптографически сильный генератор псевдослучайных чисел, и если у вас много времени, или, может быть, аппаратный генератор случайных чисел), удалите каждый символ, который не является десятичной цифрой, сложите выводим на длину $MAXи режем ведущие 0. Если нам довелось получить только 0, то $rndпусто, поэтому в этом случае установите rndзначение 0. Проверьте, находится ли результат за пределами нашего диапазона, и если да, то повторите. Я ввел здесь «тело» цикла while в защитное устройство, чтобы вызвать выполнение тела хотя бы один раз, в духе эмуляции do ... whileцикла, поскольку rndдля начала не определено.

Я думаю, что я выполнил условия 1 и 2 здесь, но теперь я испортил условие 3. Это довольно медленно. Занимает до секунды или около того (десятой секунды, когда мне повезет). На самом деле, цикл даже не гарантированно завершается (хотя вероятность завершения сходится к 1 с увеличением времени).

Есть ли эффективный способ получить непредвзятые случайные целые числа, в заранее заданном и потенциально большом диапазоне, в bash? (Я продолжу исследовать, когда позволит время, но в то же время я подумал, что кто-то здесь может иметь классную идею!)

Таблица ответов

  1. Самая основная (и, следовательно, переносимая) идея состоит в том, чтобы генерировать случайную цепочку битов достаточно долго. Существуют разные способы генерирования случайной цепочки битов, либо используя встроенную $RANDOMпеременную bash, либо используя odи /dev/urandom(или /dev/random). Если случайное число больше чем $MAX, начните сначала.

  2. В качестве альтернативы, можно использовать внешние инструменты.

    • Решение Perl
      • Pro: довольно портативный, простой, гибкий
      • Против: не для очень больших чисел выше 2 32 -1
    • Решение Python
      • Pro: просто, гибко, работает даже для большого количества
      • Против: менее портативный
    • Zsh решение
      • Pro: хорошо для людей, которые используют Zsh в любом случае
      • Против: вероятно, даже менее портативный
Malte Skoruppa
источник
Зачем выбирать только целые числа вместо кодировки base64 случайных битов, а затем преобразовывать определенное количество символов (в зависимости от необходимого диапазона) из закодированной формы в base10 из base64?
Муру
Это должно быть Баш? Будет ли что-то подобное rand=$(command)сделать, если commandвернет итератор, который удовлетворяет вашим требованиям?
Тердон
@muru На самом деле это хорошая идея. Я немного подумал о подобной идее, используя dd if=/dev/urandom 2>/dev/nullи пропуская ее od -t d(избегая обхода через base64), но мне не ясно, как происходит преобразование и действительно ли оно непредвзято. Если вы сможете расширить свою идею до эффективного, работающего сценария и объяснить, почему нет предвзятости, это послужит хорошим ответом. :)
Malte Skoruppa
@terdon Я бы предпочел Баш. Я имею в виду, конечно, вы можете использовать pythonили perlили ваш любимый язык, но это не везде доступно. Я бы предпочел что-то более портативное. Ну, awkслучайная функция была бы в порядке, я думаю. Но чем более портативным, тем лучше :)
Malte Skoruppa
2
Да, я думал вдоль линий perl -e 'print int(rand(2**32-1))');. Это чертовски портативно и будет очень быстро. Awk не будет сокращать его, так как большинство реализаций начинаются с одного и того же семени. Таким образом, вы получаете то же случайное число при последующих запусках. Это только изменяется в пределах того же самого запуска.
Тердон

Ответы:

17

Я вижу другой интересный метод отсюда .

rand=$(openssl rand 4 | od -DAn)

Этот также, кажется, хороший вариант. Он читает 4 байта из случайного устройства и форматирует их как целое число без знака между 0и 2^32-1.

rand=$(od -N 4 -t uL -An /dev/urandom | tr -d " ")
Рамеш
источник
7
вы должны использовать, /dev/urandomесли вы не знаете, что вам нужно/dev/random ; /dev/randomблоки на линуксе.
JFS
почему odкоманды разные. Оба просто выводят 4-байтовые целые числа без знака: 1-й - из openssl, 2-й - из /dev/random.
JFS
1
@Ramesh Я редактировал, чтобы использовать /dev/urandomвместо /dev/random- я не вижу смысла использовать /dev/random, и это может быть очень дорогим / медленным или замедлять работу других частей системы. (Не стесняйтесь, отредактируйте назад и объясните, действительно ли это необходимо.)
Volker Siegel
1
Не беспокойтесь, это действительно удивительно, что эта простая разница имеет столь сложные эффекты. Вот почему я настаивал на том, чтобы сменить пример на правильный - люди учатся на примерах.
Фолькер Сигел
1
@MalteSkoruppa: Iозначает, sizeof(int)что может быть меньше, чем 4в принципе. Кстати, od -DAnне удается, (2**32-1)но od -N4 -tu4 -Anпродолжает работать.
JFS
8

Спасибо всем за все ваши отличные ответы. Я получил следующее решение, которым я хотел бы поделиться.

Прежде чем углубляться в подробности о том, почему и как, вот tl; dr : мой новый блестящий сценарий :-)

#!/usr/bin/env bash
#
# Generates a random integer in a given range

# computes the ceiling of log2
# i.e., for parameter x returns the lowest integer l such that 2**l >= x
log2() {
  local x=$1 n=1 l=0
  while (( x>n && n>0 ))
  do
    let n*=2 l++
  done
  echo $l
}

# uses $RANDOM to generate an n-bit random bitstring uniformly at random
#  (if we assume $RANDOM is uniformly distributed)
# takes the length n of the bitstring as parameter, n can be up to 60 bits
get_n_rand_bits() {
  local n=$1 rnd=$RANDOM rnd_bitlen=15
  while (( rnd_bitlen < n ))
  do
    rnd=$(( rnd<<15|$RANDOM ))
    let rnd_bitlen+=15
  done
  echo $(( rnd>>(rnd_bitlen-n) ))
}

# alternative implementation of get_n_rand_bits:
# uses /dev/urandom to generate an n-bit random bitstring uniformly at random
#  (if we assume /dev/urandom is uniformly distributed)
# takes the length n of the bitstring as parameter, n can be up to 56 bits
get_n_rand_bits_alt() {
  local n=$1
  local nb_bytes=$(( (n+7)/8 ))
  local rnd=$(od --read-bytes=$nb_bytes --address-radix=n --format=uL /dev/urandom | tr --delete " ")
  echo $(( rnd>>(nb_bytes*8-n) ))
}

# for parameter max, generates an integer in the range {0..max} uniformly at random
# max can be an arbitrary integer, needs not be a power of 2
rand() {
  local rnd max=$1
  # get number of bits needed to represent $max
  local bitlen=$(log2 $((max+1)))
  while
    # could use get_n_rand_bits_alt instead if /dev/urandom is preferred over $RANDOM
    rnd=$(get_n_rand_bits $bitlen)
    (( rnd > max ))
  do :
  done
  echo $rnd
}

# MAIN SCRIPT

# check number of parameters
if (( $# != 1 && $# != 2 ))
then
  cat <<EOF 1>&2
Usage: $(basename $0) [min] max

Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1  
EOF
  exit 1
fi

# If we have one parameter, set min to 0 and max to $1
# If we have two parameters, set min to $1 and max to $2
max=0
while (( $# > 0 ))
do
  min=$max
  max=$1
  shift
done

# ensure that min <= max
if (( min > max ))
then
  echo "$(basename $0): error: min is greater than max" 1>&2
  exit 1
fi

# need absolute value of diff since min (and also max) may be negative
diff=$((max-min)) && diff=${diff#-}

echo $(( $(rand $diff) + min ))

Сохраните это, ~/bin/randи в вашем распоряжении будет приятная случайная функция в bash, которая может выбирать целое число в заданном произвольном диапазоне. Диапазон может содержать отрицательные и положительные целые числа и может быть длиной до 2 60 -1:

$ rand 
Usage: rand [min] max

Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1  
$ rand 1 10
9
$ rand -43543 -124
-15757
$ rand -3 3
1
$ for i in {0..9}; do rand $((2**60-1)); done
777148045699177620
456074454250332606
95080022501817128
993412753202315192
527158971491831964
336543936737015986
1034537273675883580
127413814010621078
758532158881427336
924637728863691573

Все идеи других ответчиков были великолепны. В ответах Тердона , Дж. Ф. Себастьяна и Джиммия использовались внешние инструменты для простого и эффективного выполнения задачи. Тем не менее, я предпочел настоящее решение bash для максимальной переносимости, и, возможно, немного, просто из любви к bash;)

Ответы Рамеша и l0b0 используются /dev/urandomили /dev/randomв сочетании с od. Это хорошо, однако, их подходы имели тот недостаток, что они могли выбирать случайные целые числа в диапазоне от 0 до 2 8n -1 для некоторого n, поскольку этот метод выбирает байты, то есть цепочки битов длины 8. Это довольно большие переходы с увеличивая п.

Наконец, ответ Фалько описывает общую идею, как это можно сделать для произвольных диапазонов (не только степеней двойки). По сути, для данного диапазона {0..max}мы можем определить, какова следующая степень двух, т. Е. Сколько именно битов требуется представить maxв виде цепочки битов. Затем мы можем сэмплировать столько битов и посмотреть, больше ли это число в виде целого числа max. Если так, повторите. Поскольку мы выбираем столько битов, сколько требуется для представления max, каждая итерация имеет вероятность, превышающую или равную 50% успеха (50% в худшем случае, 100% в лучшем случае). Так что это очень эффективно.

Мой сценарий, в основном, представляет собой конкретную реализацию ответа Falco, написанную на чистом bash и очень эффективную, поскольку он использует встроенные побитовые операции bash для выборки цепочек битов желаемой длины. Кроме того, она поддерживает идею Элии Кагана, которая предлагает использовать встроенную $RANDOMпеременную путем объединения цепочек битов, возникающих в результате повторных вызовов $RANDOM. Я фактически реализовал обе возможности /dev/urandomи $RANDOM. По умолчанию вышеуказанный скрипт использует $RANDOM. (И хорошо, если /dev/urandomмы используем od и tr , но они поддерживаются POSIX.)

Итак, как это работает?

Прежде чем я углублюсь в это, два замечания:

  1. Оказывается, bash не может обрабатывать целые числа больше 2 63 -1. Посмотреть на себя:

    $ echo $((2**63-1))
    9223372036854775807
    $ echo $((2**63))
    -9223372036854775808

    Казалось бы, bash внутренне использует 64-битные целые числа со знаком для хранения целых чисел. Таким образом, в 2 63 оно «оборачивается», и мы получаем отрицательное целое число. Поэтому мы не можем надеяться получить какой-либо диапазон больше, чем 2 63 с какой-либо случайной функцией, которую мы используем Баш просто не может с этим справиться.

  2. Всякий раз, когда мы хотим выбрать значение в произвольном диапазоне между minи maxс возможно min != 0, мы можем просто выбрать значение между 0и max-minвместо, а затем добавить minк конечному результату. Это работает , даже если minи возможно , также maxявляются отрицательными , но мы должны быть осторожны , чтобы попробовать значение между 0и абсолютное значение max-min . Итак, мы можем сосредоточиться на том, как выбрать случайное значение между 0произвольным положительным целым числом max. Остальное легко.

Шаг 1: Определите, сколько битов необходимо для представления целого числа (логарифм)

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

Посмотрим. Поскольку с nбитами мы можем представить до значения 2 n -1, то число nбитов, необходимое для представления произвольного значения, xявляется максимальным (log 2 (x + 1)). Итак, нам нужна функция для вычисления потолка логарифма к основанию 2. Это довольно очевидно:

log2() {
  local x=$1 n=1 l=0
  while (( x>n && n>0 ))
  do
    let n*=2 l++
  done
  echo $l
}

Нам нужно условие, n>0поэтому, если оно слишком велико, оборачивается и становится отрицательным, цикл гарантированно завершится.

Шаг 2: выборка случайной строки длины n

Наиболее переносимые идеи - использовать /dev/urandom(или даже /dev/randomесли есть веские причины) встроенную $RANDOMпеременную bash . Давайте $RANDOMсначала посмотрим, как это сделать .

Вариант А: Использование $RANDOM

Здесь используется идея, упомянутая Элией Каганом. По сути, поскольку выборка $RANDOM15-разрядного целого числа, мы можем использовать $((RANDOM<<15|RANDOM))для выборки 30-разрядного целого числа. Это означает, что сдвиньте первый вызов $RANDOMна 15 бит влево и примените побитовый или со вторым вызовом $RANDOM, эффективно объединяя две независимо выбранные цепочки битов (или, по крайней мере, столь же независимые, как встроенная команда bash $RANDOM).

Мы можем повторить это, чтобы получить 45-битное или 60-битное целое число. После этого bash больше не может это обрабатывать, но это означает, что мы можем легко выбрать случайное значение между 0 и 2 60 -1. Итак, для выборки n-битного целого числа мы повторяем процедуру до тех пор, пока наша случайная цепочка битов, длина которой увеличивается с шагом 15 бит, не будет иметь длину, большую или равную n. Наконец, мы обрезаем слишком много битов путем соответствующего побитового сдвига вправо, и в итоге получаем n-битное случайное целое число.

get_n_rand_bits() {
  local n=$1 rnd=$RANDOM rnd_bitlen=15
  while (( rnd_bitlen < n ))
  do
    rnd=$(( rnd<<15|$RANDOM ))
    let rnd_bitlen+=15
  done
  echo $(( rnd>>(rnd_bitlen-n) ))
}

Вариант Б: Использование /dev/urandom

В качестве альтернативы мы можем использовать odи /dev/urandomдля выборки n-битного целого числа. odбудет считывать байты, т. е. битовые строки длиной 8. Как и в предыдущем методе, мы выбираем столько байтов, что эквивалентное количество дискретных битов больше или равно n, и обрезаем слишком большие биты.

Наименьшее количество байтов, необходимое для получения по меньшей мере n битов, является наименьшим кратным 8, которое больше или равно n, т. Е. Floor ((n + 7) / 8).

Это работает только до 56-битных целых чисел. Выборка еще одного байта дала бы нам 64-битное целое число, то есть значение до 2 64 с -1, которое bash не может обработать.

get_n_rand_bits_alt() {
  local n=$1
  local nb_bytes=$(( (n+7)/8 ))
  local rnd=$(od --read-bytes=$nb_bytes --address-radix=n --format=uL /dev/urandom | tr --delete " ")
  echo $(( rnd>>(nb_bytes*8-n) ))
}

Соединение частей: получить случайные целые числа в произвольных диапазонах

Теперь мы можем сэмплировать nбитовые строки -бит, но мы хотим сэмплировать целые числа в диапазоне от 0до max, равномерно случайным образом , где maxможет быть произвольным, а не обязательно степенью двойки. (Мы не можем использовать по модулю, поскольку это создает уклон.)

Весь смысл, почему мы так старались собрать столько битов, сколько необходимо для представления значения max, заключается в том, что теперь мы можем безопасно (и эффективно) использовать цикл для многократной выборки n-битной цепочки битов, пока мы не выберем значение, которое меньше или равно max. В худшем случае ( maxэто степень двойки) каждая итерация заканчивается с вероятностью 50%, а в лучшем случае ( maxэто степень два минус один) первая итерация завершается с уверенностью.

rand() {
  local rnd max=$1
  # get number of bits needed to represent $max
  local bitlen=$(log2 $((max+1)))
  while
    # could use get_n_rand_bits_alt instead if /dev/urandom is preferred over $RANDOM
    rnd=$(get_n_rand_bits $bitlen)
    (( rnd > max ))
  do :
  done
  echo $rnd
}

Завершение вещей

Наконец, мы хотим выбрать целые числа между minи max, где minи maxможет быть произвольным, даже отрицательным. Как упоминалось ранее, это теперь тривиально.

Давайте поместим все это в сценарий bash. Делаем какие-то аргументы при разборе ... Нам нужны два аргумента minи max, или только один аргумент max, где по minумолчанию 0.

# check number of parameters
if (( $# != 1 && $# != 2 ))
then
  cat <<EOF 1>&2
Usage: $(basename $0) [min] max

Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1  
EOF
  exit 1
fi

# If we have one parameter, set min to 0 and max to $1
# If we have two parameters, set min to $1 and max to $2
max=0
while (( $# > 0 ))
do
  min=$max
  max=$1
  shift
done

# ensure that min <= max
if (( min > max ))
then
  echo "$(basename $0): error: min is greater than max" 1>&2
  exit 1
fi

... и, наконец, для выборки случайным образом значения между minи maxмы выбираем случайное целое число между 0и абсолютным значением max-minи добавляем minк конечному результату. :-)

diff=$((max-min)) && diff=${diff#-}

echo $(( $(rand $diff) + min ))

Вдохновленный этим , я мог бы попытаться использовать dieharder для тестирования и тестирования этого PRNG и выложить свои результаты здесь. :-)

Malte Skoruppa
источник
ваше решение предполагает, что sizeof(int) == 8(64 бита) из-за--format=u
jfs
1
Ваше решение напоминает мне, как написано random.py. random.Randomкласс использует 53бит? Генератор для возврата произвольных больших случайных чисел (несколько вызовов), random.SystemRandomделает то же самое с помощью, os.urandom()что может быть реализовано с помощью /dev/urandom.
Jfs
uL подразумевает sizeof (long)> = 8 для диапазона. Это не гарантировано. Вы можете использовать u8, чтобы утверждать, что платформа имеет такое целое число.
Jfs
@JFSebastian Я думал, что до сих пор мой скрипт не жестко кодировал какие-либо предположения о размере длинного целого. Потенциально это будет работать, даже если размер длинного целого со знаком был больше (или меньше), чем 64 бита, например, 128 бит. Однако, если я использую, --format=u8то я жестко закодирую предположение sizeof(int)==8. С другой стороны, при использовании --format=uLпроблем нет: я не думаю, что есть платформа, которая имеет 64-битные целые числа, но все же определяет длинные целые как нечто более низкое. Так что в принципе я бы сказал, что это --format=uLобеспечивает большую гибкость. о чем ты думаешь?
Malte Skoruppa
есть , long longчто может быть 64bit в то время как INT = длинный = 32bit на некоторых платформах. Вы не должны претендовать на диапазон 0..2 ** 60, если вы не можете гарантировать его на всех платформах. С другой стороны, bash может не поддерживать сам этот диапазон на таких платформах (я не знаю, возможно, он использует maxint_t, и тогда u8 более корректен, если вы хотите установить фиксированный диапазон ( odне поддерживает указание maxint, если ваш диапазон какой бы bash не зависел от платформы? range:). Если диапазон bash зависит от sizeof long, тогда uL может быть более подходящим). Вы хотите полный диапазон, который поддерживает bash на всех ОС или фиксированный диапазон?
JFS
6

Это может быть Zsh?

max=1000
integer rnd=$(( $(( rand48() )) * $max ))

Вы можете также использовать семена с rand48(seed). Смотрите man zshmodulesи man 3 erand48подробное описание, если интересно.

jimmij
источник
Лично я не пользуюсь zsh, но это отличное дополнение :)
Malte Skoruppa
5
$ python -c 'import random as R; print(R.randint(-3, 5**1234))'

python доступно в Redhat, системах на основе Debian.

JFS
источник
+1 Ах, наряду с решением на perl, должно было быть решение на python. Спасибо :)
Malte Skoruppa
5

Если вам нужно число от 0 до (2 ^ n) -1, где n mod 8 = 0, вы можете просто получить n / 8 байт /dev/random. Например, чтобы получить десятичное представление случайного числа, intвы можете:

od --read-bytes=4 --address-radix=n --format=u4 /dev/random | awk '{print $1}'

Если вы хотите взять только n битов, вы можете сначала взять предельные (n / 8) байты и сдвинуть вправо на требуемую величину. Например, если вы хотите 15 бит:

echo $(($(od --read-bytes=2 --address-radix=n --format=u4 /dev/random | awk '{print $1}') >> 1))

Если вы абсолютно уверены, что вас не волнует качество случайности, и вы хотите гарантировать минимальное время выполнения, которое вы можете использовать /dev/urandomвместо /dev/random. Убедитесь, что вы знаете, что делаете, прежде чем использовать /dev/urandom!

l0b0
источник
Спасибо. Таким образом, получить nслучайные байты из /dev/urandomи отформатировать, используя od. По духу похоже на этот ответ . Оба одинаково хороши :) Хотя оба имеют недостаток: фиксированный диапазон от 0 до 2 ^ (n * 8) -1 бит, где n - количество байтов. Я бы предпочел метод для произвольного диапазона, до 2 ^ 32-1, но также и для любого более низкого. Это создает сложность смещения.
Malte Skoruppa
Отредактировано для использования /dev/urandomвместо /dev/random- я не вижу смысла использовать /dev/random, и это может быть очень дорогим / медленным или замедлять работу других частей системы. (Не стесняйтесь, отредактируйте назад и объясните, действительно ли это необходимо.)
Volker Siegel
Это должно быть с точностью до наоборот: используйте / dev / urandom, если вы не знаете, что вам нужен / dev / random . Неверно полагать, что /dev/urandomрезультаты намного хуже, чем /dev/randomто, что урандом в большинстве случаев неприменимо. Один раз /dev/urandomинициализируется (при запуске системы); его результаты так же хороши, как и /dev/randomдля почти всех приложений в Linux. В некоторых системах случайное и случайное совпадают.
JFS
1
--format=uследует заменить, --format=u4потому что sizeof(int)может быть меньше, чем 4в теории.
Jfs
@JFSebastian В этой статье очень интересная дискуссия на эту тему. Их вывод кажется , что оба /dev/randomи /dev/urandomявляются неудовлетворительными, и что «Linux следует добавить безопасный ГСЧ , который блокирует до него накопилось достаточное семян энтропию , а затем ведет себя как urandom
10
3

Если вы не возражаете против использования внешних инструментов, это должно соответствовать вашим требованиям:

rand=$(perl -e 'print int(rand(2**32-1))'); 

Он использует randфункцию Perl, которая принимает верхний предел в качестве параметра. Вы можете установить его на то, что вам нравится. То, насколько это близко к истинной случайности в абстрактном математическом определении, выходит за рамки этого сайта, но все должно быть хорошо, если вам не нужно это для чрезвычайно чувствительного шифрования или тому подобного. Возможно, даже там, но я не буду рисковать мнением.

Тердон
источник
это ломается для больших чисел, например, 5 ** 1234
JFS
1
@JFSebastian да, это так. Я разместил это, так как ОП указал, 1^32-1но вам нужно настроить его для больших чисел.
Тердон
2

Вы должны получить ближайшее (2 ^ X) -1, равное или большее, чем желаемый максимум, и получить число битов. Затем просто вызовите / dev / random несколько раз и добавьте все биты вместе, пока у вас не будет достаточно, обрезая все биты, которых слишком много. Если полученное число больше вашего максимального повтора. В худшем случае у вас больше 50% шансов получить случайное число ниже вашего максимума, поэтому (в этом худшем случае) вы в среднем совершите два звонка.

Falco
источник
На самом деле это очень хорошая идея для повышения эффективности. Ответ Рамеша и ответ l0b0 в обоих основном получают случайные биты из /dev/urandom, но в обоих ответах это всегда кратна 8 бит. Обрезание битов, которые слишком велики для более низких диапазонов, перед форматированием до десятичного с, odявляется хорошей идеей для повышения эффективности, поскольку, как вы хорошо объясните, цикл имеет только ожидаемое количество из 2 итераций. Это, в сочетании с одним из упомянутых ответов, вероятно, будет правильным решением.
Malte Skoruppa
0

Ваш ответ интересный, но довольно длинный.

Если вы хотите произвольно большие числа, вы можете объединить несколько случайных чисел в помощник:

# $1 - number of 'digits' of size base
function random_helper()
{
  base=32768
  random=0
  for((i=0; i<$1; ++i)); do
    let "random+=$RANDOM*($base**$i)"
  done
  echo $random
}

Если проблема в предвзятости, то просто удалите ее.

# $1 - min value wanted
# $2 - max value wanted
function random()
{
  MAX=32767
  min=$1
  max=$(($2+1))
  size=$((max-min))
  bias_range=$((MAX/size))
  while
    random=$RANDOM
  [ $((random/size)) -eq $bias_range ]; do :; done
  echo $((random%size+min))
}

Соединение этих функций вместе

# $1 - min value wanted
# $2 - max value wanted
# $3 - number of 'digits' of size base
function random()
{
  base=32768
  MAX=$((base**$3-1))
  min=$1
  max=$(($2+1))
  size=$((max-min))
  bias_range=$((MAX/size))
  while
    random=$(random_helper)
  [ $((random/size)) -eq $bias_range ]; do :; done
  echo $((random%size+min))
}
Адриан
источник