[libvirt] [PATCH 1/8] Add virRandom() API to generate numbers with non-power-of-2 limit

Eric Blake eblake at redhat.com
Fri Aug 10 16:06:39 UTC 2012


On 08/10/2012 09:31 AM, Daniel P. Berrange wrote:
> On Fri, Aug 10, 2012 at 08:58:04AM -0600, Eric Blake wrote:
>> On 08/10/2012 07:47 AM, Daniel P. Berrange wrote:
>>> From: "Daniel P. Berrange" <berrange at redhat.com>
>>>
>>> The current virRandomBits() API is only usable if the caller wants
>>> a random number in the range [0, (n-1)] where n is a power of two.
>>> This adds a virRandom() API which works for upper limits which are
>>> not a power of two. It works by using virRandomBits() to generate
>>> a number to the next nearest power of 2 limit, and then scales it
>>> down.
>>
>> I like the idea, but NAK to this version of the patch.  That algorithm
>> is not uniform.
>>

>>> +    while (tmp) {
>>> +        tmp >>= 1;
>>> +        bits++;
>>> +    }
>>
>> Slow.  I would suggest using gcc's __builtin_clz (the way qemu does),
>> except that gnulib doesn't yet have a module exposing it.  I guess that
>> means I'd better write a quick gnulib module.  Once you have clz(), this
>> is much simpler as:
>>
>> assert(max);
>> bits = 64 - clz(max);

This part is independently useful, while we still discuss the algorithm;
I'll have the gnulib module complete later today.

>> that is, you have a 50% chance of getting 0, but only a 25% chance of
>> getting 1 or 2 - that is not a uniform distribution.  The ONLY way to do
>> this is:
>>
>> do {
>>     ret = virRandomBits(bits);
>> } while (ret >= max);
>>
>> Then, with the same input of max == 3, you have a 25% chance of calling
>> virRandomBits at least twice, a 12.5% chance of calling it at least
>> three times, and so on, but you are guaranteed that you will eventually
>> get a result that is uniformly distributed.
> 
> Hmm, it was the looping that I used originally, but I wanted to get
> something that had a deterministic upper bound, instead of just a
> probablistic bound.

Oh, I see your point - you don't want to drag on for a long time on the
rare occasions where probabilities are against you.

> 
> The alternative I wanted to use was  drand48_t() which returns a double
> in the range 0.0 -> 1.0, with 48 bits of entropy. You could multiply by
> 'max', and cast to int. But this isn't portable and is not fixed by
> GNULIB.

Might not be too hard to make drand48_r into a gnulib module (harder
than clz, but not intractible, so I'll take a shot at it, but no
promises).  Using drand48_r does still hit non-uniformity due to
rounding, but with much smaller fuzz factor (that is, instead of my
example of 50/25/25, you'd be more like 25.000004, 24.999993,
24.999993), which I can live with as being in the noise.  Furthermore,
since it only gives 48 bits of entropy, if 'max' is larger than 1<<47
you'll have to do some legwork yourself (I'm not quite sure what that
legwork involves to still be fair), so I'd feel more comfortable if we
capped this function to uint32_t and require the use of powers-of-2 for
anything larger than a 32-bit max.

> I'm wondering if we could emulate drand48_t() ourselves by doing
> something like this:
> 
>   #include <ieee754.h>
> 
>   double virRandom(void) {
>     union ieee754_double val;
> 
>     val.ieee.negative = 0;
>     val.ieee.exponent = IEEE754_DOUBLE_BIAS;
>     val.ieee.mantissa0 = virRandomBits(20);
>     val.ieee.mantissa0 = virRandomBits(32);
> 
>     return val.d - 1.0;
>   }

No need to do that.  -lm already gives us a very nice function: ldexp().
 Basically, you take an integer in the range [0,1<<48), then multiply
that by 2**-48 using ldexp().

-- 
Eric Blake   eblake at redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 620 bytes
Desc: OpenPGP digital signature
URL: <http://listman.redhat.com/archives/libvir-list/attachments/20120810/812b5220/attachment-0001.sig>


More information about the libvir-list mailing list