包含范围内的随机浮点双

时间:2012-03-15 16:52:40

标签: javascript c random language-agnostic

我们可以很容易地获得所需范围内的随机浮点数[X,Y)(注意X是包含的,Y是独占的),使用下面列出的函数Math.random()(以及大多数伪随机数生成器,AFAIK) )在[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 floating point double precision的位来“增加”Math.random()(或等效)的值,使最大可能值精确到1.0,但这似乎很痛苦要做到正确,特别是在不适合位操作的语言中。有更简单的方法吗?

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

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

13 个答案:

答案 0 :(得分:13)

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

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

答案 1 :(得分:5)

首先,您的代码存在问题:请尝试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
>>> (a-b)*rand+b
1.0

更容易解释为什么这种情况发生在a = 2 53 且b = 0.5:2 53 -1是下一个可表示的数字。默认的舍入模式(“舍入到最接近的偶数”)将2 53 -0.5向上舍入(因为2 53 是“even”[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二进制浮点中,证明b < 1暗示a*b < a正整数a )。

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

为了回答在[0,1]中返回数字的简单问题,下面的方法有效地生成一个整数[0,2 n ](通过在[0,2]中生成一个整数sup> 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()&gt; = 0.5,概率为1/2(偶数基数应该为真)
  • 基础PRNG足够好,我们可以做到这一点。

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

  • a=0   b=0  :return 0
  • a=0   b=0.5:返回0.5
  • a=0.5 b=0  :return 1
  • a=0.5 b=0.5:loop

问题:

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

答案 2 :(得分:5)

我对此问题的解决方法一直是使用以下代替上限。

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.MIN_VALUE),以便在随机计算中包含上限。

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

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

答案 3 :(得分:4)

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

示例:如果你想在[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;
}

注意:你产生半开R.V的次数。是随机的,可能是无限的,但你可以根据自己的意愿调整预期的调用次数,并且我认为不存在可能无限次调用的解决方案。

答案 4 :(得分:1)

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

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

如果您需要浮点值包含上限,那么会出现什么情况?对于我理解的整数,但对于浮点数,包含和排他之间的差异就像1.0e-32。

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

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

答案 9 :(得分:0)

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

这是我粗略的想法:

随机数生成器在[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。

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

感谢〜

答案 10 :(得分:0)

Math.round()将有助于包含绑定值。如果你有0&lt; = value&lt; 1(1是独占的),然后Math.round(值* 100)/ 100返回0&lt; = value&lt; = 1(包括1)。这里注意的是,该值现在只有小数位的2位数。如果您想要3位数,请尝试Math.round(值* 1000)/ 1000,依此类推。以下函数还有一个参数,即小数位数 - 我称为精度: 函数randomInRange(min,max,precision){     返回Math.round(Math.random()* Math.pow(10,precision))/             Math.pow(10,精度)*(最大 - 最小)+ min; }

答案 11 :(得分:0)

怎么样?

function randomInRange(min, max){
    var n = Math.random() * (max - min + 0.1) + min;
    return n > max ? randomInRange(min, max) : n;
}

如果您对此感到满满,我会给您买礼物。

- 编辑:不要介意现在。我迷上了:

randomInRange(0, 0.0000000000000000001)

并且堆栈溢出。

答案 12 :(得分:0)

生成一个范围内的随机浮点数并非易事。例如,将随机整数乘以常数或通过将一个统一的浮点数缩放到所需范围的通用做法具有以下缺点:并非浮点格式可以表示该范围内的所有数字都可以涵盖这种方式,并可能存在细微的偏差问题。 F. Goualard在“ Generating Random Floating-Point Numbers by Dividing Integers: a Case Study”中详细讨论了这些问题。

仅为了说明问题的严重程度,以下伪代码在闭合间隔[lo,hi]中生成随机浮点数,其形式为FPSign * FPSignificand * FPRADIX ^ FPExponent。以下伪代码摘自我在floating-point number generation上的部分。请注意,它适用于浮点数的任何精度和任何基数(包括二进制和十进制)。

METHOD RNDRANGE(lo, hi)
  losgn = FPSign(lo)
  hisgn = FPSign(hi)
  loexp = FPExponent(lo)
  hiexp = FPExponent(hi)
  losig = FPSignificand(lo)
  hisig = FPSignificand(hi)
  if lo > hi: return error
  if losgn == 1 and hisgn == -1: return error
  if losgn == -1 and hisgn == 1
    // Straddles negative and positive ranges
    // NOTE: Changes negative zero to positive
    mabs = max(abs(lo),abs(hi))
    while true
       ret=RNDRANGE(0, mabs)
       neg=RNDINT(1)
       if neg==0: ret=-ret
       if ret>=lo and ret<=hi: return ret
    end
  end
  if lo == hi: return lo
  if losgn == -1
    // Negative range
    return -RNDRANGE(abs(lo), abs(hi))
  end
  // Positive range
  expdiff=hiexp-loexp
  if loexp==hiexp
    // Exponents are the same
    // NOTE: Automatically handles
    // subnormals
    s=RNDINTRANGE(losig, hisig)
    return s*1.0*pow(FPRADIX, loexp)
  end
  while true
    ex=hiexp
    while ex>MINEXP
      v=RNDINTEXC(FPRADIX)
      if v==0: ex=ex-1
      else: break
    end
    s=0
    if ex==MINEXP
      // Has FPPRECISION or fewer digits
      // and so can be normal or subnormal
      s=RNDINTEXC(pow(FPRADIX,FPPRECISION))
    else if FPRADIX != 2
      // Has FPPRECISION digits
      s=RNDINTEXCRANGE(
        pow(FPRADIX,FPPRECISION-1),
        pow(FPRADIX,FPPRECISION))
    else
      // Has FPPRECISION digits (bits), the highest
      // of which is always 1 because it's the
      // only nonzero bit
      sm=pow(FPRADIX,FPPRECISION-1)
      s=RNDINTEXC(sm)+sm
    end
    ret=s*1.0*pow(FPRADIX, ex)
    if ret>=lo and ret<=hi: return ret
  end
END METHOD