Làm thế nào để tạo hiệu quả số nguyên lớn, phân bố đồng đều, ngẫu nhiên trong bash?


30

Tôi đã tự hỏi điều gì sẽ là cách tốt nhất để có được sự ngẫu nhiên tốt trong bash, nghĩa là, đâu sẽ là một thủ tục để có được một số nguyên dương ngẫu nhiên giữa MINMAXnhư vậy

  1. Phạm vi có thể lớn tùy ý (hoặc ít nhất là, tối đa là 2 32 -1);
  2. Các giá trị được phân phối đồng đều (nghĩa là không có sai lệch);
  3. Đó là hiệu quả.

Một cách hiệu quả để có được tính ngẫu nhiên trong bash là sử dụng $RANDOMbiến. Tuy nhiên, điều này chỉ lấy mẫu một giá trị trong khoảng từ 0 đến 2 15 -1, có thể không đủ lớn cho tất cả các mục đích. Mọi người thường sử dụng một modulo để đưa nó vào phạm vi họ muốn, ví dụ:

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

Điều này, ngoài ra, tạo ra một sự thiên vị trừ khi $MAXxảy ra để chia 2 15 -1 = 32767. Ví dụ: nếu $MINlà 0 và $MAXlà 9, thì các giá trị 0 đến 7 có thể xảy ra hơn một chút so với các giá trị 8 và 9, vì $RANDOMsẽ không bao giờ là 32768 hoặc 32769. Độ lệch này trở nên tồi tệ hơn khi phạm vi tăng, ví dụ: nếu $MINlà 0 và $MAXlà 9999, sau đó các số từ 0 đến 2767 có một xác suất 4 / 32767 , trong khi những con số 2768 qua 9999 chỉ có một xác suất 3 / 32767 .

Vì vậy, trong khi phương thức trên đáp ứng điều kiện 3, nó không đáp ứng điều kiện 1 và 2.

Phương pháp tốt nhất mà tôi đã nghĩ ra cho đến nay khi cố gắng thỏa mãn điều kiện 1 và 2 là sử dụng /dev/urandomnhư sau:

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

Về cơ bản, chỉ cần thu thập tính ngẫu nhiên từ /dev/urandom(có thể cân nhắc sử dụng /dev/randomthay vào đó nếu muốn có một trình tạo số giả mã hóa mạnh, và nếu bạn có nhiều thời gian, hoặc nếu không có thể là trình tạo số ngẫu nhiên phần cứng), hãy xóa mọi ký tự không phải là chữ số thập phân, gấp lại đầu ra theo chiều dài $MAXvà cắt 0 hàng đầu. Nếu chúng tôi tình cờ chỉ nhận được 0 thì $rndtrống, vì vậy trong trường hợp này được đặt rndthành 0. Kiểm tra xem kết quả có nằm ngoài phạm vi của chúng tôi không và nếu có thì lặp lại. Tôi đã buộc "cơ thể" của vòng lặp while vào người bảo vệ ở đây để buộc thực thi cơ thể ít nhất một lần, theo tinh thần mô phỏng một do ... whilevòng lặp, vì rndkhông xác định được bắt đầu bằng.

Tôi nghĩ rằng tôi đã đáp ứng điều kiện 1 và 2 ở đây, nhưng bây giờ tôi đã làm hỏng điều kiện 3. Nó hơi chậm. Mất tới một giây hoặc lâu hơn (một phần mười giây khi tôi may mắn). Trên thực tế, vòng lặp thậm chí không được đảm bảo để chấm dứt (mặc dù xác suất chấm dứt hội tụ đến 1 khi thời gian tăng lên).

Có cách nào hiệu quả để có được số nguyên ngẫu nhiên không thiên vị, trong phạm vi được chỉ định trước và có khả năng lớn, trong bash không? (Tôi sẽ tiếp tục điều tra khi thời gian cho phép, nhưng trong lúc đó tôi nghĩ ai đó ở đây có thể có một ý tưởng hay!)

Bảng câu trả lời

  1. Ý tưởng cơ bản nhất (và do đó có thể di động) là tạo ra một chuỗi bit ngẫu nhiên vừa đủ dài. Có nhiều cách khác nhau để tạo ra một chuỗi bit ngẫu nhiên, bằng cách sử dụng $RANDOMbiến tích hợp của bash hoặc sử dụng od/dev/urandom(hoặc /dev/random). Nếu số ngẫu nhiên lớn hơn $MAX, bắt đầu lại.

  2. Ngoài ra, có thể sử dụng các công cụ bên ngoài.

    • Giải pháp Perl
      • Pro: khá di động, đơn giản, linh hoạt
      • Contra: không cho số lượng rất lớn trên 2 32 -1
    • Giải pháp Python
      • Pro: đơn giản, linh hoạt, hoạt động ngay cả với số lượng lớn
      • Contra: ít di động
    • Giải pháp zsh
      • Pro: tốt cho những người sử dụng zsh
      • Contra: có lẽ ít di động hơn

Tại sao chỉ chọn các số nguyên, thay vì mã hóa các bit ngẫu nhiên, sau đó chuyển đổi một số ký tự nhất định (tùy thuộc vào phạm vi cần thiết) từ dạng được mã hóa sang base10 từ base64?
muru

cần phải bash? Sẽ làm điều gì đó giống như rand=$(command)nếu commandtrả về một iteger đáp ứng yêu cầu của bạn?
terdon

@muru Thật là một ý tưởng hay. Tôi đã dành một số suy nghĩ cho một ý tưởng tương tự, sử dụng dd if=/dev/urandom 2>/dev/nullvà chuyển qua đó od -t d(tránh đường vòng qua cơ sở64), nhưng tôi không rõ việc chuyển đổi xảy ra như thế nào và liệu nó có thực sự không thiên vị hay không. Nếu bạn có thể mở rộng ý tưởng của mình thành một kịch bản hiệu quả, hiệu quả và giải thích tại sao không có sự thiên vị, nó sẽ tạo ra một câu trả lời tuyệt vời. :)
Malte Skoruppa

@terdon Mình thích bash hơn. Ý tôi là, tất nhiên bạn có thể gọi pythonhoặc perlhoặc ngôn ngữ yêu thích của bạn, nhưng điều này không có sẵn ở mọi nơi. Tôi muốn một cái gì đó di động hơn. Chà, awkchức năng ngẫu nhiên sẽ ổn thôi, tôi đoán vậy. Nhưng càng di động thì càng tốt :)
Malte Skoruppa

2
Vâng, tôi đã suy nghĩ dọc theo dòng perl -e 'print int(rand(2**32-1))');. Đó là khá di động và sẽ rất nhanh. Awk sẽ không cắt nó vì hầu hết các triển khai bắt đầu từ cùng một hạt giống. Vì vậy, bạn nhận được cùng một số ngẫu nhiên trong các lần chạy tiếp theo. Nó chỉ thay đổi trong cùng một lần chạy.
terdon

Câu trả lời:


17

Tôi thấy một phương pháp thú vị khác từ đây .

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

Điều này cũng có vẻ là một lựa chọn tốt. Nó đọc 4 byte từ thiết bị ngẫu nhiên và định dạng chúng là số nguyên không dấu giữa 02^32-1.

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

7
bạn nên sử dụng /dev/urandomtrừ khi bạn biết rằng bạn cần/dev/random ; /dev/randomcác khối trên Linux.
jfs

Tại sao odcác lệnh khác nhau. Cả hai chỉ in các số nguyên không dấu 4 byte: 1st - from openssl, 2nd - from /dev/random.
jfs

1
@Ramesh Tôi đã chỉnh sửa để sử dụng /dev/urandomthay vì /dev/random- Tôi thấy không có lý do gì để sử dụng /dev/randomvà nó có thể thực sự tốn kém / chậm hoặc làm chậm các phần khác của hệ thống. (Hãy thoải mái chỉnh sửa lại và giải thích nếu thực sự cần thiết.)
Volker Siegel

1
Đừng lo lắng, thật đáng ngạc nhiên khi sự khác biệt đơn giản này có những ảnh hưởng phức tạp như vậy. Đó là lý do tại sao tôi khăng khăng thay đổi ví dụ thành đúng - mọi người học hỏi từ các ví dụ.
Volker Siegel

1
@MalteSkoruppa: Iviết tắt của nguyên tắc sizeof(int)có thể ít hơn so với 4nguyên tắc. btw, od -DAnthất bại cho (2**32-1)nhưng od -N4 -tu4 -Antiếp tục làm việc.
jfs

8

Cảm ơn tất cả các câu trả lời tuyệt vời của bạn. Tôi đã kết thúc với giải pháp sau đây, mà tôi muốn chia sẻ.

Trước khi tôi đi sâu vào chi tiết hơn về các vấn đề cá nhân và địa ngục, đây là tl; dr : kịch bản mới sáng bóng của tôi :-)

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

Lưu nó vào ~/bin/randvà bạn có sẵn một hàm ngẫu nhiên ngọt ngào trong bash có thể lấy mẫu một số nguyên trong một phạm vi tùy ý nhất định. Phạm vi có thể chứa các số nguyên âm và dương và có thể dài tới 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

Tất cả các ý tưởng của người trả lời khác là tuyệt vời. Các câu trả lời của terdon , JF Sebastianjimmij đã sử dụng các công cụ bên ngoài để thực hiện nhiệm vụ một cách đơn giản và hiệu quả. Tuy nhiên, tôi thích một giải pháp bash thực sự cho tính di động tối đa, và có thể một chút, đơn giản là vì yêu thích bash;)

Câu trả lời của Rameshl0b0 được sử dụng /dev/urandomhoặc /dev/randomkết hợp với od. Tuy nhiên, điều đó tốt, cách tiếp cận của họ có nhược điểm là chỉ có thể lấy mẫu các số nguyên ngẫu nhiên trong phạm vi 0 đến 2 8n -1 cho một số n, vì phương pháp này lấy mẫu byte, tức là bitstrings có độ dài 8. Đây là những bước nhảy khá lớn với tăng n.

Cuối cùng, câu trả lời của Falco mô tả ý tưởng chung về cách điều này có thể được thực hiện cho các phạm vi tùy ý (không chỉ sức mạnh của hai). Về cơ bản, đối với một phạm vi nhất định {0..max}, chúng ta có thể xác định sức mạnh tiếp theo của hai là gì, tức là chính xác có bao nhiêu bit được yêu cầu để biểu diễn maxdưới dạng một chuỗi bit. Sau đó, chúng ta có thể lấy mẫu nhiều bit đó và xem liệu giá trị này, như một số nguyên, có lớn hơn không max. Nếu vậy, lặp lại. Vì chúng tôi lấy mẫu nhiều bit cần thiết để đại diện max, mỗi lần lặp có xác suất lớn hơn hoặc bằng 50% thành công (50% trong trường hợp xấu nhất, 100% trong trường hợp tốt nhất). Vì vậy, điều này là rất hiệu quả.

Kịch bản của tôi về cơ bản là một triển khai cụ thể cho câu trả lời của Falco, được viết bằng bash thuần túy và hiệu quả cao vì nó sử dụng các hoạt động bitwise tích hợp của bash để lấy mẫu bitstrstr theo độ dài mong muốn. Nó cũng tôn vinh một ý tưởng của Eliah Kagan , đề nghị sử dụng $RANDOMbiến tích hợp bằng cách ghép các bitstrings do các lệnh lặp đi lặp lại của $RANDOM. Tôi thực sự đã thực hiện cả hai khả năng sử dụng /dev/urandom$RANDOM. Theo mặc định, kịch bản trên sử dụng $RANDOM. (Và ok, nếu sử dụng /dev/urandomchúng ta cần odtr , nhưng những thứ này được hỗ trợ bởi POSIX.)

Vì vậy, làm thế nào nó hoạt động?

Trước khi tôi nhận được điều này, hai quan sát:

  1. Hóa ra bash không thể xử lý các số nguyên lớn hơn 2 63 -1. Xem cho chính mình:

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

    Dường như bash sử dụng các số nguyên 64 bit đã ký để lưu các số nguyên. Vì vậy, ở 2 63, nó "kết thúc" và chúng ta nhận được một số nguyên âm. Vì vậy, chúng tôi không thể hy vọng có được bất kỳ phạm vi nào lớn hơn 2 63 -1 với bất kỳ chức năng ngẫu nhiên nào chúng tôi sử dụng. Bash đơn giản là không thể xử lý nó.

  2. Bất cứ khi nào chúng tôi muốn lấy mẫu một giá trị trong một phạm vi tùy ý giữa minmaxcó thể min != 0, chúng tôi chỉ có thể lấy mẫu một giá trị giữa 0max-minthay vào đó và sau đó thêm minvào kết quả cuối cùng. Điều này hoạt động ngay cả khi minvà cũng có thể maxâm , nhưng chúng ta cần cẩn thận để lấy mẫu một giá trị giữa 0giá trị tuyệt đối max-min . Vì vậy, sau đó, chúng ta có thể tập trung vào cách lấy mẫu một giá trị ngẫu nhiên giữa 0và một số nguyên dương tùy ý max. Phần còn lại là dễ dàng.

Bước 1: Xác định số lượng bit cần thiết để biểu diễn một số nguyên (logarit)

Vì vậy, đối với một giá trị nhất định max, chúng tôi muốn biết chỉ cần bao nhiêu bit để biểu diễn nó dưới dạng một chuỗi bit. Điều này là để sau này chúng ta có thể lấy mẫu ngẫu nhiên chỉ cần bao nhiêu bit, điều này làm cho tập lệnh rất hiệu quả.

Hãy xem nào. Vì với ncác bit, chúng ta có thể biểu thị tới giá trị 2 n -1, sau đó số nbit cần thiết để biểu thị một giá trị tùy ý xlà trần (log 2 (x + 1)). Vì vậy, chúng ta cần một hàm để tính trần của logarit đến cơ sở 2. Nó khá tự giải thích:

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

Chúng ta cần điều kiện n>0để nếu nó phát triển quá lớn, bao bọc xung quanh và trở nên tiêu cực, vòng lặp được đảm bảo chấm dứt.

Bước 2: Lấy mẫu ngẫu nhiên một chuỗi bit có độ dài n

Các ý tưởng di động nhất là sử dụng /dev/urandom(hoặc thậm chí /dev/randomnếu có lý do chính đáng) hoặc $RANDOMbiến tích hợp của bash . Trước tiên hãy xem làm thế nào để làm điều đó $RANDOM.

Tùy chọn A: Sử dụng $RANDOM

Điều này sử dụng ý tưởng được đề cập bởi Eliah Kagan. Về cơ bản, vì $RANDOMlấy mẫu số nguyên 15 bit, chúng ta có thể sử dụng $((RANDOM<<15|RANDOM))để lấy mẫu số nguyên 30 bit. Điều đó có nghĩa là, dịch chuyển một lệnh gọi đầu tiên $RANDOM15 bit sang trái và áp dụng một bitwise hoặc với lần gọi thứ hai $RANDOM, kết hợp hiệu quả hai bitstr được lấy mẫu độc lập (hoặc ít nhất là độc lập như tích hợp sẵn của bash $RANDOM).

Chúng ta có thể lặp lại điều này để có được số nguyên 45 bit hoặc 60 bit. Sau đó, bash không thể xử lý được nữa, nhưng điều này có nghĩa là chúng ta có thể dễ dàng lấy mẫu một giá trị ngẫu nhiên trong khoảng từ 0 đến 2 60 -1. Vì vậy, để lấy mẫu một số nguyên n bit, chúng tôi lặp lại quy trình cho đến khi chuỗi bit ngẫu nhiên của chúng tôi, có độ dài tăng dần theo các bước 15 bit, có độ dài lớn hơn hoặc bằng n. Cuối cùng, chúng tôi cắt bỏ các bit quá nhiều bằng cách dịch chuyển bit sang bên phải một cách thích hợp và chúng tôi kết thúc với một số nguyên ngẫu nhiên n bit.

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

Tùy chọn B: Sử dụng /dev/urandom

Ngoài ra, chúng ta có thể sử dụng od/dev/urandomlấy mẫu một số nguyên n bit. odsẽ đọc các byte, tức là các bit có độ dài 8. Tương tự như trong phương pháp trước, chúng ta lấy mẫu rất nhiều byte để số bit được lấy mẫu tương đương lớn hơn hoặc bằng n và cắt các bit quá nhiều.

Số byte thấp nhất cần thiết để có ít nhất n bit là bội số thấp nhất của 8 lớn hơn hoặc bằng n, tức là sàn ((n + 7) / 8).

Điều này chỉ hoạt động lên đến số nguyên 56 bit. Lấy mẫu thêm một byte sẽ giúp chúng ta có một số nguyên 64 bit, nghĩa là, giá trị lên tới 2 64 -1, mà bash không thể xử lý.

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

Đặt các mảnh lại với nhau: Lấy số nguyên ngẫu nhiên trong các phạm vi tùy ý

nBây giờ chúng ta có thể lấy mẫu bitbit bit, nhưng chúng ta muốn lấy mẫu các số nguyên trong một phạm vi từ 0đến max, đồng nhất ngẫu nhiên , trong đó maxcó thể là tùy ý, không nhất thiết phải là lũy thừa của hai. (Chúng ta không thể sử dụng modulo vì điều đó tạo ra sự thiên vị.)

Toàn bộ lý do tại sao chúng tôi đã cố gắng rất nhiều để lấy mẫu càng nhiều bit cần thiết để biểu thị giá trị max, giờ đây chúng tôi có thể sử dụng một vòng lặp để lấy mẫu một chuỗi nbit -bit một cách an toàn cho đến khi chúng tôi lấy mẫu một giá trị thấp hơn hoặc bằng max. Trong trường hợp xấu nhất ( maxlà lũy thừa của hai), mỗi lần lặp kết thúc với xác suất 50% và trong trường hợp tốt nhất ( maxlà lũy thừa hai trừ một), lần lặp đầu tiên chấm dứt một cách chắc chắn.

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
}

Kết thúc mọi thứ

Cuối cùng, chúng tôi muốn lấy mẫu các số nguyên giữa minmax, ở đâu minmaxcó thể tùy ý, thậm chí âm. Như đã đề cập trước đây, điều này bây giờ là tầm thường.

Hãy đặt tất cả trong một tập lệnh bash. Thực hiện một số công cụ phân tích cú pháp đối số ... Chúng tôi muốn hai đối số minmax, hoặc chỉ một đối số max, trong đó minmặc định 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

... Và cuối cùng, để lấy mẫu thống nhất một cách ngẫu nhiên một giá trị giữa minmax, chúng tôi lấy mẫu một số nguyên ngẫu nhiên giữa 0và giá trị tuyệt đối của max-min, và thêm minvào kết quả cuối cùng. :-)

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

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

Lấy cảm hứng từ điều này , tôi có thể thử sử dụng dieharder để kiểm tra và đánh giá PRNG này, và đưa những phát hiện của tôi vào đây. :-)


giải pháp của bạn giả định rằng sizeof(int) == 8(64 bit) do--format=u
jfs

1
giải pháp của bạn nhắc nhở tôi cách viết ngẫu nhiên. random.RandomLớp sử dụng 53 bit? trình tạo để trả về các số ngẫu nhiên lớn tùy ý (nhiều lệnh), random.SystemRandomthực hiện tương tự bằng cách sử dụng os.urandom()có thể được thực hiện bằng cách sử dụng /dev/urandom.
JFS

uL ngụ ý sizeof (dài)> = 8 cho phạm vi. Nó không được đảm bảo. Bạn có thể sử dụng u8 để khẳng định rằng nền tảng có số nguyên như vậy.
jfs

@JFSebastian Tôi đã nghĩ rằng cho đến nay kịch bản của tôi không mã hóa bất kỳ giả định nào về kích thước của một int dài. Có khả năng, nó sẽ hoạt động ngay cả khi kích thước của một int dài ký lớn hơn (hoặc thấp hơn) so với 64 bit, ví dụ: 128 bit. Tuy nhiên, nếu tôi sử dụng --format=u8thì tôi cứng mã giả định sizeof(int)==8. Mặt khác, nếu sử dụng --format=uLthì không có vấn đề gì: Tôi không nghĩ có một nền tảng số nguyên 64 bit nhưng vẫn định nghĩa các số nguyên dài là một cái gì đó thấp hơn. Vì vậy, về cơ bản tôi sẽ tranh luận --format=uLcho phép linh hoạt hơn. Quan điểm của bạn là gì?
Malte Skoruppa

long longthể là 64 bit trong khi int = long = 32bit trên một số nền tảng. Bạn không nên yêu cầu 0..2 ** 60 phạm vi nếu bạn không thể đảm bảo phạm vi đó trên tất cả các nền tảng. Mặt khác, bash có thể không hỗ trợ phạm vi này trên các nền tảng như vậy (tôi không biết, có lẽ nó sử dụng maxint_t và sau đó u8 chính xác hơn nếu bạn muốn xác nhận phạm vi cố định ( odkhông hỗ trợ chỉ định maxint nếu phạm vi của bạn là phạm vi phụ thuộc vào nền tảng của bash là gì. Nếu phạm vi bash phụ thuộc vào sizeof dài thì uL có thể phù hợp hơn). Bạn có muốn phạm vi đầy đủ mà bash hỗ trợ trên tất cả các hệ điều hành hoặc một phạm vi cố định không?
jfs

6

Nó có thể là zsh?

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

Bạn có thể muốn sử dụng hạt giống như với rand48(seed). Xem man zshmodulesman 3 erand48để mô tả chi tiết nếu quan tâm.


Cá nhân tôi không sử dụng zsh, nhưng đây là một bổ sung tuyệt vời :)
Malte Skoruppa

5
$ python -c 'import random as R; print(R.randint(-3, 5**1234))'

python có sẵn trên Redhat, các hệ thống dựa trên Debian.


+1 Ah, cùng với giải pháp perl , phải có giải pháp python. Cảm ơn :)
Malte Skoruppa

5

Nếu bạn muốn một số từ 0 đến (2 ^ n) -1 trong đó n mod 8 = 0 bạn chỉ cần lấy n / 8 byte từ đó /dev/random. Ví dụ: để có được biểu diễn thập phân ngẫu nhiên, intbạn có thể:

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

Nếu bạn chỉ muốn lấy n bit, trước tiên bạn có thể lấy byte trần (n / 8)dịch chuyển đúng sang số lượng bạn muốn. Ví dụ: nếu bạn muốn 15 bit:

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

Nếu bạn hoàn toàn chắc chắn rằng bạn không quan tâm đến chất lượng của sự ngẫu nhiên và bạn muốn đảm bảo thời gian chạy tối thiểu bạn có thể sử dụng /dev/urandomthay vì /dev/random. Hãy chắc chắn rằng bạn biết những gì bạn đang làm trước khi sử dụng /dev/urandom!


Cảm ơn bạn. Vì vậy, nhận được nbyte ngẫu nhiên từ /dev/urandomvà định dạng bằng cách sử dụng od. Tương tự trong tinh thần như câu trả lời này . Cả hai đều tốt như nhau :) Mặc dù cả hai đều có nhược điểm là có phạm vi cố định từ 0 đến 2 ^ (n * 8) -1 bit, trong đó n là số byte. Tôi thích một phương pháp cho một phạm vi tùy ý , lên tới 2 ^ 32-1, nhưng cũng có thể thấp hơn. Điều này tạo ra khó khăn thiên vị.
Malte Skoruppa

Chỉnh sửa để sử dụng /dev/urandomthay vì /dev/random- Tôi thấy không có lý do để sử dụng /dev/random, và nó có thể thực sự tốn kém / chậm, hoặc làm chậm các phần khác của hệ thống. (Hãy thoải mái chỉnh sửa lại và giải thích nếu thực sự cần thiết.)
Volker Siegel

Nó phải hoàn toàn ngược lại: use / dev / urandom trừ khi bạn biết rằng bạn cần / dev / ngẫu nhiên . Thật không đúng khi cho rằng /dev/urandomkết quả tồi tệ hơn nhiều so với /dev/randomviệc urandom không thể sử dụng được trong hầu hết các trường hợp. Một lần /dev/urandomđược khởi tạo (khi bắt đầu hệ thống); kết quả của nó cũng tốt như /dev/randomhầu hết các ứng dụng trên Linux. Trên một số hệ thống ngẫu nhiên và urandom là như nhau.
jfs

1
--format=unên được thay thế --format=u4bởi vì sizeof(int)có thể ít hơn 4trong lý thuyết.
jfs

@JFSebastian Bài viết này có một cuộc thảo luận rất thú vị xung quanh chủ đề này. Kết luận của họ dường như là cả hai /dev/random/dev/urandomđều không đạt yêu cầu, và "Linux nên thêm một RNG an toàn chặn cho đến khi nó thu được entropy hạt giống đầy đủ và sau đó hành xử như thế nào urandom."
l0b0

3

Giả sử bạn không phản đối việc sử dụng các công cụ bên ngoài, điều này sẽ đáp ứng yêu cầu của bạn:

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

Đó là sử dụng randchức năng của perl , lấy giới hạn trên làm tham số. Bạn có thể đặt nó vào bất cứ điều gì bạn thích. Làm thế nào gần với sự ngẫu nhiên thực sự trong định nghĩa toán học trừu tượng nằm ngoài phạm vi của trang web này nhưng nó sẽ ổn trừ khi bạn cần nó để mã hóa cực kỳ nhạy cảm hoặc tương tự. Có lẽ ngay cả ở đó nhưng tôi sẽ không mạo hiểm một ý kiến.


điều này phá vỡ cho số lượng lớn , ví dụ, 5 ** 1234
jfs

1
@JFSebastian đúng vậy. Tôi đã đăng bài này kể từ khi OP chỉ định 1^32-1nhưng bạn cần phải chỉnh nó cho số lượng lớn hơn.
terdon

2

Bạn sẽ nhận được (2 ^ X) -1 gần nhất bằng hoặc vắt hơn mức tối đa mong muốn của bạn và nhận được số bit. Sau đó, chỉ cần gọi / dev / ngẫu nhiên nhiều lần và nối tất cả các bit lại với nhau cho đến khi bạn có đủ, cắt bớt tất cả các bit quá nhiều. Nếu số kết quả lớn hơn lặp lại tối đa của bạn. Trong trường hợp xấu nhất, bạn có nhiều hơn 50% cơ hội nhận được một số ngẫu nhiên dưới mức Tối đa của bạn, vì vậy (đối với trường hợp xấu nhất này), bạn sẽ thực hiện trung bình hai cuộc gọi.


Đây thực sự là một ý tưởng khá tốt để cải thiện hiệu quả. Câu trả lời của Rameshcâu trả lời của l0b0 về cơ bản đều nhận được các bit ngẫu nhiên /dev/urandom, nhưng trong cả hai câu trả lời, nó luôn là bội số của 8 bit. Cắt bớt các bit quá nhiều cho các phạm vi thấp hơn trước khi định dạng thành thập phân odlà một ý tưởng tốt để cải thiện hiệu quả, vì vòng lặp chỉ có số lần lặp dự kiến ​​là 2 lần, như bạn giải thích độc đáo. Điều này, kết hợp với một trong những câu trả lời được đề cập, có lẽ là cách để đi.
Malte Skoruppa

0

Câu trả lời của bạn rất thú vị nhưng khá dài.

Nếu bạn muốn số lượng lớn tùy ý, thì bạn có thể tham gia nhiều số ngẫu nhiên trong một trình trợ giúp:

# $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
}

Nếu vấn đề là thiên vị, sau đó chỉ cần loại bỏ nó.

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

Kết hợp các chức năng này với nhau

# $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))
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.