Vielen Dank für all Ihre tollen Antworten. Am Ende habe ich die folgende Lösung gefunden, die ich mitteilen möchte.
Bevor ich in näher über das Wie und Warum gehen, hier ist die tl; dr : mein glänzendes neues Skript :-)
#!/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 ))
Speichern Sie das in ~/bin/rand
und Sie haben bei Ihrer Verfügbarkeit eine süße Zufallsfunktion in der Bash, die eine ganze Zahl in einem beliebigen Bereich abtasten kann. Der Bereich kann negative und positive ganze Zahlen enthalten und bis zu 2 60 -1 lang sein:
$ 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
Alle Ideen der anderen Antwortenden waren großartig. Die Antworten von terdon , JF Sebastian und jimmij verwendeten externe Tools, um die Aufgabe auf einfache und effiziente Weise zu erledigen. Ich bevorzuge jedoch eine echte bash-Lösung für maximale Portabilität und vielleicht ein bisschen, einfach aus Liebe zu bash;)
Ramesh 's und l0b0 ' s Antworten verwendet /dev/urandom
oder /dev/random
in Kombination mit od
. Das ist gut, aber ihre Ansätze hatten den Nachteil, dass sie nur zufällige ganze Zahlen im Bereich von 0 bis 2 8n -1 für einige n abtasten konnten, da diese Methode Bytes abtastet, dh Bitstrings der Länge 8. Dies sind ziemlich große Sprünge mit zunehmende n.
Schließlich beschreibt Falcos Antwort die allgemeine Idee, wie dies für beliebige Bereiche (nicht nur Zweierpotenzen) erfolgen könnte. Im Grunde genommen für einen bestimmten Bereich {0..max}
, können wir bestimmen , was die nächste Zweierpotenz ist, das heißt, genau wie viele Bits erforderlich sind , zu repräsentieren max
als Bitstring. Dann können wir nur so viele Bits abtasten und feststellen, ob diese Verzerrung als Ganzzahl größer als ist max
. Wenn ja, wiederholen. Da wir nur so viele Bits abtasten, wie zur Darstellung erforderlich sind max
, hat jede Iteration eine Wahrscheinlichkeit größer oder gleich 50% des Erfolgs (50% im schlimmsten Fall, 100% im besten Fall). Das ist also sehr effizient.
Mein Skript ist im Grunde eine konkrete Implementierung von Falcos Antwort, die in reinem Bash geschrieben und hocheffizient ist, da es die eingebauten bitweisen Operationen von Bash verwendet, um Bitstrings der gewünschten Länge abzutasten. Darüber hinaus wird eine Idee von Eliah Kagan$RANDOM
gewürdigt , die vorschlägt, die eingebaute Variable zu verwenden, indem Bitfolgen verkettet werden, die aus wiederholten Aufrufen von $RANDOM
. Ich habe tatsächlich sowohl die Verwendungsmöglichkeiten /dev/urandom
als auch implementiert $RANDOM
. Standardmäßig wird das obige Skript verwendet $RANDOM
. (Und ok, wenn /dev/urandom
wir od und tr brauchen , werden diese von POSIX unterstützt.)
Wie funktioniert es?
Bevor ich darauf eingehe, zwei Beobachtungen:
Es stellt sich heraus, dass bash keine Ganzzahlen größer als 2 63 -1 verarbeiten kann. Überzeugen Sie sich selbst:
$ echo $((2**63-1))
9223372036854775807
$ echo $((2**63))
-9223372036854775808
Es scheint, dass Bash intern signierte 64-Bit-Ganzzahlen verwendet, um Ganzzahlen zu speichern. Bei 2 63 "dreht sich alles um" und wir erhalten eine negative ganze Zahl. Wir können also nicht hoffen, mit einer beliebigen Zufallsfunktion einen größeren Bereich als 2 63 -1 zu erhalten. Bash kommt einfach nicht damit klar.
Wann immer wir einen Wert in einem beliebigen Bereich zwischen min
und max
mit abtasten möchten min != 0
, können wir einfach einen Wert zwischen 0
und max-min
abtasten und dann min
zum Endergebnis hinzufügen . Dies funktioniert auch , wenn min
und möglicherweise auch max
sind negativ , aber wir müssen vorsichtig sein , einen Wert zwischen Probe 0
und den absoluten Wert max-min
. Dann können wir uns darauf konzentrieren, wie ein zufälliger Wert zwischen 0
und einer beliebigen positiven ganzen Zahl abgetastet wird max
. Der Rest ist einfach.
Schritt 1: Bestimmen Sie, wie viele Bits benötigt werden, um eine Ganzzahl (den Logarithmus) darzustellen
Für einen bestimmten Wert max
möchten wir wissen, wie viele Bits erforderlich sind, um ihn als Bitfolge darzustellen. Dies ist so, dass wir später nur so viele Bits wie nötig zufällig abtasten können, was das Skript so effizient macht.
Wir werden sehen. Da mit n
Bits bis zum Wert 2 n -1 dargestellt werden kann, ist die Anzahl n
der Bits, die zur Darstellung eines beliebigen Werts erforderlich sind, die x
Obergrenze (log 2 (x + 1)). Wir brauchen also eine Funktion, um die Obergrenze eines Logarithmus zur Basis 2 zu berechnen. Es ist ziemlich selbsterklärend:
log2() {
local x=$1 n=1 l=0
while (( x>n && n>0 ))
do
let n*=2 l++
done
echo $l
}
Wir brauchen die Bedingung, n>0
damit die Schleife garantiert endet, wenn sie zu groß wird, sich umgibt und negativ wird.
Schritt 2: Wählen Sie eine zufällige Bitfolge mit einer Länge aus n
Die tragbarsten Ideen sind, entweder die eingebaute Variable von bash zu verwenden /dev/urandom
(oder auch /dev/random
wenn es einen starken Grund gibt) $RANDOM
. Schauen wir uns zuerst an, wie es geht $RANDOM
.
Option A: Verwenden von $RANDOM
Dies basiert auf der Idee von Eliah Kagan. Grundsätzlich $RANDOM
können wir , da eine 15-Bit-Ganzzahl abgetastet wird, $((RANDOM<<15|RANDOM))
eine 30-Bit-Ganzzahl abtasten. Verschieben Sie also einen ersten Aufruf $RANDOM
um 15 Bit nach links und wenden Sie einen bitweisen oder einen zweiten Aufruf an $RANDOM
, um zwei unabhängig abgetastete Bitstrings effektiv zu verketten (oder zumindest so unabhängig, wie es in bash integriert ist $RANDOM
).
Wir können dies wiederholen, um eine 45-Bit- oder 60-Bit-Ganzzahl zu erhalten. Nach dieser Bash kann es nicht mehr verarbeitet werden, aber dies bedeutet, dass wir leicht einen Zufallswert zwischen 0 und 2 60 -1 abtasten können. Um eine n-Bit-Ganzzahl abzutasten, wiederholen wir die Prozedur, bis unsere zufällige Bitfolge, deren Länge in 15-Bit-Schritten zunimmt, eine Länge größer oder gleich n hat. Schließlich schneiden wir die zu großen Bits ab, indem wir sie entsprechend bitweise nach rechts verschieben. Am Ende erhalten wir eine n-Bit-Zufallszahl.
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) ))
}
Option B: Verwenden von /dev/urandom
Alternativ können wir od
und verwenden /dev/urandom
, um eine n-Bit-Ganzzahl abzutasten. od
Bytes, dh Bitstrings der Länge 8, werden gelesen. Ähnlich wie bei der vorherigen Methode werden nur so viele Bytes abgetastet, dass die äquivalente Anzahl der abgetasteten Bits größer oder gleich n ist, und die zu großen Bits werden abgeschnitten.
Die niedrigste Anzahl von Bytes, die benötigt wird, um mindestens n Bits zu erhalten, ist das niedrigste Vielfache von 8, das größer oder gleich n ist, dh Floor ((n + 7) / 8).
Dies funktioniert nur mit 56-Bit-Ganzzahlen. Wenn Sie ein weiteres Byte abtasten, erhalten Sie eine 64-Bit-Ganzzahl, dh einen Wert bis zu 2 64 -1, den die Bash nicht verarbeiten kann.
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) ))
}
Zusammenfügen der Teile: Ermitteln Sie zufällige ganze Zahlen in beliebigen Bereichen
Wir können probieren n
jetzt -Bit bitstrings, aber wir wollen Probe ganze Zahlen in einem Bereich von 0
bis max
, gleichmäßig zufällig , wo max
willkürlich kann, die nicht unbedingt eine Zweierpotenz. (Wir können Modulo nicht verwenden, da dies zu einer Verzerrung führt.)
Der springende Punkt, warum wir uns so sehr bemüht haben, nur so viele Bits abzutasten, wie zur Darstellung des Werts erforderlich sind max
, ist, dass wir jetzt eine Schleife sicher (und effizient) zum wiederholten n
Abtasten eines Bitstrings mit -bit verwenden können, bis wir einen niedrigeren Wert abtasten oder gleich max
. Im ungünstigsten Fall ( max
Zweierpotenz) endet jede Iteration mit einer Wahrscheinlichkeit von 50%, und im besten Fall ( max
Zweierpotenz minus Eins) endet die erste Iteration mit Sicherheit.
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
}
Dinge einpacken
Schließlich wollen wir ganze Zahlen zwischen min
und max
, wo min
und max
kann beliebig sein, auch negativ abtasten. Wie bereits erwähnt, ist dies jetzt trivial.
Lassen Sie uns alles in ein Bash-Skript schreiben. Führe ein paar Argumente aus, die analysiert werden ... Wir wollen zwei Argumente min
und max
oder nur ein Argument max
, wobei der min
Standardwert ist 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
... und schließlich, um einen Wert zwischen min
und gleichmäßig zufällig abzutasten max
, tasten wir eine zufällige ganze Zahl zwischen 0
und dem absoluten Wert von max-min
und addieren min
zum Endergebnis. :-)
diff=$((max-min)) && diff=${diff#-}
echo $(( $(rand $diff) + min ))
Davon inspiriert , könnte ich versuchen, diesen PRNG mit Dieharder zu testen und zu vergleichen und meine Erkenntnisse hier einzubringen . :-)