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

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

是否可以使用整数随机生成器 $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.432or 的内容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的输出精度

相关内容