![Bash:如何使用 $RANDOM 生成随机浮点数](https://linux22.com/image/127853/Bash%EF%BC%9A%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%20%24RANDOM%20%E7%94%9F%E6%88%90%E9%9A%8F%E6%9C%BA%E6%B5%AE%E7%82%B9%E6%95%B0.png)
是否可以使用整数随机生成器 $RANDOM 生成具有特定精度和特定范围的真实随机数?例如,我们如何生成 0 到 1 之间 4 精度的实数?
0.1234
0.0309
0.9001
0.0000
1.0000
一个简单的解决方法:
printf "%d04.%d04\n" $RANDOM $RANDOM
答案1
awk -v n=10 -v seed="$RANDOM" 'BEGIN { srand(seed); for (i=0; i<n; ++i) printf("%.4f\n", rand()) }'
这将输出n
[0,1) 范围内的随机数(示例中为 10 个),具有四位十进制数字。它使用rand()
in 函数awk
(不是标准的awk
,而是由最常见的awk
实现实现的)返回该范围内的随机值。随机数生成器由 shell 的$RANDOM
变量作为种子。
当awk
程序只有BEGIN
块(并且没有其他代码块)时,awk
将不会尝试从其标准输入流读取输入。
在任何 OpenBSD 系统(或具有相同jot
公用事业,最初在 4.2BSD 中),以下将生成指定的 10 个随机数:
jot -p 4 -r 10 0 1
答案2
正如另一个答案中指出的,还有其他实用程序可用于生成随机数。在这个答案中,我将我的资源限制为$RANDOM
和一些基本算术函数。
对于浮点数,尝试类似的方法
printf '%s\n' $(echo "scale=8; $RANDOM/32768" | bc )
这将为您提供最佳精度,因为$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
将使您落在所需的范围内。
生成整数值
[0,b)
首先,考虑在小于b
之间生成随机数32768
。考虑乘积q*b
,其中q
是 的整数部分32768/b
。那么你可以做的是生成 0 到 32767 之间的随机数,但丢弃那些大于或等于 的随机数q*b
。拨打由此生成的号码G
。那么G
将落在 0 到 的范围内q*b
,并且其分布将是均匀的。现在,应用模算术将该值缩减到所需的范围:
G % b
注意,随机生成一个数字如下
$RANDOM % b
不会创建均匀分布,除非b
恰好是 的约数之一32768
。
为此编写 bash 脚本
如上所述的计算q*b
听起来很痛苦。但事实并非如此。您可以通过以下方式获取:
q*b = 32768 - ( 32768 % b )
在 Bash 中,你可以使用
$((32768 - $((32768 % b)) ))
下面的代码将生成一个范围内的随机数0..b
(不包括b
)。 b=$1
m=$((32768 - $((32768 % $1)) ))
a=$RANDOM
while (( $a > $m ));
do
a=$RANDOM
done
a=$(($a % $1))
printf "$a\n"
附录
从技术上讲,没有什么理由与之合作
m=$((32768 - $((32768 % $1)) ))
下面将完成同样的事情
a=$RANDOM
while (( $a > $1 ));
do
a=$RANDOM
done
printf "$a\n"
虽然工作量很大,但计算机速度很快。
生成更大范围内的整数
我会让你解决这个问题。需要小心,在某些时候您必须考虑计算机在处理算术运算时的内存限制。
最后说明
接受的答案不会创建统一在 0 到 1 之间的随机数。
要查看此内容,请尝试以下操作
$ for i in {1..1000}; do echo .$RANDOM; done | awk '{ a += $1 } END { print a }'
对于真正均匀的分布,[0,1)
您应该看到平均值接近0.500
。
但正如您通过运行上面的代码片段所看到的,您将得到类似314.432
or 的内容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;
}'
我在此处添加整数是为了帮助您了解这种使用方法为何.$RANDOM
没有实现您最可能希望它执行的操作。换句话说,想想哪些整数正在生成,哪些整数被完全丢失。相当多的内容被跳过;相当多的都翻倍了。
答案3
在 shell 的 printf 能够理解%a
格式(bash ksh zsh 等)并因此能够执行内部基数更改(十六进制 -> 十进制)(统一[0,1)
范围从 0.00003 到 0.99997)的系统上:
printf '%.5f\n' "$(printf '0x0.%04xp1' $RANDOM)"
您甚至可以通过组合更多调用来使用更多数字$RANDOM
(从 0.000000001 到 0.999999999)
printf '%.9f\n' "$(printf '0x0.%08xp2' $(( ($RANDOM<<15) + $RANDOM )))"
内部(对于 shell)“$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
获得浮点数的一个非常简单(但不均匀)的解决方案是:
printf '0.%04d\n' $RANDOM
使其在范围内[0,1)
(不包括1)统一的一种方法:
while a=$RANDOM; ((a>29999)); do :; done; printf '0.%04d\n' "$((a%10000))"
答案4
在bash上
bc -l <<< "scale=4 ; $((RANDOM % 10000 ))/10000"
1/10000
你的随机精度和数字是你4
的输出精度