包含范围内的随机浮点双

由于Math.random() (以及大多数伪随机数生成器,AFAIK)产生数字,因此我们可以很容易地获得所需范围[X,Y)内的随机浮点数(注意X是包含的,Y是独占的)和下面列出的函数在[0,1)

 function randomInRange(min, max) { return Math.random() * (max-min) + min; } // Notice that we can get "min" exactly but never "max". 

我们怎样才能得到一个包含两个边界的期望范围内的随机数,即[X,Y]

我想我们可以通过“滚动” IEE-754浮点双精度的位来“增加”我们从Math.random() (或等效物)的值,以将最大可能值精确地设置为1.0,但这似乎很痛苦。做得对,特别是在不适合位操作的语言中。 有没有更简单的方法?

(顺便说一下,为什么随机数生成器在[0,1)而不是[0,1]产生数字?)

[编辑]请注意,我没有要这样做,我完全清楚这种区别是迂腐的。 只是好奇并希望得到一些有趣的答案。 如果这个问题不合适,请随意投票结束。

我相信有更好的决定,但这个应该工作:)

 function randomInRange(min, max) { return Math.random() < 0.5 ? ((1-Math.random()) * (max-min) + min) : (Math.random() * (max-min) + min); } 

首先,代码中存在问题:尝试randomInRange(0,5e-324)或在浏览器的JavaScript控制台中输入Math.random()*5e-324

即使没有溢出/下溢/变形,也很难对浮点运算进行可靠的推理。 经过一番挖掘,我可以找到一个反例:

 >>> a=1.0 >>> b=2**-54 >>> rand=a-2*b >>> a 1.0 >>> b 5.551115123125783e-17 >>> rand 0.9999999999999999 >>> (ab)*rand+b 1.0 

更容易解释为什么在a = 2 53和b = 0.5时发生这种情况:2 53 -1是下一个可表示的数字。 默认的舍入模式(“舍入到最接近的偶数”)向上舍入2 53 -0.5(因为2 53是“偶数”[LSB = 0]而2 53 -1是“奇数”[LSB = 1]),所以你减去b得到2 53 ,乘以得到2 53 -1,再加上b得到2 53


回答你的第二个问题:因为底层PRNG几乎总是在区间[0,2 n -1]中生成一个随机数,即它产生随机位。 选择合适的n(浮点表示中的精度位)并除以2 n并获得可预测的分布非常容易。 请注意, [0,1)中有一些数字,您将永远不会使用此方法生成(使用IEEE双精度( 0,2-53 )中的任何数字)。

这也意味着你可以做a[Math.floor(Math.random()*a.length)]而不用担心溢出(作业:在IEEE二进制浮点中,certificateb < 1意味着a*b < a for正整数a )。

另一件好事是,您可以将每个随机输出x视为表示间隔[x,x + 2 -53 )(不太好的是返回的平均值略小于0.5)。 如果你在[0,1]中返回,你是否以与其他所有相同的概率返回端点,或者它们是否只有一半的概率,因为它们只代表其他所有区间的一半?

为了回答在[0,1]中返回数字这个更简单的问题,下面的方法有效地生成一个整数[0,2 n ](通过在[0,2 n + 1 -1]中生成一个整数并将其抛弃,如果它太大了)除以2 n

 function randominclusive() { // Generate a random "top bit". Is it set? while (Math.random() >= 0.5) { // Generate the rest of the random bits. Are they zero? // If so, then we've generated 2^n, and dividing by 2^n gives us 1. if (Math.random() == 0) { return 1.0; } // If not, generate a new random number. } // If the top bits are not set, just divide by 2^n. return Math.random(); } 

评论意味着基数2,但我认为这些假设是:

  • 应该等概率地返回0和1(即Math.random()不使用0附近的浮点数的更近间距)。
  • Math.random()> = 0.5,概率为1/2(偶数基数应为真)
  • 潜在的PRNG足够好,我们可以做到这一点。

请注意,随机数总是成对生成: while (a)中的一个始终后跟if的一个或末尾的一个(b)。 通过考虑返回0或0.5的PRNG来validation它是否合理是相当容易的:

  • a=0 b=0 :返回0
  • a=0 b=0.5 :返回0.5
  • a=0.5 b=0 :返回1
  • a=0.5 b=0.5 :循环

问题:

  • 这些假设可能不正确。 特别是,常见的PRNG是采用48位LCG的前32位(Firefox和Java这样做)。 要生成一个double,您需要从两个连续输出中取53位并除以2 53 ,但有些输出是不可能的(您不能生成具有48位状态的2 53个输出!)。 我怀疑其中一些永远不会返回0(假设是单线程访问),但我现在不想检查Java的实现。
  • 由于需要获得额外的位,Math.random()对于每个潜在的输出是两倍,但是这对PRNG施加了更多约束(要求我们推断上述LCG的四个连续输出)。
  • 每个输出平均调用Math.random()大约四次 。 有点慢。
  • 它确定性地抛弃结果(假设单线程访问),因此几乎可以保证减少输出空间。

我对这个问题的解决方案一直是使用以下代替你的上限。

 Math.nextAfter(upperBound,upperBound+1) 

要么

 upperBound + Double.MIN_VALUE 

所以你的代码看起来像这样:

 double myRandomNum = Math.random() * Math.nextAfter(upperBound,upperBound+1) + lowerBound; 

要么

 double myRandomNum = Math.random() * (upperBound + Double.MIN_VALUE) + lowerBound; 

这只是将您的上限增加最小的double( Double.MIN_VALUE ),以便在随机计算中包含您的上限。

这是一个很好的方法,因为它不会偏向任何一个数字的概率。

唯一不起作用的情况是上限等于Double.MAX_VALUE

只需选择稍微大一点的半开区间,这样您选择的闭区间就是一个子集。 然后,继续生成随机变量,直到它落入所述闭合间隔。

示例:如果在[3,8]中需要一些统一的东西,则在[3,9]中重复生成一个均匀的随机变量,直到它碰巧落在[3,8]中。

 function randomInRangeInclusive(min,max) { var ret; for (;;) { ret = min + ( Math.random() * (max-min) * 1.1 ); if ( ret <= max ) { break; } } return ret; } 

注意:生成半开放RV的次数是随机的并且可能是无限的,但是您可以根据需要将预期的呼叫数量设置为接近1,并且我认为不存在解决方案可能无限次呼叫。

鉴于介于0和1之间的“极大”数值,它真的重要吗? 实际击中1的几率很小,因此不太可能对你正在做的任何事情产生重大影响。

在什么情况下你需要一个浮点值来包含上限? 对于我理解的整数,但对于浮点数,包含和排除之间的差异就像1.0e-32。

这样想吧。 如果您认为浮点数具有任意精度,则精确得到min的几率为零。 获得max的机会也是如此。 我会让你自己得出结论。

这个’问题’相当于在0和1之间的实线上得到一个随机点。没有’包容性’和“排他性”。

问题类似于询问, 1.0之前的浮点数是多少? 有这样一个浮点数,但是它是2 ^ 24中的一个(对于IEEE float )或者是2 ^ 53中的一个(对于double float )。

在实践中差异可以忽略不计。

我的经验相当不足,所以我也在寻找解决方案。

这是我粗略的想法:

随机数生成器在[0,1)而不是[0,1]中生成数字,

因为[0,1)是一个单位长度,后面跟着[1,2]等等而没有重叠……

对于随机[x,y],您可以这样做:

 float randomInclusive(x, y){ float MIN = smallest_value_above_zero; float result; do{ result = random(x, (y + MIN)); } while(result > y); return result; } 

如果[x,y]中的所有值都具有相同的可能性,那么你现在可以达到y。

如果这不起作用或有潜在问题,请告诉我。

谢谢〜

 private static double random(double min, double max) { final double r = Math.random(); return (r >= 0.5d ? 1.5d - r : r) * (max - min) + min; } 

Math.round()将有助于包含绑定值。 如果您有0 <= value < 1 (1是独占),则Math.round(value * 100) / 100返回0 <= value <= 1 (包括1)。 这里注意的是,该值现在只有小数位的2位数。 如果您想要3位数,请尝试Math.round(value * 1000) / 1000 ,依此类推。 以下函数还有一个参数,即小数位数 - 我称之为精度

 function randomInRange(min, max, precision) { return Math.round(Math.random() * Math.pow(10, precision)) / Math.pow(10, precision) * (max - min) + min; }