【问题标题】:How to generate a number in arbitrary range using random()={0..1} preserving uniformness and density?如何使用 random()={0..1} 生成任意范围内的数字,保持均匀性和密度?
【发布时间】:2011-11-05 10:32:05
【问题描述】:

在 [x..y] 范围内生成一个随机数,其中 x 和 y 是任意浮点数。使用函数 random(),它从 P 个均匀分布的数字中返回一个范围为 [0..1] 的随机浮点数(称为“密度”)。必须保持均匀分布,P 也必须按比例缩放。

我认为,此类问题没有简单的解决方案。为了简化一点,我问你如何在区间 [-0.5 .. 0.5] 中生成一个数字,然后在 [0 .. 2] 中,然后在 [-2 .. 0] 中,保持均匀性和密度?因此,对于 [0 .. 2],它必须从 P*2 均匀分布的数中生成一个随机数。

显而易见的简单解决方案random() * (x - y) + y 不会生成所有可能的数字,因为所有abs(x-y)>1.0 案例的密度都较低。许多可能的值将被遗漏。请记住,random() 仅返回 P 个可能数字中的一个数字。然后,如果你将这个数字乘以 Q,它只会给你 P 个可能的值之一,按 Q 缩放,但你还必须将密度 P 缩放 Q。

【问题讨论】:

  • 阅读您的 cmets,您似乎已经知道答案(或者您的 cmets 不会那么迫切,对吗?)我想看看您对这个问题的解决方案,关于为什么您的解决方案有效而不是其他回答者给出的争论性讨论。
  • 您是否正在寻找所有 IEEE 浮点表示的统一分布?或者您是否正在寻找数字范围内的均匀分布,以浮点数表示?只有当您所需的分辨率可以用尾数表示时,后者才有可能。
  • 最好的起点是http://en.wikipedia.org/wiki//dev/random - 因为你对“保持均匀性和密度”的定义与我的不同,这让我想起了康托尔的不同层次的无穷大;。

标签: algorithm random entropy


【解决方案1】:

如果我能很好地理解你的问题,我会为你提供解决方案:但我会从范围中排除 1。

N = numbers_in_your_random // [0, 0.2, 0.4, 0.6, 0.8] will be 5

// This turns your random number generator to return integer values between [0..N[;
function randomInt()
{
    return random()*N;
}

// This turns the integer random number generator to return arbitrary
// integer
function getRandomInt(maxValue)
{
    if (maxValue < N)
    {
        return randomInt() % maxValue;
    }
    else
    {
        baseValue = randomInt();
        bRate = maxValue DIV N;
        bMod = maxValue % N;
        if (baseValue < bMod)
        {
            bRate++;
        }
        return N*getRandomInt(bRate) + baseValue;
    }
}

// This will return random number in range [lower, upper[ with the same density as random()
function extendedRandom(lower, upper)
{
    diff = upper - lower;
    ndiff = diff * N;
    baseValue = getRandomInt(ndiff);
    baseValue/=N;
    return lower + baseValue;
}

【讨论】:

  • 您的函数不会生成范围内所有可能的数字(下限、上限)。如果上限和下限很大,许多可能的数字将被遗漏。
  • 嗯,这取决于你使用的随机函数。有些函数可以生成 32768 个数字,有些函数可以生成 4294967296 个数字。
  • 想象一下你的 random() 只返回 0、0.5 或 1。将你的方法应用于lower=0 和upper=100,你会发现问题
  • 啊哈!您应该编辑您的问题,以便我们不能假设您的随机数是连续的,但可能会返回均匀分布的离散数字。而且您想要一个函数统一返回数字并保持它们之间的相同距离,不是吗?顺便说一句,显而易见的解决方案使其保持一致,但数字之间的距离会更大。 :)
  • 请阅读我更新的问题,我已经指定了一些要点
【解决方案2】:

如果你真的想在给定的范围内生成所有可能的浮点数,并且具有统一的数字密度,你需要考虑浮点格式。对于二进制指数的每个可能值,您都有不同的代码数字密度。直接生成方法需要明确地处理这个问题,而间接生成方法仍然需要考虑到它。我将开发一种直接的方法;为简单起见,以下仅指IEEE 754 单精度(32 位)浮点数。

最困难的情况是任何包含零的区间。在这种情况下,要产生完全均匀的分布,您需要将每个指数处理到最低,再加上非规范化的数字。作为一种特殊情况,您需要将零分为两种情况,+0 和 -0。

此外,如果您如此密切地关注结果,您将需要确保您使用的是具有足够大状态空间的良好伪随机数生成器,您可以期望它以接近 -均匀概率。这取消了 C/Unix rand() 和可能的*rand48() 库函数的资格;你应该改用Mersenne Twister 之类的东西。


关键是将目标区间分解为子区间,每个子区间被二进制指数和符号的不同组合覆盖:在每个子区间内,浮点代码是均匀分布的。

第一步是选择合适的子区间,概率与其大小成正比。如果间隔包含 0,或者以其他方式覆盖大的动态范围,则这可能潜在地需要多个随机位,直到可用指数的整个范围。

特别是,对于 32 位 IEEE-754 数字,有 256 个可能的指数值。每个指数控制一个范围,该范围是下一个更大指数大小的一半,但非规范化情况除外,它与最小的正常指数区域大小相同。零可以被认为是最小的非规格化数;如上所述,如果目标区间跨越零,则 +0 和 -0 的概率可能应该减半,以避免其权重加倍。

如果选择的子区间覆盖了由特定指数控制的整个区域,那么只需用随机位(23 位,对于 32 位 IEEE-754 浮点数)填充尾数。但是,如果子区间未覆盖整个区域,则需要生成仅覆盖该子区间的随机尾数。

处理初始和次要随机步骤的最简单方法可能是将目标区间四舍五入以包括部分覆盖的所有指数区域的整体,然后拒绝并重试超出它的数字。这允许以简单的 2 次幂概率生成指数(例如,通过计算随机比特流中前导零的数量),并提供一种简单而准确的方法来生成仅涵盖部分的尾数指数区间。 (这也是处理 +/-0 特殊情况的好方法。)

作为另一种特殊情况:为了避免生成远小于它们所在的指数区域的目标区间,“明显简单”的解决方案实际上将为此类区间生成相当统一的数字。如果您想要完全均匀分布,您可以通过仅使用足够的随机位来覆盖该子区间来生成子区间尾数,同时仍然使用上述拒绝方法来消除目标区间之外的值。

【讨论】:

  • 您有/可以指出实现您建议的代码吗?
  • 对不起,不;这只是第一原则的草图。
  • 让我看看我是否理解正确。例如,如果我的目标范围是 [1.0, 8.0),我会选择概率 p 的符号/指数对 +/0、概率 2p 的 +/1 和概率 4p 的 +/2,其中 7p = 1。如果我的区间是 [1.0, 9.0),这个方案会超重这对 +/3 吗? – pholser 18 分钟前
  • 简单的拒绝机制会将 [1.0,9.0) 区间舍入到 [1.0,16.0),然后在结果 >= 9.0 时重试(这不是特别有效,但不是糟糕,要么......)
  • 对于区间 [1.0, 9.0),符号/指数对和概率是否为:+/0:p、+/1:2p、+/2:4p 和 +/3: p,8p = 1,然后命中 +/3 >= 9.0 被丢弃?
【解决方案3】:

嗯,[0..1] * 2 == [0..2](还是统一的)

[0..1] - 0.5 == [-0.5..0.5]

不知道你在哪里经历过这样的采访?

更新:好吧,如果我们想开始关心乘法的精度损失(这很奇怪,因为不知何故你在原始任务中并不关心这一点,并假装我们关心“数字的值”,我们可以开始迭代。为了做到这一点,我们还需要一个函数,它会在[0..1) 中返回均匀分布的随机值——这可以通过删除1.0 值来完成。也就是说,我们可以将整个范围分成足够小的等分部分,而不用担心丢失精度,随机选择一个(我们有足够的随机性来做到这一点),然后使用 [0..1) 函数在这个桶中为所有选择一个数字部分,但最后一个。

或者,您可以想出一种方法来编码足够多的值来关心 - 并为此代码生成随机位,在这种情况下,您并不真正关心它是 [0..1] 还是只是 {0 , 1}.

【讨论】:

  • 使用 [0..1]*2 会忽略几乎一半的可能值
  • @psihodelia 查看更新;它远远超出了我能想象的任何面试或实际任务。首先,“来自 [0..1] 的随机 FP 数”与“来自 [0..1] 的随机数的 FP 表示”不同:你越接近零,你的 FP 值就越多有。
  • 请阅读我更新的问题,我已经指定了一些要点
  • 查看更新后的答案 :) 分割,随机获取一部分,处理重叠点——就是这样。
【解决方案4】:

让我重新表述你的问题:

random() 成为在[0,1) 上具有离散均匀分布的随机数生成器。令Drandom() 返回的可能值的数量,每个值都恰好比1/D 大。创建一个随机数生成器rand(L, U),其在[L, U) 上具有离散均匀分布,使得每个可能的值都精确地比前一个大1/D

--

一些简短的笔记。

  1. 这种形式的问题,正如你所说的,它是无法解决的。那 也就是说,如果 N = 1,我们无能为力。
  2. 我不要求0.0random() 的可能值之一。如果不是,那么当U - L &lt; 1 / D 时,下面的解决方案可能会失败。我并不特别担心那个案子。
  3. 我使用所有半开范围,因为它使分析更简单。使用封闭范围很简单,但也很乏味。

最后,好东西。这里的关键见解是,可以通过独立选择结果的整体和小数部分来保持密度。

首先,请注意给定random(),创建randomBit() 是微不足道的。也就是说,

randomBit() { return random() >= 0.5; }

然后,如果我们想随机选择{0, 1, 2, ..., 2^N - 1} 中的一个,使用randomBit() 很简单,只需生成每个位。打电话给random2(N)

使用random2() 我们可以选择{0, 1, 2, ..., N - 1} 之一:

randomInt(N) { while ((val = random2(ceil(log2(N)))) >= N); return val; }

现在,如果知道D,那么问题就很简单了,因为我们可以将其简化为简单地随机选择floor((U - L) * D) 值之一,我们可以使用randomInt() 来做到这一点。

所以,让我们假设D 是未知的。现在,让我们首先创建一个函数来生成具有适当密度的[0, 2^N) 范围内的随机值。这很简单。

rand2D(N) { return random2(N) + random(); }

rand2D() 是我们要求random() 的连续可能值之间的差异精确为1/D 的地方。如果不是,这里的可能值将不会具有均匀的密度。

接下来,我们需要一个函数,在[0, V) 范围内选择一个具有适当密度的值。这类似于上面的randomInt()

randD(V) { while ((val = rand2D(ceil(log2(V)))) >= V); return val; }

最后……

rand(L, U) { return L + randD(U - L); }

如果L / D 不是整数,我们现在可能已经偏移了离散位置,但这并不重要。

--

最后一点,您可能已经注意到其中一些函数可能永远不会终止。这本质上是一个要求。例如,random() 可能只有一位随机性。如果我随后要求您从三个值之一中进行选择,则您无法使用保证终止的函数随机地统一执行此操作。

【讨论】:

    【解决方案5】:

    考虑这种方法:

    我假设基本随机数生成器在[0..1] 范围内 在数字中生成

    0, 1/(p-1), 2/(p-1), ..., (p-2)/(p-1), (p-1)/(p-1)

    如果目标区间长度小于等于1, 返回random()*(y-x) + x

    否则,将基础 RNG 中的每个数字 r 映射到 目标范围:

    [r*(p-1)*(y-x)/p, (r+1/(p-1))*(p-1)*(y-x)/p]

    (即为每个 P 编号分配长度为 (y-x)/p 的 P 个间隔之一)

    然后在该区间内递归生成另一个随机数并 将它添加到间隔开始。

    伪代码:

    const p;
    
    function rand(x, y)
      r = random()
      if y-x <= 1
        return x + r*(y-x)
      else
        low = r*(p-1)*(y-x)/p
        high = low + (y-x)/p
        return x + low + rand(low, high)
    

    【讨论】:

      【解决方案6】:

      在实际数学中:解决方案只是提供的:

      return random() * (upper - lower) + lower
      

      问题是,即使你有浮点数,也只有一定的分辨率。所以你可以做的是应用上面的函数并添加另一个随机()值缩放到缺失的部分。

      如果我举一个实际的例子,我的意思就很清楚了:

      例如从 0..1 中获取 random() 返回值,精度为 2 位,即 0.XY,下限为 100,上限为 1100。

      因此,使用上述算法,结果为 0.XY * (1100-100) + 100 = XY0.0 + 100。 你永远不会看到 201 作为结果,因为最后一个数字必须是 0。

      这里的解决方案是再次生成一个随机值并将其添加 *10,因此您的精度为一位数(在这里您必须注意不要超出给定范围,这可能会发生,在这种情况下您有丢弃结果并生成一个新数字)。

      也许您必须重复一遍,频率取决于 random() 函数提供多少个位置以及您对最终结果的期望值。

      在标准 IEEE 格式中具有有限的精度(即双 53 位)。因此,当您以这种方式生成一个数字时,您永远不需要生成多个额外的数字。

      但是您必须小心,当您添加新数字时,不要超过给定的上限。有多种解决方案:首先,如果超出限制,则从新开始,生成一个新数字(不要截断或类似,因为这会改变分布)。

      第二种可能性是检查丢失的低位范围的间隔大小,并且 找到中间值,生成一个合适的值,保证结果合适。

      【讨论】:

        【解决方案7】:

        您必须考虑每次调用 RNG 所产生的熵量。这是我刚刚编写的一些 C# 代码,演示了如何从低熵源累积熵并最终得到高熵随机值。

        using System;
        using System.Collections.Generic;
        using System.Security.Cryptography;
        
        namespace SO_8019589
        {
          class LowEntropyRandom
          {
            public readonly double EffectiveEntropyBits;
            public readonly int PossibleOutcomeCount;
            private readonly double interval;
            private readonly Random random = new Random();
            public LowEntropyRandom(int possibleOutcomeCount)
            {
              PossibleOutcomeCount = possibleOutcomeCount;
              EffectiveEntropyBits = Math.Log(PossibleOutcomeCount, 2);
              interval = 1.0 / PossibleOutcomeCount;
            }
            public LowEntropyRandom(int possibleOutcomeCount, int seed)
              : this(possibleOutcomeCount)
            {
              random = new Random(seed);
            }
            public int Next()
            {
              return random.Next(PossibleOutcomeCount);
            }
            public double NextDouble()
            {
              return interval * Next();
            }
          }
        
          class EntropyAccumulator
          {
            private List<byte> currentEntropy = new List<byte>();
            public double CurrentEntropyBits { get; private set; }
            public void Clear()
            {
              currentEntropy.Clear();
              CurrentEntropyBits = 0;
            }
            public void Add(byte[] entropy, double effectiveBits)
            {
              currentEntropy.AddRange(entropy);
              CurrentEntropyBits += effectiveBits;
            }
            public byte[] GetBytes(int count)
            {
              using (var hasher = new SHA512Managed())
              {
                count = Math.Min(count, hasher.HashSize / 8);
                var bytes = new byte[count];
                var hash = hasher.ComputeHash(currentEntropy.ToArray());
                Array.Copy(hash, bytes, count);
                return bytes;
              }
            }
            public byte[] GetPackagedEntropy()
            {
              // Returns a compact byte array that represents almost all of the entropy.
              return GetBytes((int)(CurrentEntropyBits / 8));
            }
            public double GetDouble()
            {
              // returns a uniformly distributed number on [0-1)
              return (double)BitConverter.ToUInt64(GetBytes(8), 0) / ((double)UInt64.MaxValue + 1);
            }
            public double GetInt(int maxValue)
            {
              // returns a uniformly distributed integer on [0-maxValue)
              return (int)(maxValue * GetDouble());
            }
          }
        
          class Program
          {
            static void Main(string[] args)
            {
              var random = new LowEntropyRandom(2);  // this only provides 1 bit of entropy per call
              var desiredEntropyBits = 64; // enough for a double
              while (true)
              {
                var adder = new EntropyAccumulator();
                while (adder.CurrentEntropyBits < desiredEntropyBits)
                {
                  adder.Add(BitConverter.GetBytes(random.Next()), random.EffectiveEntropyBits);
                }
                Console.WriteLine(adder.GetDouble());
                Console.ReadLine();
              }
            }
        
          }
        }
        

        由于我使用的是 512 位哈希函数,因此这是您可以从 EntropyAccumulator 中获取的最大熵。如果有必要,这可以修复。

        【讨论】:

          【解决方案8】:

          如果我正确理解您的问题,那就是 rand() 生成间隔精细但最终离散的随机数。如果我们将它乘以很大的 (y-x),这会将这些间隔精细的浮点值分散开来,从而丢失 [x,y] 范围内的许多浮点值。可以吗?

          如果是这样,我认为 Dialecticus 已经给出了解决方案。让我解释一下为什么他是对的。

          首先,我们知道如何生成一个随机浮点数,然后向其添加另一个浮点值。由于加法,这可能会产生舍入误差,但它只会在最后一个小数位。如果您想要更高的精度,请使用双精度数或具有更精细数值分辨率的东西。因此,有了这个警告,问题并不比在 [0,y-x] 范围内找到一个密度均匀的随机浮点数更难。假设 y-x = z。显然,由于 z 是浮点数,它可能不是整数。我们分两步处理这个问题:首先我们生成小数点左侧的随机数字,然后生成小数点右侧的随机数字。两者一致意味着它们的总和也均匀分布在 [0,z] 范围内。令 w 为最大整数

          那么在随机整数是最大整数(即 w)并且我们添加到它的随机浮点数大于 z - w 以使随机数超过允许的最大值的极端情况下呢?答案很简单:再次执行所有操作并检查新结果。重复直到你得到允许范围内的数字。这是一个简单的证明,即如果均匀生成的随机数在允许范围之外,则会被丢弃并再次生成,这会导致均匀生成的随机 in 允许范围。一旦您进行了这一关键观察,您就会发现 Dialecticus 符合您的所有标准。

          【讨论】:

          • 他的解决方案不符合标准。请参阅我对他的回答的评论。
          【解决方案9】:

          当您使用 random() 生成随机数时,您会得到一个介于 0 和 1 之间的浮点数,具有未知的精度(或密度,您可以命名)。

          当你将它与一个数字 (NUM) 相乘时,你会失去这个精度,即 lg(NUM)(基于 10 的对数)。因此,如果乘以 1000 (NUM=1000),就会丢失最后 3 位数字 (lg(1000) = 3)。

          您可以通过在缺少 3 位数字的原始数据中添加一个较小的随机数来纠正此问题。但是你不知道精度,所以你无法确定它们到底在哪里。

          我可以想象两种情况:

          (X = 范围开始,Y = 范围结束)

          1:您定义精度(PREC,例如 20 位,因此 PREC=20),并认为它足以生成随机数,因此表达式为:

          ( random() * (Y-X) + X ) + ( random() / 10 ^ (PREC-trunc(lg(Y-X))) )
          

          带数字:(X = 500,Y = 1500,PREC = 20)

          ( random() * (1500-500) + 500 ) + ( random() / 10 ^ (20-trunc(lg(1000))) )
          ( random() * 1000 + 500 ) + ( random() / 10 ^ (17) )
          

          这有一些问题:

          • 2阶段随机生成(随机多少?)
          • 第一个随机返回 1 -> 结果可能超出范围

          2:通过随机数猜测精度

          您定义了一些尝试(例如 4 次)通过生成随机数来计算精度并每次计算精度:

          - 0.4663164 -> PREC=7
          - 0.2581916 -> PREC=7
          - 0.9147385 -> PREC=7
          - 0.129141  -> PREC=6 -> 7, correcting by the average of the other tries
          

          这是我的想法。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-09-22
            • 2013-07-22
            • 2020-02-12
            相关资源
            最近更新 更多