如何在0和bigint之间选择一个随机值?

时间:2016-10-10 12:51:48

标签: perl random bigint

我有一个组合系统问题,我希望能够在0和一个大整数之间随机选择一个整数。

我当前方法的不足之处

现在对于常规整数,我通常会编写类似int rand 500;的内容并完成它。

但是对于大整数,看起来rand不适合这个。

使用以下代码,我运行了对rand $bigint的200万次调用的模拟:

$ perl -Mbigint -E 'say int rand 1230138339199329632554990773929330319360000000 for 1 .. 2e6' > rand.txt

结果集的分布远非理想:

  • 0(56个计数)
  • 幅度1e + 040(112个计数)
  • 幅度1e + 041(1411计数)
  • 幅度1e + 042(14496计数)
  • 幅度1e + 043(146324计数)
  • 幅度1e + 044(1463824计数)
  • 幅度1e + 045(373777计数)

因此,该流程永远无法选择9995e+020这样的数字,这使得此方法不适合我想要做的事情。

看起来这与rand的任意精度有关,在我的测试过程中它永远不会超过15位数:

$ perl -E 'printf "%.66g", rand'
0.307037353515625

我如何克服这个限制?

我最初的想法是,可能有一种方法可以影响rand的精确度,但感觉就像是一个更大问题的创可贴(即rand无法处理大问题整数)。

无论如何,我希望有人之前走过这条路,知道如何纠正这种情况。

3 个答案:

答案 0 :(得分:5)

(从我的评论中转换)

更理论化的方法是使用多次调用PRNG为您的数字创建足够的随机位进行采样。如果某个PRNG产生的比特数不等于下面所述的比特数,则必须小心!

伪代码

  • 计算代表您的号码所需的位:n_needed_bits
  • 检查PRNG返回的位数:n_bits_prng
  • 计算所需的样本数量:needed_prng_samples = ceil(n_needed_bits / n_bits_prng)
  • 虽然如此:
    • 示例needed_prng_samples(致PRNG)时间&连接所有获得的位
    • 检查结果数字是否在您的范围内
    • 是吗?:返回号码(已完成)
    • 否?:什么都不做(循环继续;将重新取样所有组件!)

说明

  • 这是acceptance-sampling / rejection-sampling
  • 的一种形式
  • 方法是Las-vegas type of algorithm:运行时在理论上不受限制
    • 所需的循环次数平均为:n_possible-sample-numbers-of-full-concatenation / n_possible-sample-numbers-within-range
  • 根据拒绝方法进行的完整重采样(如果结果不在范围内)可以访问非偏置/均匀性的更正式分析,这是此方法的一个非常重要的方面
  • 当然,需要有关PRNG输出的经典假设才能实现这一目标。
    • 例如,如果PRNG在低位/高位(如常提到的话)方面存在一些不均匀性,则会产生上述输出的影响

答案 1 :(得分:3)

我从错误的角度看这个问题

垃圾箱大小不一样。每个箱子的尺寸是前一个箱子的10倍。为了正确看待这一点,对于幅度为interviewed: true的每个整数,有10,000个可能的整数1e+44

1e+40找到bigint的任意数量1e+20的概率小于1e+45

在干草堆中忘记针,这更像是在类星体中寻找针!

答案 2 :(得分:1)

一种方法可以是将数字的字符串表示形式切割成块,初始化的布尔值($ low)为false,而第一次随机抽取等于上限。

编辑:在评论后添加了一些解释

# first argument (in) upper bound
# second argument (in/out) is lower (false while random returns upper bound, after it remains true)
sub randhlp {
    my($upp)=@_;
    my $l=length $upp;
    # random number less than
    # - upper bound if islower is false
    # - 9..99 otherwise
    my $x=int rand ($_[1] ? 10**$l : $upp+1);
    if ($x<$upp) {
        $_[1]=1;
    }
    # left padding with 0
    return sprintf("%0*d",$l,$x);
}

# returns a random number less than argument (numeric string)
sub randistr {
    my($n)=@_;
    $n=~/^\d+$/ or die "invalid input not numeric";
    $n ne "0" or die "invalid input 0";
    my($low,$x);
    do {
        undef $x;
        # split string by chunks of 6 characters
        # except last chunk which has 1 to 6 characters
        while ($n=~/.{1,6}/g) {
            # concatenate random results
            $x.=randhlp($&,$low)
        }
    } while ($x eq $n);
    $x=~s/^0+//;
    return $x;
}

测试

for ($i=0;$i<2e6;++$i) {
    $H{length(randistr("1230138339199329632554990773929330319360000000"))}+=1;
}

print "$_ $H{$_}\n" for sort keys %H;

返回

39 4
40 61
41 153
42 1376
43 14592
44 146109
45 1463301
46 374404