如何有效地将几个字节转换为范围之间的整数?

时间:2012-11-10 16:29:59

标签: math integer

我正在写一些从远程随机数生成源读取字节(只是一个List<int>)的东西,它非常慢。为了这个和我的个人要求,我想从源中检索尽可能少的字节

现在我正在尝试实现签名看起来像的方法:

int getRandomInteger(int min, int max)

我有两种理论可以从随机源中获取字节,并将它们转换为整数。

方法#1是天真的。获取(max - min) / 256个字节并添加它们。它可以工作,但它会从我的慢速随机数生成器源获取大量字节。例如,如果我想获得一百万到零之间的随机整数,它将获取近4000字节...这是不可接受的。

方法#2对我来说听起来很理想,但我无法想出算法。它是这样的:

让我们以min:0,max:1000为例。

  • 计算ceil(rangeSize / 256),在这种情况下为ceil(1000 / 256) = 4。现在从源中获取一(1)个字节。
  • 将这一个字节从0-255范围缩放到0-3范围(或1-4),并让它确定我们使用哪个组。例如。如果字节是250,我们将选择第4组(代表最后250个数字,在我们的范围内为750-1000)。
  • 现在获取另一个字节并从0-255缩放到0-250并让它确定我们所拥有的组内的位置。所以如果第二个字节是例如120,那么我们的最终整数是750 + 120 = 870

在那种情况下,我们只需要总共获取2个字节。然而,它要复杂得多,好像我们的范围是0-1000000,我们需要几个“组”。

我如何实现这样的事情?我对Java / C#/ JavaScript代码或伪代码没问题。

我还希望保持结果不会丢失熵/随机性。所以,我有点担心缩放整数。

4 个答案:

答案 0 :(得分:2)

不幸的是你的方法#1被打破了。例如,如果min为0且max为510,则添加2个字节。只有一种方法可以获得0结果:两个字节都为零。这个机会是(1/256)^ 2。然而,有很多方法可以获得其他值,例如100 = 100 + 0,99 + 1,98 + 2 ......所以100的概率要大得多:101(1/256)^ 2.

或多或少标准的做法是:

Let R = max - min + 1   -- the number of possible random output values
Let N = 2^k >= mR, m>=1  -- a power of 2 at least as big as some multiple of R that you choose.
loop
   b = a random integer in 0..N-1 formed from k random bits
while b >= mR -- reject b values that would bias the output
return min + floor(b/m)

这被称为拒绝方法。它会丢弃随机选择的二进制数字,这会对输出产生偏差。如果min-max+1恰好是2的幂,那么你将被拒绝。

如果你有m=1min-max+1只是2的一个大的力量,那么拒绝接近一半。在这种情况下,你肯定想要更大的m

通常,较大的m值会导致较少的拒绝,但当然它们每个数字需要更多的位数。有一种可能的最佳算法来挑选m

此处介绍的其他一些解决方案存在问题,但我很抱歉,我现在没有时间发表评论。如果有兴趣,也许在几天内。

答案 1 :(得分:1)

3个字节(一起)为您提供0..16777215范围内的随机整数。您可以使用此值的20位来获取范围0..1048575并丢弃值&gt;百万

答案 2 :(得分:1)

range 1 to r
256^a >= r

first find 'a' 

get 'a' number of bytes into array A[]

num=0
for i=0 to len(A)-1
    num+=(A[i]^(8*i))
next

random number = num mod range

答案 3 :(得分:1)

您的随机来源为每次通话提供8个随机位。对于[min,max]范围内的整数,您需要ceil(log2(max-min + 1))位。

假设您可以使用某个函数从源获取随机字节:

bool RandomBuf(BYTE* pBuf , size_t nLen); // fill buffer with nLen random bytes

现在,您可以使用以下函数在给定范围内生成随机值:

// --------------------------------------------------------------------------
// produce a uniformly-distributed integral value in range [nMin, nMax]
// T is char/BYTE/short/WORD/int/UINT/LONGLONG/ULONGLONG
template <class T> T RandU(T nMin, T nMax)
{
    static_assert(std::numeric_limits<T>::is_integer, "RandU: integral type expected");

    if (nMin>nMax)
        std::swap(nMin, nMax);

    if (0 == (T)(nMax-nMin+1)) // all range of type T
    {
        T nR;
        return RandomBuf((BYTE*)&nR, sizeof(T)) ? *(T*)&nR : nMin;
    }

    ULONGLONG nRange    = (ULONGLONG)nMax-(ULONGLONG)nMin+1        ; // number of discrete values
    UINT      nRangeBits= (UINT)ceil(log((double)nRange) / log(2.)); // bits for storing nRange discrete values
    ULONGLONG nR                                                   ;

    do
    {
        if (!RandomBuf((BYTE*)&nR, sizeof(nR)))
            return nMin;

        nR= nR>>((sizeof(nR)<<3) - nRangeBits); // keep nRangeBits random bits
    }
    while (nR >= nRange);                       // ensure value in range [0..nRange-1]

    return nMin + (T)nR;                        // [nMin..nMax]
}

由于您总是得到8位的倍数,因此可以在调用之间保存额外的位(例如,您可能只需要16位中的9位)。它需要一些比特操作,并且由你决定是否值得努力。

如果您使用“半位”,您可以节省更多:我们假设您要生成[1..5]范围内的数字。每个随机值都需要log2(5)= 2.32位。使用32个随机位,您实际上可以在此范围内生成楼层(32 / 2.32)= 13个随机值,但需要额外的工作量。