วิธีการสร้างจำนวนเต็มสุ่มขนาดใหญ่กระจายอย่างสม่ำเสมอในทุบตี?


30

ฉันสงสัยว่าสิ่งใดจะเป็นวิธีที่ดีที่สุดที่จะได้แบบแผนที่ดีในการทุบตีนั่นคือขั้นตอนในการรับจำนวนเต็มบวกแบบสุ่มระหว่าง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 เช่นถ้า$MINเป็น 0 และ$MAX9 แล้วค่า 0 ถึง 7 เล็กน้อยน่าจะมากกว่าค่าที่ 8 และ 9 เป็น$RANDOMจะไม่เป็น 32768 หรือ 32769. อคตินี้ได้รับเลวร้ายยิ่งเป็นช่วงเพิ่มขึ้นเช่นถ้า$MINเป็น 0 และ$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แทนถ้าเครื่องกำเนิดไฟฟ้าจำนวน pseudorandom แข็งแกร่งเข้ารหัสเป็นที่ต้องการและถ้าคุณมีจำนวนมากของเวลาหรืออื่น ๆ ที่อาจจะเป็นฮาร์ดแวร์เครื่องกำเนิดไฟฟ้าจำนวนสุ่ม) ลบตัวอักษรที่ไม่ได้เป็นหลักทศนิยมทุกพับ ผลลัพธ์ตามความยาว$MAXและตัดส่วนนำ 0 ของ ถ้าเราเกิดขึ้นจะได้รับเพียง 0 จากนั้นก็$rndเป็นที่ว่างเปล่าดังนั้นในกรณีชุดนี้ไปrnd 0ตรวจสอบว่าผลลัพธ์อยู่นอกช่วงที่เรากำหนดหรือไม่ถ้าใช่ให้ทำซ้ำ ฉันบังคับให้ "ร่างกาย" ในขณะที่วนเข้าไปในยามที่นี่เพื่อบังคับให้ดำเนินการของร่างกายอย่างน้อยหนึ่งครั้งในจิตวิญญาณของการเลียนแบบdo ... whileห่วงเนื่องจากrndไม่ได้กำหนดที่จะเริ่มต้นด้วย

ฉันคิดว่าฉันได้ปฏิบัติตามเงื่อนไขที่ 1 และ 2 ที่นี่ แต่ตอนนี้ฉันเมาแล้วเงื่อนไขที่ 3 มันค่อนข้างช้า ใช้เวลาประมาณวินาทีหรือมากกว่านั้น (สิบวินาทีเมื่อฉันโชคดี) ที่จริงแล้วการวนซ้ำนั้นไม่ได้รับประกันว่าจะยุติ (แม้ว่าความน่าจะเป็นของการเลิกจ้างจะแปรเป็น 1 เมื่อเวลาเพิ่มขึ้น)

มีวิธีที่มีประสิทธิภาพในการรับจำนวนเต็มแบบสุ่มที่ไม่เอนเอียงภายในช่วงที่กำหนดไว้ล่วงหน้าและอาจมีขนาดใหญ่ในทุบตี? (ฉันจะตรวจสอบต่อไปเมื่อถึงเวลา แต่ในขณะเดียวกันฉันคิดว่ามีคนที่นี่อาจมีความคิดที่ยอดเยี่ยม!)

สารบัญ

  1. แนวคิดพื้นฐานที่สุด (และพกพาได้) คือการสร้างบิตเร็ตแบบสุ่มที่ยาวพอ มีหลายวิธีในการสร้างบิตสตริงแบบสุ่มโดยใช้$RANDOMตัวแปรในตัวของ bash หรือการใช้odและ/dev/urandom(หรือ/dev/random) หากตัวเลขสุ่มมากกว่า$MAXเริ่มต้นใหม่

  2. หรืออาจใช้เครื่องมือภายนอก

    • วิธีการแก้ปัญหา Perl
      • Pro: ค่อนข้างพกพาง่ายยืดหยุ่นได้
      • Contra: ไม่ใช่สำหรับตัวเลขที่สูงกว่า 2 32 -1
    • โซลูชัน Python
      • Pro: เรียบง่ายยืดหยุ่นได้ผลแม้แต่กับคนจำนวนมาก
      • ในทางตรงกันข้าม: พกพาน้อย
    • โซลูชัน zsh
      • Pro: ดีสำหรับคนที่ใช้ zsh อยู่ดี
      • ในทางตรงกันข้าม: อาจพกพาได้น้อยลง

เหตุใดจึงเลือกเฉพาะจำนวนเต็มแทนการเข้ารหัส base64 บิตแบบสุ่มจากนั้นจึงแปลงอักขระจำนวนหนึ่ง (ขึ้นอยู่กับช่วงที่ต้องการ) จากแบบฟอร์มที่เข้ารหัสเป็นเบส 10 จาก base64
muru

มันไม่จำเป็นที่จะทุบตี? ฉันจะrand=$(command)ทำอย่างไรถ้าcommandส่งคืน iteger ที่ตอบสนองความต้องการของคุณ?
terdon

@muru มันเป็นความคิดที่ดีจริงๆ ฉันใช้ความคิดบางอย่างเกี่ยวกับความคิดที่คล้ายกันการใช้dd if=/dev/urandom 2>/dev/nullและการส่งผ่านod -t d(หลีกเลี่ยงทางอ้อมผ่าน base64) แต่ก็ไม่ชัดเจนสำหรับฉันว่าการแปลงเกิดขึ้นได้อย่างไร หากคุณสามารถขยายความคิดของคุณไปสู่สคริปต์ทำงานที่มีประสิทธิภาพและอธิบายว่าทำไมไม่มีอคติก็จะทำให้ได้คำตอบที่ดี :)
Malte Skoruppa

@terdon ฉันต้องการทุบตี ฉันหมายถึงแน่นอนว่าคุณสามารถเรียกpythonหรือperlหรือภาษาที่คุณชื่นชอบ แต่นี่ไม่สามารถใช้ได้ทุกที่ ฉันต้องการบางสิ่งที่พกพาได้มากกว่า ดีawk's ฟังก์ชั่นแบบสุ่มจะดีผมคิดว่า แต่พกพามากขึ้นดีกว่า :)
Malte Skoruppa

2
perl -e 'print int(rand(2**32-1))');ใช่ผมคิดตามสายของ พกพาไปได้สวยและจะเร็วมาก Awk จะไม่ตัดมันเนื่องจากการใช้งานส่วนใหญ่เริ่มต้นจากเมล็ดเดียวกัน ดังนั้นคุณจะได้รับหมายเลขสุ่มเดียวกันในการวิ่งครั้งต่อไป มันเปลี่ยนแปลงภายในการรันเดียวกันเท่านั้น
terdon

คำตอบ:


17

ผมเห็นวิธีการที่น่าสนใจอื่น ๆ ได้จากที่นี่

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

อันนี้ก็ดูเหมือนจะเป็นตัวเลือกที่ดี มันอ่าน 4 ไบต์จากอุปกรณ์สุ่มและรูปแบบพวกเขาเป็นจำนวนเต็มไม่ได้ลงนามระหว่างและ02^32-1

rand=$(od -N 4 -t uL -An /dev/urandom | tr -d " ")


ทำไมodคำสั่งต่างกัน ทั้งสองเพียงแค่พิมพ์ 4 ไบต์จำนวนเต็มไม่ได้ลงนาม: วันที่ 1 - จาก OpenSSL ที่ 2 - /dev/randomจาก
jfs

1
@Ramesh ฉันแก้ไขให้ใช้/dev/urandomแทน/dev/random- ฉันไม่เห็นเหตุผลที่จะใช้/dev/randomและอาจมีราคาแพง / ช้าหรือช้าลงส่วนอื่น ๆ ของระบบ (อย่าลังเลที่จะแก้ไขและอธิบายหากจำเป็นจริงๆ)
Volker Siegel

1
ไม่ต้องกังวลมันแปลกใจจริงๆที่ความแตกต่างที่เรียบง่ายนี้มีเอฟเฟกต์ที่ซับซ้อนมาก นั่นเป็นเหตุผลที่ฉันยืนยันว่าจะเปลี่ยนตัวอย่างเป็นตัวอย่างที่ถูกต้อง - ผู้คนเรียนรู้จากตัวอย่าง
Volker Siegel

1
@MalteSkoruppa: Iย่อมาจากsizeof(int)อาจจะน้อยกว่า4ในหลักการ btw od -DAnล้มเหลว(2**32-1)แต่od -N4 -tu4 -Anยังคงทำงานต่อไป
jfs

8

ขอบคุณทุกคำตอบที่ดี ฉันลงเอยด้วยวิธีแก้ปัญหาต่อไปนี้ซึ่งฉันต้องการแบ่งปัน

ก่อนที่ฉันจะพูดถึงรายละเอียดเพิ่มเติมเกี่ยวกับ whys and hows นี่คือ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

ความคิดทั้งหมดโดยผู้ตอบคนอื่นดีมาก คำตอบของterdon , JF Sebastianและjimmijใช้เครื่องมือภายนอกเพื่อทำงานในลักษณะที่เรียบง่ายและมีประสิทธิภาพ อย่างไรก็ตามฉันต้องการโซลูชันทุบตีที่แท้จริงสำหรับการพกพาสูงสุดและอาจจะเล็กน้อยออกจากความรักสำหรับทุบตี;)

คำตอบของRameshและl0b0ที่ใช้/dev/urandomหรือใช้/dev/randomร่วมกับodร่วมกับนั่นเป็นสิ่งที่ดี แต่วิธีการของพวกเขามีข้อเสียเพียงแค่สามารถสุ่มจำนวนเต็มในช่วง 0 ถึง 2 8n -1 สำหรับบาง n เนื่องจากวิธีนี้เป็นตัวอย่างไบต์ตัวอย่างเช่นบิตของความยาว 8 พวกนี้ค่อนข้างกระโดดด้วย เพิ่ม n

ในที่สุดคำตอบของฟัลโกจะอธิบายแนวคิดทั่วไปว่าจะทำอย่างไรกับช่วงที่กำหนดเอง (ไม่ใช่แค่พลังสองอันเท่านั้น) โดยทั่วไปสำหรับช่วงที่กำหนด{0..max}เราสามารถกำหนดได้ว่ากำลังสองถัดไปของอะไรคือจำนวนบิตที่ต้องการแสดงmaxเป็นบิตสตริง จากนั้นเราสามารถสุ่มตัวอย่างได้หลายบิตและดูว่าการแบ่งนี้เป็นจำนวนเต็มมากกว่าmaxหรือไม่ ถ้าเป็นเช่นนั้นทำซ้ำ เนื่องจากเราสุ่มตัวอย่างได้มากเท่าที่จำเป็นในการเป็นตัวแทนmaxแต่ละการวนซ้ำมีความน่าจะเป็นมากกว่าหรือเท่ากับ 50% ของการประสบความสำเร็จ (50% ในกรณีที่เลวร้ายที่สุด 100% ในกรณีที่ดีที่สุด) ดังนั้นนี่จึงมีประสิทธิภาพมาก

สคริปท์ของฉันเป็นการใช้งานคำตอบของ Falco อย่างเป็นรูปธรรมเขียนด้วย bash บริสุทธิ์และมีประสิทธิภาพสูงเพราะใช้การทำงาน bitcoin ในตัวของ bash เพื่อทดสอบ bitstrings ตามความยาวที่ต้องการ นอกจากนี้ยังได้รับเกียรตินิยมความคิดโดยEliah Kaganที่แสดงให้เห็นการใช้งานในตัว$RANDOMตัวแปรโดย bitstrings concatening $RANDOMที่เกิดจากการสวดซ้ำ ที่จริงผมนำมาใช้ทั้งความเป็นไปได้ที่จะใช้และ/dev/urandom โดยค่าเริ่มต้นการใช้สคริปต์ข้าง$RANDOM $RANDOM(และตกลงถ้าใช้/dev/urandomเราต้องการodและ trแต่สิ่งเหล่านี้ได้รับการสนับสนุนโดย POSIX)

แล้วมันทำงานอย่างไร

ก่อนที่ฉันจะได้รับสิ่งนี้ข้อสังเกตสองประการ:

  1. ปรากฎว่า bash ไม่สามารถจัดการจำนวนเต็มที่มากกว่า 2 63 -1 ดูตัวเอง:

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

    มันจะปรากฏว่าทุบตีภายในใช้จำนวนเต็ม 64 บิตลงนามในการจัดเก็บจำนวนเต็ม ดังนั้นที่ 2 63มัน "ล้อมรอบ" และเราได้จำนวนเต็มลบ ดังนั้นเราจึงไม่สามารถหวังได้ว่าจะมีช่วงที่ใหญ่กว่า 2 63 -1 ด้วยฟังก์ชันสุ่มใด ๆ ที่เราใช้ Bash ไม่สามารถจัดการได้

  2. เมื่อใดก็ตามที่เราต้องการตัวอย่างค่าในช่วงใดก็ได้ระหว่างminและmaxอาจเป็นไปmin != 0ได้เราสามารถสุ่มค่าระหว่าง0และmax-minแทนและเพิ่มminในผลลัพธ์สุดท้าย นี้ทำงานแม้ว่าminและอาจจะยังmaxเป็นเชิงลบแต่เราจะต้องระมัดระวังที่จะลิ้มลองค่าระหว่าง0และค่าสัมบูรณ์ของ max-minดังนั้นแล้วเราสามารถมุ่งเน้นเกี่ยวกับวิธีการที่จะลิ้มลองค่าสุ่มระหว่างและจำนวนเต็มบวกโดยพลการ0 maxที่เหลือก็ง่าย

ขั้นตอนที่ 1: กำหนดจำนวนบิตที่ต้องการเพื่อแสดงจำนวนเต็ม (ลอการิทึม)

ดังนั้นสำหรับค่าที่กำหนด maxเราต้องการทราบจำนวนบิตที่ต้องการเพื่อแสดงว่าเป็นบิตสตริง นี่คือเพื่อให้ในภายหลังเราสามารถสุ่มตัวอย่างเพียงแค่บิตจำนวนมากเท่าที่ต้องการซึ่งทำให้สคริปต์มีประสิทธิภาพมาก

มาดูกัน. เนื่องจากด้วยnบิตเราสามารถแทนค่าได้สูงสุด 2 n -1 จากนั้นจำนวนnของบิตที่จำเป็นในการแทนค่าโดยพลการxคือ Ceiling (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ก่อน

ตัวเลือก A: การใช้ $RANDOM

สิ่งนี้ใช้ความคิดที่กล่าวถึงโดย Eliah Kagan โดยทั่วไปเนื่องจาก$RANDOMตัวอย่างจำนวนเต็ม 15 บิตเราสามารถใช้$((RANDOM<<15|RANDOM))เพื่อสุ่มตัวอย่างจำนวนเต็ม 30 บิต นั่นหมายความว่าให้เลื่อนการเรียกใช้ครั้งแรกที่$RANDOM15 บิตไปทางซ้ายและใช้ค่าบิตหรือการเรียกครั้งที่สองของการ$RANDOMเชื่อมบิตสองตัวอย่างที่เป็นอิสระได้อย่างมีประสิทธิภาพ$RANDOMไป)

เราสามารถทำสิ่งนี้ซ้ำเพื่อรับจำนวนเต็ม 45 บิตหรือ 60 บิต หลังจาก bash นั้นไม่สามารถจัดการได้อีกต่อไป แต่นี่หมายความว่าเราสามารถสุ่มค่าสุ่มระหว่าง 0 ถึง 2 60 -1 ได้ ดังนั้นเพื่อสุ่มตัวอย่างจำนวนเต็ม n-bit เราจะทำซ้ำขั้นตอนนี้จนกระทั่ง bitstring แบบสุ่มของเราซึ่งความยาวเพิ่มขึ้นในขั้นตอน 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) ))
}

ตัวเลือก B: การใช้ /dev/urandom

อีกวิธีหนึ่งเราสามารถใช้odและ/dev/urandomตัวอย่างจำนวนเต็ม n-bit odจะอ่านจำนวนไบต์เช่น bitstrings ที่มีความยาว 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บิต bitstrings ตอนนี้ แต่เราต้องการที่จะจำนวนเต็มตัวอย่างอยู่ในช่วงจาก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 script ทำบางสิ่งอาร์กิวเมนต์แยก ... เราต้องการทั้งสองมีปากเสียงminและmaxหรือเพียงหนึ่งอาร์กิวเมนต์maxที่ค่าเริ่มต้นmin0

# 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 ))

โดยได้รับแรงบันดาลใจจากสิ่งนี้ฉันอาจลองใช้เครื่องมิจฉาทิฐิเพื่อทดสอบและกำหนดมาตรฐาน PRNG นี้และวางสิ่งที่ฉันค้นพบที่นี่ :-)


โซลูชันของคุณจะถือว่า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 ฉันกำลังคิดว่าสคริปต์ของฉันจะไม่เขียนโค้ดใด ๆ เกี่ยวกับขนาดของ int ที่มีความยาว อาจเป็นไปได้ว่ามันจะทำงานได้แม้ว่าขนาดของ int ที่มีความยาวที่เซ็นชื่อจะมากกว่า (หรือต่ำกว่า) มากกว่า 64 บิตเช่น 128 บิต อย่างไรก็ตามถ้าฉันใช้--format=u8ฉันก็จะทำการแฮ็กรหัสsizeof(int)==8อย่างหนัก ในทางกลับกันหากใช้--format=uLไม่มีปัญหา: ฉันไม่คิดว่ามีแพลตฟอร์มที่มีจำนวนเต็ม 64- บิต แต่ยังคงกำหนด int ยาวเป็นสิ่งที่ต่ำกว่า ดังนั้นโดยทั่วไปฉันจะเถียง--format=uLช่วยให้มีความยืดหยุ่นมากขึ้น คุณคิดยังไง?
Malte Skoruppa

มีlong longที่สามารถเป็น 64 บิตในขณะที่ int = ยาว = 32 บิตในบางแพลตฟอร์ม คุณไม่ควรอ้างสิทธิ์ 0..2 ** 60 ช่วงหากคุณไม่สามารถรับประกันได้ในทุกแพลตฟอร์ม ในทางกลับกันทุบตีอาจไม่สนับสนุนช่วงนี้เองบนแพลตฟอร์มดังกล่าว (ฉันไม่รู้บางทีมันอาจใช้ maxint_t และ u8 นั้นถูกต้องมากขึ้นถ้าคุณต้องการยืนยันช่วงคงที่ ( odไม่สนับสนุนการระบุ maxint ถ้าช่วงของคุณคือ ช่วงใดที่ขึ้นอยู่กับแพลตฟอร์มของ bash ช่วงใด) ถ้าช่วง bash ขึ้นกับขนาดความยาว uL อาจเหมาะสมกว่า) คุณต้องการเต็มรูปแบบที่ทุบตีรองรับระบบปฏิบัติการทั้งหมดหรือช่วงที่กำหนดหรือไม่?
jfs

6

มันเป็น zsh ได้ไหม?

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

rand48(seed)คุณอาจต้องการที่จะใช้เมล็ดพันธุ์เป็นอย่างดีด้วย ดูman zshmodulesและman 3 erand48สำหรับคำอธิบายโดยละเอียดหากสนใจ


ผมเองไม่ได้ใช้ zsh แต่ตอนนี้เป็นยังดี :)
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!


ขอขอบคุณ. เพื่อรับ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/urandomผลลัพธ์นั้นแย่กว่าการใช้/dev/randomurandom ในกรณีส่วนใหญ่ เมื่อ/dev/urandomเริ่มต้นแล้ว (ที่จุดเริ่มต้นของระบบ); ผลลัพธ์ของมันดีพอ ๆ/dev/randomกับแอพพลิเคชั่นเกือบทั้งหมดบน Linux ในบางระบบสุ่มและ urandom เหมือนกัน
jfs

1
--format=uควรแทนที่ด้วย--format=u4เพราะsizeof(int)อาจน้อยกว่า4ในทางทฤษฎี
jfs

@JFSebastian บทความนี้มีการสนทนาที่น่าสนใจมากเกี่ยวกับเรื่องนี้ ข้อสรุปของพวกเขาดูเหมือนว่าทั้งคู่/dev/randomและ/dev/urandomไม่น่าพอใจและ "Linux ควรเพิ่ม RNG ที่ปลอดภัยที่บล็อกจนกว่าจะมีการรวบรวมเอนโทรปีของเมล็ดที่เพียงพอและหลังจากนั้นจะมีพฤติกรรมเช่นurandomนั้น"
l0b0

3

สมมติว่าคุณไม่คัดค้านการใช้เครื่องมือภายนอกสิ่งนี้ควรตอบสนองความต้องการของคุณ:

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

มันใช้randฟังก์ชั่นของ perl ซึ่งใช้ขีด จำกัด สูงสุดเป็นพารามิเตอร์ คุณสามารถตั้งค่าเป็นอะไรก็ได้ที่คุณต้องการ วิธีนี้ใกล้เคียงกับการสุ่มอย่างแท้จริงในคำนิยามทางคณิตศาสตร์ที่เป็นนามธรรมอยู่นอกเหนือขอบเขตของเว็บไซต์นี้ แต่ควรจะดีถ้าคุณไม่จำเป็นต้องใช้มันสำหรับการเข้ารหัสที่ละเอียดอ่อนมากหรือสิ่งที่คล้ายกัน อาจจะถึงที่นั่น แต่ฉันจะไม่แสดงความคิดเห็น


การแบ่งนี้เป็นจำนวนมากเช่น 5 ** 1234
jfs

1
@JFSebastian ใช่มันเป็นเช่นนั้น ฉันโพสต์สิ่งนี้ตั้งแต่ OP ระบุไว้1^32-1แต่คุณต้องปรับแต่งสำหรับตัวเลขที่มากขึ้น
terdon

2

คุณควรจะได้รับ (2 ^ X) -1 ที่ใกล้เคียงที่สุดหรือมากกว่าที่คุณต้องการขูดและจำนวนบิต จากนั้นเพียงโทร / dev / สุ่มหลายครั้งและผนวกบิตทั้งหมดเข้าด้วยกันจนกว่าคุณจะพอตัดทอนบิตทั้งหมดที่มากเกินไป หากจำนวนผลลัพธ์มากกว่าจำนวนการทำซ้ำสูงสุดของคุณ ในกรณีที่เลวร้ายที่สุดคุณมีโอกาสมากกว่า 50% ที่จะได้รับหมายเลขสุ่มต่ำกว่าค่าสูงสุดดังนั้น (สำหรับกรณีที่เลวร้ายที่สุดนี้) คุณจะรับสายสองสายโดยเฉลี่ย


อันที่จริงมันเป็นความคิดที่ดีที่จะปรับปรุงประสิทธิภาพ คำตอบของ Rameshและคำตอบของl0b0โดยทั่วไปแล้วจะได้รับบิตสุ่มจาก/dev/urandomทั้งคู่ การตัดส่วนบิตที่มากเกินไปสำหรับช่วงล่างก่อนที่จะจัดรูปแบบเป็นทศนิยมด้วย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))
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.