How to efficiently convert a few bytes into an integer between a range?

后端 未结 4 2125
离开以前
离开以前 2021-01-19 17:45

I\'m writing something that reads bytes (just a List) from a remote random number generation source that is extremely slow. For that and my personal

相关标签:
4条回答
  • 2021-01-19 18:02

    Your random source gives you 8 random bits per call. For an integer in the range [min,max] you would need ceil(log2(max-min+1)) bits.

    Assume that you can get random bytes from the source using some function:

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

    Now you can use the following function to generate a random value in a given range:

    // --------------------------------------------------------------------------
    // 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]
    }
    

    Since you are always getting a multiple of 8 bits, you can save extra bits between calls (for example you may need only 9 bits out of 16 bits). It requires some bit-manipulations, and it is up to you do decide if it is worth the effort.

    You can save even more, if you'll use 'half bits': Let's assume that you want to generate numbers in the range [1..5]. You'll need log2(5)=2.32 bits for each random value. Using 32 random bits you can actually generate floor(32/2.32)= 13 random values in this range, though it requires some additional effort.

    0 讨论(0)
  • 2021-01-19 18:14

    Unfortunately your Approach #1 is broken. For example if min is 0 and max 510, you'd add 2 bytes. There is only one way to get a 0 result: both bytes zero. The chance of this is (1/256)^2. However there are many ways to get other values, say 100 = 100+0, 99+1, 98+2... So the chance of a 100 is much larger: 101(1/256)^2.

    The more-or-less standard way to do what you want is to:

    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)
    

    This is called the method of rejection. It throws away randomly selected binary numbers that would bias the output. If min-max+1 happens to be a power of 2, then you'll have zero rejections.

    If you have m=1 and min-max+1 is just one more than a biggish power of 2, then rejections will be near half. In this case you'd definitely want bigger m.

    In general, bigger m values lead to fewer rejections, but of course they require slighly more bits per number. There is a probabilitistically optimal algorithm to pick m.

    Some of the other solutions presented here have problems, but I'm sorry right now I don't have time to comment. Maybe in a couple of days if there is interest.

    0 讨论(0)
  • 2021-01-19 18:16
    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
    
    0 讨论(0)
  • 2021-01-19 18:17

    3 bytes (together) give you random integer in range 0..16777215. You can use 20 bits from this value to get range 0..1048575 and throw away values > 1000000

    0 讨论(0)
提交回复
热议问题