Bash:如何使用 $RANDOM 生成随机浮点数

sci*_*ci9 8 shell bash random numeric-data printf

是否可以使用整数随机生成器 $RANDOM 生成具有特定精度和特定范围的真实随机数?例如,我们如何生成介于 0 和 1 之间的 4 个精度的实数?

0.1234
0.0309
0.9001
0.0000
1.0000
Run Code Online (Sandbox Code Playgroud)

一个简单的解决方法:

printf "%d04.%d04\n" $RANDOM $RANDOM
Run Code Online (Sandbox Code Playgroud)

Kus*_*nda 11

awk -v n=10 -v seed="$RANDOM" 'BEGIN { srand(seed); for (i=0; i<n; ++i) printf("%.4f\n", rand()) }'
Run Code Online (Sandbox Code Playgroud)

这将输出n具有四位十进制数字的范围 [0,1) 中的随机数(示例中为十)。它使用rand()函数 in awk(不是在标准中,awk而是由最常见的awk实现实现)返回该范围内的随机值。随机数生成器由 shell 的$RANDOM变量作为种子。

awk程序只有BEGIN块(没有其他代码块)时,awk不会尝试从其标准输入流中读取输入。

在任何 OpenBSD 系统(或具有相同jot实用程序的系统,最初在 4.2BSD 中),以下将生成指定的 10 个随机数:

jot -p 4 -r 10 0 1
Run Code Online (Sandbox Code Playgroud)


A.E*_*ett 10

正如另一个答案中所指出的,您可以使用其他实用程序来生成随机数。在这个答案中,我将我的资源限制为$RANDOM和一些基本的算术函数。

对于浮点数,请尝试类似

printf '%s\n' $(echo "scale=8; $RANDOM/32768" | bc )
Run Code Online (Sandbox Code Playgroud)

这将为您提供最佳精度,因为$RANDOM只生成 0 到 32767 之间的数字。(包括 32767!)但是,我还通过调用bc.

但在继续之前,我想看看浮点数的精度范围两个问题。在那之后,我将研究生成一系列整数(如果您可以生成整数,如果您希望使用任何您喜欢的实用程序来完成它,您可以稍后将它们除以得到小数。)

精确

采取的方法 $RANDOM/32768,由于$RANDOM生成从 0 到 32767 的值,因此 的结果$RANDOM/32768同样将是有限多个值。换句话说,它仍然是一个离散的随机变量(使用计算机你永远无法摆脱这个事实)。考虑到这一点,那么你就可以实现一定程度的精确使用printf

如果你想要更好地覆盖区间,你可以开始考虑以 32768 为底。所以,理论上$RANDOM + $RANDOM*32768应该给你一个在 0 和 1,073,741,823 之间的均匀分布。但是,我怀疑命令行能否很好地处理这种精度。与此特定案例相关的几点:

  • 两个独立的、均匀分布的随机变量的总和通常不均匀。在这种情况下,至少从理论上讲(见第三点),它们是。
  • 不要认为你可以简化$RANDOM + $RANDOM*32768 = $RANDOM * ( 1 + 32768 )。的两次发生$RANDOM实际上是两个不同的事件。
  • 我对如何$RANDOM生成知之甚少,不知道像这样调用两次是否会真正生成两个独立的随机事件。

范围

让我们考虑一下$RANDOM/32768。如果你想要一个范围内的数字,比如[a,b),那么

$RANDOM/32768*(b-a) + a
Run Code Online (Sandbox Code Playgroud)

将使您进入所需的范围。

整数值的生成

首先,考虑在[0,b)whereb小于之间生成随机数32768。考虑乘积q*b,其中q是 的整数部分32768/b。然后你可以做的是生成 0 到 32767 之间的随机数,但丢弃那些大于或等于q*b. 拨打这样生成的号码G。然后G将落在 0 到 的范围内q*b,并且其分布将是均匀的。现在,应用模算术将这个值缩小到所需的范围:

G % b
Run Code Online (Sandbox Code Playgroud)

注意,随机生成一个数字如下

$RANDOM % b
Run Code Online (Sandbox Code Playgroud)

不会产生均匀分布,除非b恰好是32768

为此编写一个 bash 脚本

q*b如上所述的计算听起来很痛苦。但事实并非如此。您可以通过以下方式获取它:

q*b = 32768 - ( 32768 % b )
Run Code Online (Sandbox Code Playgroud)

在 Bash 中,您可以使用

$((32768 - $((32768 % b)) ))
Run Code Online (Sandbox Code Playgroud)

以下代码将生成范围内的随机数0..b(不包括b)。 b=$1

m=$((32768 - $((32768 % $1)) ))
a=$RANDOM
while (( $a > $m )); 
do
    a=$RANDOM
done
a=$(($a % $1))
printf "$a\n"
Run Code Online (Sandbox Code Playgroud)

附录

从技术上讲,几乎没有理由与之合作

m=$((32768 - $((32768 % $1)) ))
Run Code Online (Sandbox Code Playgroud)

以下将完成同样的事情

a=$RANDOM
while (( $a > $1 )); 
do
    a=$RANDOM
done
printf "$a\n"
Run Code Online (Sandbox Code Playgroud)

这是更多的工作,但计算机速度很快。

在更大范围内生成整数

我会让你弄清楚这一点。需要小心,并且在某些时候您必须考虑计算机在处理算术运算时的内存限制。

最后说明

接受的答案不会在 0 到 1 范围内均匀地创建随机数。

要看到这一点,请尝试以下操作

$ for i in {1..1000}; do echo .$RANDOM; done | awk '{ a += $1 } END { print a }'
Run Code Online (Sandbox Code Playgroud)

对于真正均匀的分布,[0,1)您应该看到平均值接近0.500

但是正如您通过运行上面的代码片段所看到的,您将得到类似314.432或 的内容322.619。由于它是 1000 个数字,因此它的平均值是.322. 这个生成数字序列的真实平均值是.316362

您可以使用 perl 脚本获得此真实平均值

  perl -e '{ $i=0;  
             $s=0; 
             while ( $i<=32767 ) 
               { 
                 $j = sprintf "%.5f", ".$i"; 
                 $j =~ s/^0\.//; 
                 print "$j\n"; 
                 $s += $j; 
                 $i++ 
               }; 
             printf "%.5f\n", $s/32767; 
           }' 
Run Code Online (Sandbox Code Playgroud)

我在这里添加整数是为了帮助您了解这种使用方法如何.$RANDOM没有做您最可能希望它做的事情。换句话说,考虑正在生成哪些整数以及完全遗漏哪些整数。跳过了相当多的数字;不少是翻倍的。


ImH*_*ere 7

在 shell 的 printf 能够理解%a格式(bash ksh zsh 等)并因此能够执行内部基数更改(十六进制 -> dec)([0,1)范围从 0.00003 到 0.99997 的统一)的系统上:

printf '%.5f\n' "$(printf '0x0.%04xp1' $RANDOM)"
Run Code Online (Sandbox Code Playgroud)

您甚至可以通过组合更多呼叫来使用更多数字$RANDOM(从 0.000000001 到 0.999999999)

printf '%.9f\n'  "$(printf '0x0.%08xp2' $(( ($RANDOM<<15) + $RANDOM )))"
Run Code Online (Sandbox Code Playgroud)

内部(外壳)“$RANDOM”算法基于线性反馈移位寄存器(LFSR)。这些不是加密安全的伪随机数生成器 (CSPRNG)。更好的选择是使用/dev/urandom设备中的字节。这将需要调用外部八进制或十六进制转储。

$ printf '%.19f\n' "0x0.$(od -N 8 -An -tx1 /dev/urandom | tr -d ' ')"
0.7532810412812978029

$ printf '%.19f\n' "0x0.$(hexdump -n 8 -v -e '"%02x"' /dev/urandom)"
0.9453460825607180595
Run Code Online (Sandbox Code Playgroud)

获得浮点数的一个非常简单(但不统一)的解决方案是:

printf '0.%04d\n' $RANDOM
Run Code Online (Sandbox Code Playgroud)

一种使其在范围内统一的方法[0,1)(不包括 1):

while a=$RANDOM; ((a>29999)); do :; done; printf '0.%04d\n' "$((a%10000))"
Run Code Online (Sandbox Code Playgroud)