包含范围内的随机浮点双精度

IT技术 javascript c random language-agnostic
2021-03-10 05:52:09

我们可以[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]

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

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

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

6个回答

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

function randomInRange(min, max) {
  return Math.random() < 0.5 ? ((1-Math.random()) * (max-min) + min) : (Math.random() * (max-min) + min);
}
+1 表示对称性,但请注意1-Math.random()不会失去精度的隐含假设
2021-05-01 05:52:09
+1 不错!除了 0 和 1 的机会都是其余数字的一半。:)
2021-05-05 05:52:09

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

即使没有上溢/下溢/规范,也很难可靠地推断浮点运算。经过一番挖掘,我可以找到一个反例:

>>> 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是“偶数”[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),你将将永远不会产生使用这种方法(在任何(0,2 -53)与IEEE双打)。

这也意味着您可以这样做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 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,很容易验证它是否合理:

  • 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 这样做)。要生成双精度数,您需要从两个连续的输出中取出 53 位并除以 2 53,但有些输出是不可能的(您不能生成 2 53具有 48 位状态的输出!)。我怀疑其中一些永远不会返回 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

..Double.MAX_VALUE作为上限处理的正确代码是什么?
2021-05-01 05:52:09

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

例子:如果你想在 [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,我认为不存在不存在的解决方案t 可能会无限次调用。

它在数学上工作,但在浮点世界中,不能保证它永远返回max
2021-05-08 05:52:09

鉴于 0 和 1 之间的“非常大”数量的值,这真的很重要吗?实际命中 1的机会很小,因此不太可能对您正在做的任何事情产生重大影响。

不,我的问题没有实际应用。我只是好奇 =)
2021-05-20 05:52:09