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
没有做您最可能希望它做的事情。换句话说,考虑正在生成哪些整数以及完全遗漏哪些整数。跳过了相当多的数字;不少是翻倍的。
在 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)