【问题标题】:How to generate a random double number in the inclusive [0,1] range? [duplicate]如何生成包含 [0,1] 范围内的随机双精度数? [复制]
【发布时间】:2021-03-17 19:51:59
【问题描述】:

以下代码在 [0,1) 范围内生成一个双精度数,这意味着 1 是互斥的。

var random = new Random();
random.NextDouble();

我正在寻找一些智能方法来生成 [0,1] 范围内的随机双精度数。这意味着 1 包含在内。我知道生成 0 或 1 的概率非常低,但想象一下我想实现一个正确的数学函数,该函数需要我的包容性限制。我该怎么做?

问题是:在 [0,1] 范围内生成随机数的正确方法是什么。如果没有这样的方法,我也很想学。

【问题讨论】:

  • 拥有1连续段[0..1]上的)的概率为0,即为什么你可以保留random.NextDouble();
  • @Dimitry 那不是也意味着在 [0..1] 中生成 0 的概率也是 .. 0 吗?然而,0 在 [0, 1) 中,如果有足够的样本,返回。 Double(和随机源)具有一组有限的值。
  • 双精度值没有精确值。如果你想要精确的值,你应该改用小数。查看这篇文章以了解随机十进制实现:stackoverflow.com/questions/609501/…
  • 一个双精度可以准确地表示值 1.0。由于小数仍然不能代表 [0,1] 中的所有数字,因此建议在问题的上下文中使用小数似乎有点愚蠢,尤其是因为它不会改变 NextDouble(或假设的 NextDecimal)的范围。
  • 如果我们假设 r.NextDouble() 可以产生 any 双精度值,那么就没有正确的方法来产生你想要的东西。您可以制定几种方案来获得值 1 作为可能的输出,但是获得该数字的结果机会将是倾斜的。如果我们假设 r.NextDouble() 不能产生从 0 到 1 的每一个可能的双精度值,则可以使用 r.Next(int.MaxInt) / (double)(int.MaxInt - 1)

标签: c# random


【解决方案1】:

洗完澡后,根据我对随机浮点生成器工作原理的理解,我构思了一个潜在的解决方案。我的解决方案做了三个假设,我认为这是合理的,但是 我无法验证这些假设是否正确。因此,以下代码纯属学术性质,我不建议在实践中使用它。假设如下:

  1. random.NextDouble()的分布是均匀的
  2. random.NextDouble() 产生的范围内任意两个相邻数字之间的差是一个常数 epsilon e
  3. random.NextDouble()产生的最大值等于1 - e

如果这三个假设是正确的,下面的代码会在 [0, 1] 范围内生成随机双精度数。

// For the sake of brevity, we'll omit the finer details of reusing a single instance of Random
var random = new Random();

double RandomDoubleInclusive() {
    double d = 0.0;
    int i = 0;

    do {
        d = random.NextDouble();
        i = random.Next(2);
    } while (i == 1 && d > 0)
    
    return d + i;
}

这有点难以概念化,但本质有点像下面抛硬币的解释,只不过不是从 0.5 开始,而是从 1 开始,如果在任何时候总和超过 1,则重新启动整个过程。

从工程的角度来看,这段代码是公然悲观的,几乎没有实际优势。然而,在数学上,只要最初的假设是正确的,那么结果将与最初的实现在数学上一样合理。

以下是关于随机浮点值的性质及其生成方式的原始评论。

原回复:

您的问题带有一个严重的错误假设:您使用了“正确”一词。我们正在处理浮点数。我们早就放弃了正确性。

以下是我对随机数生成器如何产生随机浮点值的粗略理解。

你有一个硬币,一个从零开始的总和,一个从二分之一 (0.5) 开始的值。

  1. 掷硬币。
  2. 如果正面,则将该值添加到总和中。
  3. 价值减半。
  4. 重复 23 次。

您刚刚生成了一个随机数。以下是数字的一些属性(供参考,2^23 是 8,388,608,2^(-23) 是它的倒数,或大约 0.0000001192):

  • 该数字是 2^23 个可能值之一
  • 最小值为0
  • 最大值为1 - 2^(-23);
  • 任何两个潜在值之间的最小差值为 2^(-23)
  • 这些值均匀分布在潜在值的范围内
  • 获得任何一个值的几率在整个范围内完全一致
  • 无论您掷硬币多少次,最后两点都是正确的
  • 生成数字的过程真的很简单

最后一点是关键。这意味着如果您可以生成原始熵(即完全一致的随机位),您可以在非常有用的范围内生成任意精确的数字,并且完全一致。这些都是很棒的属性。唯一需要注意的是它不会生成数字 1。

警告被认为是可以接受的原因是因为这一代人的其他方面都非常好。如果您试图获得介于 0 和 1 之间的高精度随机值,那么您实际上可能并不关心降落在 1 上,而不是关心降落在 0.38719 或该范围内的任何其他随机数上。

虽然有 方法可以将 1 包含在您的范围内(其他人已经说过),但它们都会在速度或均匀性方面花费您。我只是在这里告诉你,这实际上可能不值得权衡。

【讨论】:

  • 感谢您的评论。我为不够清楚而道歉。我知道你上面描述的权衡。当我问这个问题时,我想到的是如果我遗漏了什么,我不确定。我不是这方面的专家。我无法在互联网上找到任何人询问什么是在 [0,1] 范围内实现随机的好/正确/工作方式的问题。我意识到之前一定有人解决了这个问题。这就是我在论坛上问它的原因。
  • 我理解并道歉——我的目标不是提供一个明确的解决方案,而是解释为什么生成器会做它所做的事情,以造福未来的读者。您的问题是我过去也遇到过的问题。祝你好运,希望你能找到合适的解决方案。
  • 为了在上面的评论中添加更多内容,我经常为各种数学问题实施解决方案。数学问题定义明确,就像 [0,1] 范围内的随机数。当我在不遵循所有约束的情况下解决这样的问题时,我觉得我还没有真正解决问题;我只是简化了它。这就是为什么我很高兴看到更聪明的人分享他们将如何解决已定义的问题。
  • 洗完澡后,我想出了一个我认为在数学上合理的解决方案。
【解决方案2】:

如果你想要均匀分布,它比接缝更难。看how NextDouble is implemented

有一些方法可以在任意间隔内生成均匀分布的数字,一种简单的方法是选择性地丢弃一些生成的值。以下是我将如何解决您的问题。

/// <summary>Utility function to generate random 64-bit numbers</summary>
static ulong nextUlong( Random rand )
{
    Span<byte> buffer = stackalloc byte[ 8 ];
    rand.NextBytes( buffer );
    return BitConverter.ToUInt64( buffer );
}

/// <summary>Generate a random number in [ 0 .. +1 ] interval, inclusive.</summary>
public static double nextDoubleInclusive( Random rand )
{
    // We need uniformly distributed integer in [ 0 .. 2^53 ]
    // The interval contains ( 2^53 + 1 ) distinct values.

    // The complete range of ulong is [ 0 .. 2^64 - 1 ], 2^64 distinct values.
    // 2^64 / ( 2^53 + 1 ) is about 2047.99, here's why
    // https://www.wolframalpha.com/input/?i=2%5E64+%2F+%28+2%5E53+%2B+1+%29

    const ulong discardThreshold = 2047ul * ( ( 1ul << 53 ) + 1 );

    ulong src;
    do
    {
        src = nextUlong( rand );
    }
    while( src >= discardThreshold );
    // Got uniformly distributed value in [ 0 .. discardThreshold ) interval
    // Dividing by 2047 gets us a uniformly distributed value in [ 0 ..  2^53 ]
    src /= 2047;

    // Produce the result
    return src * ( 1.0 / ( 1ul << 53 ) );
}

【讨论】:

    【解决方案3】:

    通常,知道NextDouble() 有一个有限范围,我们乘以该值以适应我们需要的范围。

    因此,当内置逻辑不满足您的要求时,通常创建自己的包装器以产生下一个业务价值

    对于这个特定的例子,为什么不只是对结果进行后处理,当零从Next(0,2)获取值时

    public static double NextInclude1(this Random rand = null)
    {
        rand = rand ?? new Random();
        var result = rand.NextDouble();
        if (result == 0) result = rand.Next(0,2);
        return result;
    }
    

    您可以通过将比较更改为零来实现自己的 0 或 1 偏差,如果您这样做,虽然您可能会创建一个排除范围,所以在比较之后您可能需要返回 下一个 NextDouble()

    public static double NextInclude1(this Random rand = null)
    {
        rand = rand ?? new Random();
        var result = rand.NextDouble();
        if (result < 0.2) 
            result = rand.Next(0,2);
        else
            result = rand.NextDouble();
        return result;
    }
    

    此特定示例导致总体偏差为 0,由您决定您可以接受的具体参数,总体而言,NextDouble() 是您的大多数自定义随机需求的基本工具。

    【讨论】:

    • 您是否验证了结果的分布?有 1/N 的机会得到 0,然后有 1/2 的机会得到 0 或 1。这意味着所有数字都有 1/N 的机会产生,除了 0 或 1 有机会1/N*2.
    • 如果您对结果的特定分布感兴趣,并且无法轻松补偿,那么请不要使用Random 我猜。
    • @LasseV.Karlsen 只要 N 足够大(而且确实如此),这并不重要
    • 我之前删除的评论更适合这里的另一个答案,所以忽略这一点,但是根据您的最新回复,是的,这是极不可能的,但是 0 和 1 将是一半可能性极小。我的观点不是该解决方案不可用,只是您需要了解它的局限性。 0 和 1 的生成概率是 0 到 1 之间的任何其他数字的一半
    • @LasseV.Karlsen 是正确的,但这实际上取决于您要达到的精度。正如@ChrisSchaller 所说,如果您需要这种精度,您可能根本不应该使用System.Random
    【解决方案4】:

    Random.Next 方法返回[0..Int32.MaxValue) 范围内的整数值(右括号表示独占范围结束)。因此,如果您想让值 1.0 成为 NextDouble 方法 (source code) 的可能结果,您可以这样做:

    /// <summary>Returns a random floating-point number that is greater than or equal to 0.0,
    /// and less than or equal to 1.0.</summary>
    public static double NextDoubleInclusive(this Random random)
    {
        return (random.Next() * (1.0 / (Int32.MaxValue - 1)));
    }
    

    This fiddle 验证表达式 (Int32.MaxValue - 1) * (1.0 / (Int32.MaxValue - 1)) 的计算结果为 1.0

    【讨论】:

      【解决方案5】:

      这绝对有效,你可以在这里查看分布https://dotnetfiddle.net/SMMOrM

      Random random = new Random();
      double result = (int)(random.NextDouble() * 10) > 4
          ? random.NextDouble()
          : 1 - random.NextDouble();
      

      更新

      同意 Snoot,这个版本返回 0 和 1 的频率会比其他值低两倍

      【讨论】:

      • 您的版本返回 0 和 1 的频率比区间中的其余值低两倍。
      【解决方案6】:

      简单,你可以做到这一点

      var random = new Random();
      var myRandom = 1 - Math.Abs(random.NextDouble() - random.NextDouble());
      

      更新

      抱歉,这不会产生正态分布的结果,它们往往会更高,接近 1。

      【讨论】:

      • 您是否验证了结果的分布?这将更像是一个高斯曲线分布而不是平坦分布。
      • 是的,刚刚做了,不幸的是它倾向于更高的数字,所以接近 1 的数字的概率更高。 dotnetfiddle.net/7W7U2i
      猜你喜欢
      • 2012-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-07
      • 2011-03-01
      相关资源
      最近更新 更多